DF-4995 / verdict.json
{ "finding_id": "DF-4995", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced at logic level with a verbatim userland replica of the dcn10 irq table (irq_service_dcn10.c:110-331 hpd_ack/funcs/entry-macros/table body, plus to_dal_irq_source_dcn10 :41-108 with the real DCN_1_0 SRCID/CTXID constants), the enum (irq_types.h:40-148), and the generic dispatch (irq_service.c:77-158), run on the audit guest. The shared enum runs to DAL_IRQ_SOURCES_NUMBER=90 but irq_source_info_dcn10[] stops at DC_IRQ_SOURCE_VBLANK6 (=83): DC_IRQ_SOURCE_DC1..6_VLINE0 (84..89) stay zero-initialized with .funcs==NULL, and find_irq_source_info (:82) bounds-checks only the index, so dal_irq_service_ack (:152, invoked first by dal_irq_service_set at :116) and dal_irq_service_set (:118) dereference info->funcs->ack/.set on NULL \u2014 replica set(DC_IRQ_SOURCE_DC1_VLINE0) SIGSEGVs deterministically (exit 139, run.log + run.2.log), the kernel-equivalent being a page fault at ~offsetof(.ack) => hard panic. This is the DF-4713/DF-4932 NULL-funcs-dispatch family rooted in this files incomplete table \u2014 the family tail after dce80/dce110 (DF-4932 filed dce110s identical gap; each asic table is its own code). Reachability is latent and traced honestly: every in-tree dc_interrupt_set/ack caller was checked (dm_irq_state PFLIP/VBLANK+otg_inst bounded 0..5; dm_set_vblank VBLANK+otg_inst; hpd_init/fini HPD/HPDRX; suspend/resume loops HPD1..HPD6RX; the IH path amdgpu_dm_irq.c:477-487 can only receive to_dal-mapped covered sources or INVALID \u2014 the harness to_dal walk (13x13 src/ext combos incl. 0xdead) maps 168 to covered sources and 0 to 84..89) \u2014 nothing passes 84..89 into a dcn10 service today, so no unprivileged trigger exists; the panic is one future caller away (DM change using VLINE0 on DCN, or the DAL_VALID_IRQ_SRC_NUM off-by-one at irq_types.h:158 already noted under DF-4932). The guest also has no AMD GPU (0 PCI 0x1002 devices), so the kernel trigger is hardware-gated regardless. No write primitive, no leak; honest ceiling is a kernel panic (local DoS). Fix validated: fix.diff (six vline0 dummy entries completing the table over the shared enum) applied single-fix to guest /usr/src (patch -V none -p1, hunk succeeded at 328; patched md5 c31141c9 recorded at amdgpu_module_build.log:790 at build time); full amdgpu.ko rebuilt under -Werror (make -j6, RC=0, amdgpu.ko linked, irq_service_dcn10.c compiled at log:578, zero error:/warning: diagnostics); harness fixed mode (same shape: dummy handlers + funcs guards) exits 0 vs baseline 139; guest sources reverted md5-verified pristine (6ec2956a), make clean done, guest returned clean.", "exploit_chain": "", "evidence": "run.log + run.2.log (decisive: table[84..89] (.funcs == NULL), 6 NULL-funcs entries in irq_source_info_dcn10[], controls incl. control to_dal walk ... 0 to VLINE0 84..89 (ok \u2014 latent), trigger: dal_irq_service_set(DC_IRQ_SOURCE_DC1_VLINE0=84) ... Segmentation fault ... EXIT=139 both runs; fixed survived the VLINE0 set (fixed behavior) EXIT=0); panic.txt (fault signature); dcn10_vline0gap.c (verbatim table/dispatch/to_dal replica); amd_fix_apply.log (pristine/patched md5s) + amdgpu_module_build.log (-Werror full module build RC=0) + fix_run.log (fixed-mode run); VERDICT.md (full reachability trace + negative results)", "kernel_refs": [ "sys/dev/drm/amd/display/dc/irq/dcn10/irq_service_dcn10.c:245", "sys/dev/drm/amd/display/dc/irq/dcn10/irq_service_dcn10.c:331", "sys/dev/drm/amd/display/dc/irq/irq_service.c:82", "sys/dev/drm/amd/display/dc/irq/irq_service.c:116", "sys/dev/drm/amd/display/dc/irq/irq_service.c:118", "sys/dev/drm/amd/display/dc/irq/irq_service.c:152", "sys/dev/drm/amd/display/dc/irq_types.h:140", "sys/dev/drm/amd/display/dc/core/dc.c:1682", "sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c:477", "sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:3007" ], "poc_changes": "Authored fresh (no seed), following the DF-4932 harness methodology. Iterations: (1) reg-field accessor macros needed the real dm_services.h:120-126 paste shape (reg_name ## __ ## reg_field ## _MASK/__SHIFT) and set_reg_field_value must ASSIGN back (dm_services.h) so the hpd_ack controls polarity write is observable (0xffffffff -> 0xfffffffb); (2) forward declaration of dal_irq_service_ack_generic placed before hpd_ack; (3) DragonFly patch(1) has no --no-backup-if-mismatch \u2014 used patch -V none -d /usr/src -p1 after the first interactive skip (caught by md5, file left pristine, redone). Added beyond the family harness: a to_dal_irq_source_dcn10 walk control (169 src/ext combos) proving the IH-driven entry can never return 84..89 \u2014 the latent-reachability evidence \u2014 and an hpd_ack control exercising this files only live bespoke function.", "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": 1400, "guest_dirty": 0, "build_cmd": "sh build.sh (cc -O2 -Wall -std=gnu11 [-DFIX_DF4995])", "run_cmd": "sh run.sh (baseline then fixed; run.2.log = determinism rerun)", "code_hash": "37c3dbc4f94e5d307bbc4c40f511fb04bf88d54177a99483a5e332b9d4911a81", "notes": "Hardware-gated (guest has no AMD GPU: 0 PCI 0x1002 devices, no drm klds) and caller-gated (latent) \u2014 classification mirrors DF-4932 (dce110) exactly; severity Low, bucket base:dev, CWE-476. Negative results on this file: DF-4933s vblank_set-underflow family does NOT extend to dcn10 (vblank/vupdate/pflip funcs are {.set=NULL,.ack=NULL} -> generic constant-reg path; no .src_id, no pipe arithmetic); pflip 4 real + PFLIP5/6/UNDERLAY0 explicit dummies; dal_irq_service_dummy_set/ack are true no-ops (dce110:177-192) so dummy entries cannot MMIO stale reg 0; to_dal has default:INVALID at both switch levels; construct/create is upstream-identical (kzalloc NULL-checked; dal_irq_service_construct is void here, ctx-NULL path unreachable from dc_construct); no user-controlled input reaches this file at all (compile-time tables + GPU IV src_id/ext_id). dce120/dce80 share the 84..89 gap shape (noted under DF-4932 for maintainers). Companion file in this batch: sys/dev/drm/drm_lock.c audited clean (dead code, all bodies #if 0; live stubs return 0 without touching user memory).", "recommended_fix": "Make the dcn10 table total over the shared enum ([DC_IRQ_SOURCE_DC1..6_VLINE0] = dummy_irq_entry(), after vblank_int_entry(5)); for defense-in-depth also add the !info->funcs guards before the info->funcs->set/->ack dereferences in dal_irq_service_set/dal_irq_service_ack (irq_service.c:118/:152 \u2014 DF-4932s shared-code guard) so every asic table is protected against gaps.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fixed: fix.diff (six vline0 dummy entries) applied single-fix to guest /usr/src with patch -V none -d /usr/src -p1 (Hunk #1 succeeded at 328; patched md5 c31141c9ba1f50da042a2f63da779a97 verified at build time, amdgpu_module_build.log:790); full amdgpu.ko rebuilt under -Werror in /usr/src/sys/dev/drm/amd/amdgpu (make -j6, MODULE_BUILD_RC=0, amdgpu.ko linked 3741272 bytes, irq_service_dcn10.c compiled at log:578, zero error:/warning: diagnostics); behaviorally, the fixed-mode harness (same table shape: vline0 handled by dal_irq_service_dummy_ack/set which log and return false) survives the VLINE0 set with exit 0 while the baseline SIGSEGVs with 139; guest sources reverted md5-verified pristine (6ec2956a479058297a5c730b09c32423), make clean (0 .o/.ko), /tmp scratch removed, guest returned clean.", "fix_evidence": "fix.diff; amd_fix_apply.log; amdgpu_module_build.log (also copied as fix_build.log); fix_run.log (fixed-mode run); run.log (baseline section); VERDICT.md Fix validation" } |