⬢ DragonFlyBSD Kernel Audit
DF-4696 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4696",
  "status": "not_reproduced",
  "reproduced": 0,
  "impact": "none",
  "confidence": "certain",
  "verdict": "Divide-by-zero at sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:439 (lb_memory_size / memory_line_size_y; same at :440/:441) in dpp1_dscl_calc_lb_num_partitions, called from dpp1_dscl_find_lb_memory_config (:476) on the atomic-commit program path (update_scaler, dcn10_hw_sequencer.c:2029-2041 -> dpp1_dscl_set_scaler_manual_scale dscl.c:647). line_size = min(viewport.width, recout.width) becomes exactly 0 because calculate_recout clamps recout.width to (dst.x+dst.width)-recout.x == 0 for a plane positioned flush off the right edge of the stream window (dc_resource.c:639-643) while viewport.width stays >= 16 in SOURCE pixels (dc_resource.c:543), so the only degeneracy guard (dc_resource.c:1125-1127) passes; amdgpu_dm never clips plane rects to the CRTC (amdgpu_dm.c:2210-2240), plane validation is format-only (amdgpu_dm.c:3585-3603, dc_resource.c:2891-2900, dcn10_resource.c:1131-1138), and the DCN10 taps check computes pixel_width=min(viewport,recout) but never reads it (dcn10_dpp.c:148-151) - the config validates DC_OK and the commit faults with integer #DE -> kernel panic. Could not be executed on the audit QEMU guest because it has no AMD GPU (QEMU std VGA 0x1234:1111, no /dev/dri, no 0x1002 PCI device): the amdgpu DC module cannot attach, so the kernel path is unreachable at runtime on this guest (same constraint as DF-4373/DF-4582 family members DF-4373). The full producer+sink arithmetic was instead executed on the guest as a userland twin with the kernels integer types: the attack geometry yields line_size=0, memory_line_size_y=0 and the exact division from :439 faults with SIGFPE (exit 136) on 3/3 runs while the control geometry completes - the static chain is certain, the runtime trigger needs DCN1.0 (Raven) hardware. Local DoS (kernel panic) by an unprivileged session user (DRM master) on real DCN1.0 hardware; not memory corruption, no privesc avenue.",
  "exploit_chain": "unprivileged session user (DRM master) -> DRM_IOCTL_MODE_ATOMIC commit, plane: src 640x480 XRGB8888, CRTC_X=640 (== mode width), CRTC_W=1, CRTC_H=480 on a 640x480 mode -> amdgpu_dm fill_rects_from_plane_state copies raw rects, clip=dst (amdgpu_dm.c:2210-2240) -> dm_plane_atomic_check + dc_validate_plane format-only (amdgpu_dm.c:3585-3603, dc_resource.c:2891-2900, dcn10_resource.c:1131-1138) -> resource_build_scaling_params: viewport.width=1*640/1=640 passes the >=16 guard (dc_resource.c:543,1125-1127); calculate_recout: recout.x=640, recout.width=(0+640)-640=0 exactly (dc_resource.c:639-643) -> dpp_get_optimal_number_of_taps returns true (pixel_width computed and never used, dcn10_dpp.c:148-151) -> validation DC_OK -> commit: dcn10_update_dchubp_dpp (dcn10_hw_sequencer.c:2101-2104) -> update_scaler (:2029-2041) -> dpp1_dscl_set_scaler_manual_scale (dscl.c:647, memcmp passes, not DSCL_BYPASS) -> dpp1_dscl_find_lb_memory_config (:463, use_max_lb=false) -> dpp1_dscl_calc_lb_num_partitions (:476) -> line_size=min(640,0)=0, memory_line_size_y=(0*10+71)/72=0 (:411-416) -> 816/0 at :439 -> integer #DE in kernel mode -> kernel panic (guest userland twin: SIGFPE exit 136, 3/3 runs)",
  "evidence": "run.log/run.2.log: attack case prints viewport.width=640 recout.x=640 recout.width=0 line_size=0 memory_line_size_y=0 then Floating point exception exit 136; control case completes with clamped num_part_y=64; env.txt: no /dev/dri, no vendor 0x1002 PCI, guest uname; build_first_attempt.log: the UB-pitfall record (gcc8 elides a constant-numerator x/0 - kernel uses runtime divisor => real idiv => #DE, cf. DF-4582 trap-18 panic on this same guest); VERDICT.md has the link-by-link static chain with path:line",
  "kernel_refs": [
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:411",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:416",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:439",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:440",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:441",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:463",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:476",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp_dscl.c:647",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp.c:141",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_dpp.c:148",
    "sys/dev/drm/amd/display/dc/core/dc_resource.c:543",
    "sys/dev/drm/amd/display/dc/core/dc_resource.c:615",
    "sys/dev/drm/amd/display/dc/core/dc_resource.c:639",
    "sys/dev/drm/amd/display/dc/core/dc_resource.c:1125",
    "sys/dev/drm/amd/display/dc/core/dc_resource.c:2891",
    "sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:2210",
    "sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:3585",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_hw_sequencer.c:2029",
    "sys/dev/drm/amd/display/dc/dcn10/dcn10_hw_sequencer.c:2101"
  ],
  "poc_changes": "Seed was authored fresh for this batch (no prior pack). Two fixes during verification: (1) a malformed dead-variable line failed to compile (build_first_attempt.log) - replaced with a faithful mirror of the kernels dead pixel_width (dcn10_dpp.c:148-151); (2) the naive division replica let gcc8 exploit the C UB of a constant-numerator x/0 and silently avoided the fault - fixed by hiding the divisor behind an asm barrier so a real divide instruction executes, making SIGFPE the faithful twin of the kernel #DE (kernel divisor is a runtime value => real idiv => fault; cf. DF-4582).",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "runtime_sec": 420,
  "guest_dirty": 0,
  "build_cmd": "cd /tmp/df4696 && sh build.sh  (cc -O2 -Wall -o trigger_df4696 trigger_df4696.c)",
  "run_cmd": "cd /tmp/df4696 && sh run.sh   (exit 136 = 128+SIGFPE on attack case)",
  "code_hash": "cb72495ef4eb86740e96ea5d21dd4b867028603d457c9b32279e519f7e8ca500",
  "notes": "Second member of the recout==0 divide family after DF-4373 (DCE side, same producer chain, crashes during validation); this one is the DCN10 commit-path sink. DF-4582 proves this guest panics (trap 18) on reachable integer div-by-zero. Neither baseline nor patched run is possible on this guest (no AMD GPU), so fix validation is honestly not_testable; fix.diff guards all three divisors (also covering the lb_bpc==-1 unsupported-depth path) and makes every LB config validation fail cleanly via the existing return path.",
  "recommended_fix": "In dpp1_dscl_calc_lb_num_partitions, return *num_part_y=*num_part_c=0 without dividing when memory_line_size_y/c/a <= 0 (degenerate viewport/recout or unsupported depth); callers dpp1_dscl_is_lb_conf_valid then rejects the config instead of #DE-panicking.",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Fix cannot be exercised on the audit guest: no AMD GPU means the amdgpu DC module (which contains dcn10_dpp_dscl.c) never attaches, so neither baseline nor patched kernel can run the path. fix.diff is authored against the read-only tree; it guards all three divisors (<=0) before the divisions at :439-441 and returns zero partitions, which makes dpp1_dscl_is_lb_conf_valid fail every LB config (vtaps>=1 vs 0 partitions) so dpp1_dscl_find_lb_memory_config falls through to the default config with the existing ASSERT warning instead of faulting. The userland twin of the guarded arithmetic returns 0 partitions for the attack geometry with no fault (code-reviewed by hand; not executed as a kernel).",
  "fix_evidence": "fix.diff (divisor guard, git-apply-able); VERDICT.md Fix validation section"
}