Unguarded irq_service->funcs->to_dal_irq_source double-deref in dal_irq_service_to_irq_source plus swallowed dal_irq_service_construct failure β latent NULL-deref hardening gap (asymmetric with every sibling entry point)
Summary
The only unvalidated dereference chain in the dispatcher (every sibling validates), compounded by a void-returning construct whose failure the family constructors swallow (dce110/80/120/dcn10 wire funcs unconditionally). Reachability traced dead in-tree (ctx kzalloc-checked, all 8 create-sites NULL-check irqs) - pure defense-in-depth for future refactors. KLD harness compiles the VERBATIM dispatcher against a synthetic table with canaries: T1 proves the zeroed-object fault on :165 while also proving the surrounding negatives (bounds accept exactly 0..89; canaries intact; DF-4731 macro defused at these paths; DFly's >= is STRICTER than upstream v4.17's > which would accept one-past-end). guard+INVALID-return fix authored.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5778 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df5778_harness.c | β | 18.8 KB | view raw | |
| Makefile | β | 213 B | β download | |
| build.sh | β | 147 B | view raw | |
| run.sh | β | 175 B | view raw | |
| build.log | β | 1003 B | view raw | |
| run.log | β | 3.8 KB | view raw | |
| env.txt | β | 736 B | view raw | |
| VERDICT.md | β | 8.3 KB | β raw | |
| fix.diff | β | 2.6 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 5.1 KB | view raw |
DF-5778 VERDICT β full narrative
File: sys/dev/drm/amd/display/dc/irq/irq_service.c (169 LOC, pass 2 GLM 5.3)
Finding: Unguarded service-object dereference in dal_irq_service_to_irq_source
plus swallowed dal_irq_service_construct failure β latent NULL-deref hardening gap.
Class: CWE-476 (NULL pointer dereference, latent/defense-in-depth).
Severity: Info. Confidence: certain (guards certainly absent; reachability
certainly nil via in-tree callers β both proven below).
1. The defect (what is missing, exactly)
irq_service.c:160-169βdal_irq_service_to_irq_source()executes
c
return irq_service->funcs->to_dal_irq_source(irq_service, src_id, ext_id);
with no check of irq_service, irq_service->funcs, or the
to_dal_irq_source member. Contrast the sibling entry points in the same
file, which do validate before dereferencing:
- dal_irq_service_set() (:101-114) and dal_irq_service_ack()
(:138-150) validate via find_irq_source_info() (:77-86).
- dal_irq_service_construct() (:53-63) validates init_data/ctx.
- dal_irq_service_destroy() (:65-75) validates the handle.
Even the DC wrappers are asymmetric: dc_interrupt_set()
(core/dc.c:1681-1688) checks dc == NULL, while
dc_interrupt_to_irq_source() (core/dc.c:1673-1679) and
dc_interrupt_ack() (core/dc.c:1690-1693) check nothing.
irq_service.c:53-63βdal_irq_service_construct()returnsvoid. On badinit_data/ctxit bails (BREAK_TO_DEBUGGER(); return;) leaving the object zeroed. Every family constructor swallows that β e.g. dce110'sconstruct()(irq_service_dce110.c:398-405) ignores the outcome, unconditionally wiresinfo/funcs, anddal_irq_service_dce110_create()(:407-418) hands the object back as live. Actx == NULLservice would later reachdm_read_reg(irq_service->ctx, addr)(dm_services.h:66-77), whosectx->cgs_devicederef faults. Same shape in dce80/dce120/dcn10.
2. Reachability trace β why this is latent, not live
init_data.ctx = dc->ctxat all 8 create sites (dce100_resource.c:960, dce110_resource.c:1306, dce112_resource.c:1252, dce120_resource.c:1010, dce80_resource.c:967/1177/1383, dcn10_resource.c:1362).dc->ctxiskzalloc'd and checked before resource-pool creation (core/dc.c:602-607 succeeds orgoto fail; pool created at :671-678 only afterwards) βctxcan never be NULL when the family constructors run β the construct-failure branch is dead in-tree.pool->base.irqs = dal_irq_service_*_create()is NULL-checked at all 8 sites (if (!pool->base.irqs) goto res_create_fail;/irqs_create_fail/fail) βres_pool->irqsis non-NULL in any livedc, andirq_service->funcsis unconditionally wired by the family constructors βdal_irq_service_to_irq_source's chain is non-NULL on every reachable call.- Callers of the wrappers:
amdgpu_dm_irq_handler(amdgpu_dm_irq.c:472-487) with a validadev->dm.dc;register_hpd_handlersloops (amdgpu_dm.c:1336/1358/1421/1444);dm_irq_state/amdgpu_dm_set_*(amdgpu_dm_irq.c:357-670) β all with constructeddcs. - No userland input reaches these entry points with pointer-shaped data;
the only externally-shaped inputs are
src_id/ext_id(hardware IV ring) which flow throughfuncs->to_dal_irq_source, not into the pointer chain.
Conclusion: no in-tree caller can produce the faulting object today. The gap matters for robustness (future callers, partial-init refactors, driver-teardown windows) β hence Info, defense-in-depth.
3. Verification (what ran, on what)
Guest: DragonFly 6.5-DEVELOPMENT (X86_64_GENERIC, with-src snapshot), cc 8.3, no AMD GPU (vgapci 0x1234:0x1111 QEMU VGA; /dev/dri absent). Per the reservation note for this file, the evidence class is a production-code KLD harness with synthetic tables.
df5778_harness.c compiles the verbatim production code β the
complete enum dc_irq_source + DAL_VALID_IRQ_SRC_NUM
(irq_types.h:36-158), the four structs (irq_service.h:36-70), the
interface prototypes (irq_service_interface.h:28-47), and the six
dispatcher function bodies (irq_service.c:77-169, byte-identical; the
dm_read_reg/dm_write_reg/DC_LOG_ERROR macro interposition mirrors
how dm_services.h itself provides them as macros) β against a synthetic
[DAL_IRQ_SOURCES_NUMBER] table with physically-adjacent guard canaries
and a register recorder standing in for cgs MMIO.
Decisive run (run.log, module loaded/unloaded cleanly, guest healthy):
- T1 (the finding): a zero-initialised
irq_service(the object the swallowed-construct path would leave) hassvc->funcs(offset +16) == NULL; the replication of the exact irq_service.c:165 deref chain shows the verbatim line would fault, with no guard on the path. Fault-free probe β the guest is not panicked by design. - B1 (bounds, pass-2 angle (a)):
find_irq_source_infoaccepts exactly 0..89 (0 = INVALID dummy slot, by design) and rejects one-past-end 90, 91, 95, 255, 65535, 0x7FFFFFFF, 0xFFFFFFFF ((enum)-1); every accepted pointer lands in-table. Canaries intact (B2). The table walk is in-bounds β executable negative result. - B3:
DAL_VALID_IRQ_SRC_NUM(90) == true(the DF-4731 macro off-by-one, filed on amdgpu_dm_irq.c β not re-reported here) whilefind_irq_source_info(90) == NULL: this file's>=defuses the macro on the set/ack paths. - B4 (divergence, pass-2 angle (c)): upstream Linux v4.17
find_irq_source_infousedsource > DAL_IRQ_SOURCES_NUMBERβ which accepts 90 and returns&info[90], a one-past-end read of the[90]family tables. The DFly tree uses>=(irq_service.c:82): stricter and correct. Verified by fetching torvalds/linux v4.17 (drivers/gpu/drm/amd/display/dc/irq/irq_service.c) during this audit and by emulating the upstream predicate in the harness. Apart from this predicate (and thestaticqualifier onfind_irq_source_info), the file is byte-identical to upstream v4.17 β no port-introduced defects. - T2: verbatim
dal_irq_service_to_irq_sourcehappy path executes correctly through the synthetic funcs. - T3: dummy-slot dispatch (the family
dummy_irq_entry()wiring, funcs non-NULL with log-and-fail members) produces zero register traffic and cleanfalsereturns β behaviorally distinct from the zero-filled VLINE0 slots of the DF-2168/4932/4995/5054/5078 family (known; not re-reported). - T4: generic RMW dispatch reads/writes exactly the in-table synthetic registers with correct masking, ack-before-set ordering as in production.
Status: untested in the poc_results sense used for structural findings (house precedent DF-4731): the in-kernel manifestation cannot be exercised on this guest (no AMD GPU, and no in-tree caller can produce the NULL object). The structure of the missing guard and the correctness of everything around it were executed and verified.
4. Fix
fix.diff (authored post-verification, not applied to the read-only
sys/ tree):
- dal_irq_service_to_irq_source: reject !irq_service, missing
funcs, or missing member with DC_IRQ_SOURCE_INVALID (callers
already handle INVALID β dummy slot).
- dc_interrupt_to_irq_source/dc_interrupt_ack (core/dc.c): the same
dc/res_pool->irqs guard dc_interrupt_set already has.
A deeper (optional) hardening would make dal_irq_service_construct
return bool and have the four family create() functions return NULL
on failure; not included to keep the diff minimal and reviewable.
5. Fix validation
fix_status: not_testable β the guards are pure hardening on paths that
cannot be driven to NULL on this guest (no AMD GPU; in-tree callers
proven non-NULL). The harness's T1 probe is the baseline demonstration of
the unguarded chain; with the guard applied the same object would return
DC_IRQ_SOURCE_INVALID by inspection (single early-return). No kernel
rebuild is warranted for an Info hardening finding; the guard logic was
desk-checked line-by-line against the cited call sites.
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
not_testablePure-hardening early-return guards on paths provably non-NULL in-tree; no AMD GPU on the guest to build an amdgpu kernel and no way to drive the objects to NULL, so a fix-build validation is not possible (house precedent DF-4731). Guard logic desk-checked against every cited call site; callers already tolerate DC_IRQ_SOURCE_INVALID (dummy slot).
fix.diff (guards at irq_service.c:165 and dc.c:1673/1690); VERDICT.md section 5
Confirmed kernel references
- sys/dev/drm/amd/display/dc/irq/irq_service.c:53
- sys/dev/drm/amd/display/dc/irq/irq_service.c:160
- sys/dev/drm/amd/display/dc/irq/irq_service.c:165
- sys/dev/drm/amd/display/dc/core/dc.c:1673
- sys/dev/drm/amd/display/dc/core/dc.c:1690
- sys/dev/drm/amd/display/dc/irq/dce110/irq_service_dce110.c:398
- sys/dev/drm/amd/display/dc/dm_services.h:68
Detail
Evidence (decisive lines)
run.log: 'T1 zeroed irq_service=... svc->funcs (off+16) = 0' + 'T1 VERDICT: ... NO guard'; run.log 'B1 RESULT: PASS' (bounds) and 'B2 ... PASS (intact)'; run.log B3/B4 lines; VERDICT.md section 2 reachability trace with path:line for all 8 create-sites; env.txt (no AMD GPU); fix.diff
PoC changes
no seed PoC; harness authored fresh. Build iterations on guest: (1)
Verified recommended fix
Guard dal_irq_service_to_irq_source (reject !irq_service/!funcs/!member with DC_IRQ_SOURCE_INVALID) and give dc_interrupt_to_irq_source/dc_interrupt_ack the same dc/res_pool->irqs NULL check dc_interrupt_set already has (fix.diff)
Verdict
Structural/latent finding verified statically and by production-code KLD harness on the DragonFly guest (no AMD GPU; synthetic-table family standard). The harness compiles the verbatim dispatcher (irq_service.c:77-169, irq_service.h structs, irq_types.h enum+macro) and proves: T1 the exact irq_service.c:165 deref chain on a zero-initialised irq_service hits svc->funcs (off+16)==NULL with NO guard on the path (fault-free probe); B1 find_irq_source_info accepts exactly 0..89 and rejects one-past-end 90 and all larger/(enum)-1 values with canaries intact (table walk in-bounds β executable negative result for the pass-2 bounds question); B3 the DF-4731 DAL_VALID_IRQ_SRC_NUM off-by-one is defused at this file's set/ack paths by the >= bound; B4 upstream Linux v4.17 used > (accepts 90 β &info[90] one-past-end) while the DFly tree's >= (irq_service.c:82) is stricter/correct β no port-introduced defect, byte-identical otherwise; T2-T4 verbatim happy path, dummy-slot log-and-fail with zero register traffic, and generic RMW confined to in-table regs. The in-kernel NULL-deref manifestation cannot be exercised: no AMD hardware on the guest and no in-tree caller can produce the NULL object (all 8 resource create-sites NULL-check pool->base.irqs; dc->ctx is kzalloc-checked at core/dc.c:602-607 before pool creation at :671). Severity Info, defense-in-depth.
No comments yet.