irq_source_info_dce110[] leaves DC_IRQ_SOURCE_DC{1..6}_VLINE0 (enum 84..89) zero-initialized with .funcs==NULL; dal_irq_service_set/ack dereference info->funcs without a NULL check (DF-4713 family)
Summary
The 90-entry table fills only 0..83 - the six VLINE0 entries stay zero and find_irq_source_info checks only the index, so a set/ack of a VLINE0 source reads NULL+offsetof in the unguarded funcs deref. No in-tree caller passes 84..89 today (traced every set/ack caller) - a loaded trap incl. the shared shape in dce120/dce80 and the DAL_VALID_IRQ_SRC_NUM <= off-by-one noted. Replica harness: table walk shows the six NULLs, trigger SIGSEGVs in ack; dummy-entries + guards fix validated on amdgpu.ko -Werror.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4932 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| dce110_vline0gap.c | β | 19.7 KB | view raw | |
| build.sh | β | 109 B | view raw | |
| run.sh | β | 117 B | view raw | |
| build.log | β | 50 B | view raw | |
| run.log | β | 1.8 KB | view raw | |
| run.2.log | β | 1.8 KB | view raw | |
| amd_fix_apply.log | β | 568 B | view raw | |
| amdgpu_module_build.log | β | 12.5 KB | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| env.txt | β | 965 B | view raw | |
| VERDICT.md | β | 4.8 KB | β raw |
DF-4932 VERDICT
Status: reproduced (logic level, verbatim userland replica on the audit
guest). Kernel trigger hardware-gated (no AMD GPU β 0 PCI 0x1002 devices) and β
importantly β caller-gated/latent: no in-tree caller of dc_interrupt_set()/
dc_interrupt_ack() passes a DC_IRQ_SOURCE_DC*_VLINE0 source on a dce110
service today. The defect is proven; the panic requires a future/alternate
caller.
The defect
enum dc_irq_source (sys/dev/drm/amd/display/dc/irq_types.h:40-148) runs to
DAL_IRQ_SOURCES_NUMBER = 90 and includes DC_IRQ_SOURCE_DC1_VLINE0 ..
DC_IRQ_SOURCE_DC6_VLINE0 = 84..89 (VBLANK1..6 = 78..83).
irq_source_info_dce110[DAL_IRQ_SOURCES_NUMBER]
(irq_service_dce110.c:226-313) is initialized with designated initializers
covering exactly enum values 0..83 (INVALID, HPD, HPDRX, I2C_DDC, DPSINK,
TIMER, PFLIP, PFLIP_UNDERLAY0, GPIOPAD0-30, DC1-6UNDERFLOW, DMCU_SCP, VBIOS_SW,
VUPDATE1-6, VBLANK1-6). Entries 84..89 (the VLINE0 block) receive no
initializer β they stay zero: enable_reg=0 β¦ .funcs=NULL.
The generic dispatch only bounds-checks the index:
find_irq_source_info()βirq_service.c:82:if (source >= DAL_IRQ_SOURCES_NUMBER || source < DC_IRQ_SOURCE_INVALID) return NULL;β 84..89 pass.dal_irq_service_ack()βirq_service.c:152:if (info->funcs->ack) β¦β reads*(NULL + offsetof(ack))β fault.dal_irq_service_set()βirq_service.c:116,118: acks first (same deref), thenif (info->funcs->set).
This is the same NULL-funcs-dispatch shape as the reproduced DF-4713 family (amdgpu_irq.c), here rooted in this file's incomplete table rather than an unregistered DRM-level source.
Reachability audit (why "latent")
Every in-tree path into the service was traced:
dc_interrupt_set/ack(core/dc.c:1681-1693) do no source validation beyond the generic index check.- Callers:
dm_irq_state(amdgpu_dm_irq.c:520-556) passesIRQ_TYPE_PFLIP/IRQ_TYPE_VBLANK + acrtc->otg_instwithotg_instfromstatus->primary_otg_inst = pipe_ctx->stream_res.tg->inst(amdgpu_dm.c:4856, dc_resource.c:2055) β hardware-bounded 0..5 on dce110;amdgpu_dm_hpd_init/finipassdc_link->irq_source_hpd{,_rx}(HPD/HPDRX);amdgpu_dm.c:3014passesIRQ_TYPE_VBLANK + otg_inst(same bound). - The interrupt arrival path (
amdgpu_dm_irq_handlerβdc_interrupt_to_irq_sourceβto_dal_irq_source_dce110) can only return {INVALID, VBLANK1-6, VUPDATE1-6, PFLIP1-6, HPD1-6, HPD RX1-6} β all covered; INVALID maps to the dummy entry.
So nothing passes 84..89 into a dce110 service in this tree. The gap becomes a
panic the moment any code (a DCN-port, a DM change, or
DAL_VALID_IRQ_SRC_NUM's own off-by-one β irq_types.h:158 allows
src == DAL_IRQ_SOURCES_NUMBER, see notes) starts forwarding VLINE0 or
edge-of-enum sources. dce120/dce80 tables have the identical shape (context
note for the maintainers).
Reproduction (run.log / run.2.log, audit guest, cc 8.3)
- Table walk: exactly
table[84] β¦ table[89] (.funcs == NULL); 6 gaps. - Control: index 90 rejected;
PFLIP1enable works via the generic RMW path. - Trigger:
dal_irq_service_set(DC_IRQ_SOURCE_DC1_VLINE0=84)crashes insidedal_irq_service_ackat theinfo->funcs->ackload β SIGSEGV exit 139, identical across two runs. - Fixed: dummy entries +
!info->funcsguards βdal_irq_service_dummy_ack/setlog and return false; exit 0.
Impact ceiling
A NULL-dereference in kernel context β hard panic (local DoS) on the machine owning the display. No write primitive, no info leak, no escalation chain. Not triggerable by an unprivileged local user through any existing path.
Fix validation
fix.diff = six dummy_irq_entry() rows for DC_IRQ_SOURCE_DC{1..6}_VLINE0 in
the dce110 table + if (!info->funcs) {DC_LOG_ERROR; return false;} guards in
dal_irq_service_set/dal_irq_service_ack (protects every asic table against
any future gap, incl. dce120/dce80). Applied to the guest's /usr/src together
with DF-4933's diff (patched md5s 724635b5β¦/305f0881β¦ vs pristine
95266b5bβ¦/35607b1aβ¦ β the first build attempt accidentally skipped the
irq_service.c hunks (interactive patch on an already-patched tree); redone from
pristine with both files verified patched at build time). amdgpu.ko rebuilt
under -Werror: RC=0, zero errors, irq_service.o irq_service_dce110.o linked
(amdgpu_module_build.log). Harness fixed mode: no crash, graceful dummy
handling. Guest sources reverted md5-verified pristine.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedfixed: fix.diff (vline0 dummy entries + funcs guards) applied to guest /usr/src with DF-4933's diff; both patched files md5-verified at build time; amdgpu.ko rebuilt under -Werror (RC=0, zero errors, irq_service.o irq_service_dce110.o linked, amdgpu_module_build.log); harness baseline SIGSEGV (exit 139) vs fixed graceful dummy handling (exit 0); guest sources reverted md5-verified pristine.
fix.diff; amd_fix_apply.log; amdgpu_module_build.log; run.log (fixed section); VERDICT.md
Confirmed kernel references
- sys/dev/drm/amd/display/dc/irq/dce110/irq_service_dce110.c:226
- sys/dev/drm/amd/display/dc/irq/irq_service.c:82
- 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:1681
- sys/dev/drm/amd/display/dc/irq_types.h:158
Detail
Evidence (decisive lines)
run.log + run.2.log (decisive: 'table[84..89] (.funcs == NULL)', controls OK, 'trigger: dal_irq_service_set(DC_IRQ_SOURCE_DC1_VLINE0=84) -> dal_irq_service_ack -> info->funcs->ack with funcs==NULL' then Segmentation fault exit=139 both runs; fixed exit=0); dce110_vline0gap.c (verbatim table/dispatch replica); amd_fix_apply.log (patched vs pristine md5s) + amdgpu_module_build.log (-Werror build); VERDICT.md (full reachability trace)
PoC changes
Authored fresh (no seed). Iterations: forward declarations for the prototype-scoped struct parameters (gcc 8.3), unbuffered stdout so the pre-crash table walk and controls reach the log before SIGSEGV, and the fix-build redo after patch(1)'s interactive skip of already-applied hunks was caught by md5-at-build-time verification.
Verified recommended fix
Make the dce110 table total over the shared enum ([DC_IRQ_SOURCE_DC1..6_VLINE0] = dummy_irq_entry()) and add !info->funcs guards (log + return false) before the info->funcs->set/->ack dereferences in dal_irq_service_set/dal_irq_service_ack (irq_service.c:118/:152) so every asic table is protected against gaps.
Verdict
Reproduced at logic level with a verbatim userland replica of the dce110 irq table (irq_service_dce110.c:226-313 entry macros and body), the enum (irq_types.h:40-155), and the generic dispatch (irq_service.c:77-158) run on the audit guest. The table covers enum sources 0..83 only; DC_IRQ_SOURCE_DC1..6_VLINE0 (84..89 < DAL_IRQ_SOURCES_NUMBER=90) stay zero-initialized with .funcs==NULL, and find_irq_source_info (:82) bounds-checks only the index, so dal_irq_service_ack (:152) and dal_irq_service_set (:118) dereference info->funcs->ack/.set on NULL β replica set(DC_IRQ_SOURCE_DC1_VLINE0) SIGSEGVs deterministically (exit 139, both runs), the kernel-equivalent being a page fault at ~offsetof(.ack) => hard panic. This is the DF-4713 NULL-funcs-dispatch family rooted in this file's incomplete table. Reachability is latent: every in-tree dc_interrupt_set/ack caller was traced (dm_irq_state PFLIP/VBLANK+otg_inst hardware-bounded 0..5 via tg->inst per dc_resource.c:2055 and amdgpu_dm.c:4856; hpd_init/fini HPD/HPDRX; amdgpu_dm.c:3014 VBLANK+otg_inst) and the IH-arrival path (to_dal_irq_source_dce110) can only return covered sources or INVALID (dummy-handled) β nothing passes 84..89 into a dce110 service today, so no unprivileged trigger exists; the panic is one future caller away (DCN ports, DM changes, or the DAL_VALID_IRQ_SRC_NUM off-by-one at irq_types.h:158 which admits src==DAL_IRQ_SOURCES_NUMBER). dce120/dce80 tables share the shape (noted for maintainers). No write primitive, no leak; ceiling is a kernel panic. Fix validated: fix.diff (six vline0 dummy entries + !info->funcs guards in set/ack, protecting every asic table) applied to guest /usr/src together with DF-4933's; both files verified patched at build time (irq_service.c md5 724635b5, dce110 305f0881 β an initial attempt accidentally skipped the irq_service.c hunks and was redone from pristine); amdgpu.ko rebuilt under -Werror, RC=0, zero errors, irq_service.o irq_service_dce110.o linked; harness fixed mode handles VLINE0 gracefully (exit 0); guest sources reverted md5-verified pristine.
No comments yet.