β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5079

dce120 vupdate entries carry no .src_id, so dce110_vblank_set (wired by dce120's own vblank_irq_info_funcs) re-derives DC_IRQ_SOURCE_INVALID and underflows pipe_offset to 178 β†’ far-OOB pipe_ctx[178] read feeding a wild arm_vert_intr indirect call β€” the DF-4933/DF-5055 twin on the DCE120 table wiring

Summary

Same double-baked defect as dce80: vupdate entries init no src_id, and even a correct remap underflows (wrong base 78). Latent (no in-tree VUPDATE .set caller). Replica: SIGBUS x2 through poisoned tg; true-src_id + index-derived-pipe two-hunk fix validated via full amdgpu.ko -Werror rebuild.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5079 Β· 16 files
FileTypeDescriptionSize
dce120_vupd.c β€” 27.6 KB view raw
build.sh β€” 79 B view raw
run.sh β€” 103 B view raw
build.log β€” 370 B view raw
run.log β€” 2.4 KB view raw
run.2.log β€” 2.4 KB view raw
dmesg.txt β€” 1.2 KB view raw
fix.diff β€” 2.6 KB view raw
amdgpu_module_build.log β€” 1.2 MB ↓ download
fix_build.log β€” 1.2 MB ↓ download
fix_run.log β€” 1.6 KB view raw
env.txt β€” 201 B view raw
VERDICT.md β€” 4.9 KB ↓ raw
README.md β€” 2.2 KB ↓ raw
verdict.json β€” 6.4 KB view raw
manifest.json β€” 1.5 KB view raw

DF-5079 β€” dce120 vupdate entries carry no .src_id β†’ dce110_vblank_set pipe-offset underflow (pipe_ctx[178])

File: sys/dev/drm/amd/display/dc/irq/dce120/irq_service_dce120.c:138-144 (wired at :82-85 to dce110_vblank_set) β€” DF-4933 (dce110) / DF-5055 (dce80) twin on the DCE120 table wiring.

Build (on the guest)

mkdir -p /root/df5079 && cp dce120_vupd.c build.sh run.sh /root/df5079/
sh /root/df5079/build.sh

Run (on the guest; run twice for determinism)

sh /root/df5079/run.sh     # -> run.log, run.2.log

Expected

  • baseline (dce120_vupd_base):
  • table check: vupdate1 src_id=0 ext_id=0 (macro initializes none, unlike vblank's 19..24); the "double-baked" note prints β€” even a correct VUPDATE remap underflows: (uint8_t)(VUPDATE1(72) βˆ’ IRQ_TYPE_VBLANK(78)) = 250;
  • controls: VBLANK1..6 arm pipes 0..5 through the REAL conversion path (to_dal maps the consecutive VLINE0 src ids 19..24 β†’ VBLANK1..6);
  • trigger: set(DC_IRQ_SOURCE_VUPDATE1) β†’ to_dal_irq_source_dce110(0,0) β†’ DC_IRQ_SOURCE_INVALID β†’ uint8_t pipe_offset = 0 βˆ’ 78 = 178 β†’ pipe_ctx[178] far-OOB read on the MAX_PIPES=6 array (poisoned 0x5a mapping) β†’ garbage tg β†’ tg->funcs->arm_vert_intr(tg,2) wild indirect call β†’ SIGBUS exit 138. Kernel equivalent: far-OOB read feeding a wild indirect call β‡’ panic; a groomed heap could in principle aim tg (not demonstrated β€” honest ceiling is a panic).
  • fixed (-DFIX_DF5079, = fix.diff: pipe derived from the TABLE INDEX, provably 0..5 for both ranges + correct V_UPDATE src ids in the macro): all 12 VUPDATE/VBLANK sources arm pipes 0..5, exit 0.

Observed (guest, cc 8.3): baseline exit=138 x2, fixed exit=0 x2 (dmesg.txt corroborates: pid ... (dce120_vupd_base) ... exited on signal 10).

Fix validation: same combined amdgpu.ko rebuild as DF-5078 (rc=0, 0/0 under -Werror; irq_service_dce110.c hunk = DF-4933/DF-5055's identical fix shape); sources reverted md5-verified pristine; make clean.

Reachability is latent (traced in VERDICT.md) and the guest has no AMD GPU β€” family-standard logic-level replica methodology.

VERDICT.md
↓ download raw

DF-5079 VERDICT β€” dce120 vupdate entries (no .src_id) β†’ dce110_vblank_set pipe_ctx[178] OOB, replica-reproduced

Status: reproduced (logic level, family-standard replica), impact panic, confidence certain. DF-4933 (dce110) / DF-5055 (dce80) twin wired independently by the DCE120 table's own macros.

Root cause (code-certain)

  • vupdate_int_entry (irq_service_dce120.c:138-144) initializes IRQ_REG_ENTRY(...) + .funcs = &vblank_irq_info_funcs β€” and no .src_id/.ext_id (struct irq_service.h:46-57 has both fields; the entries therefore carry {src_id=0, ext_id=0}).
  • vblank_irq_info_funcs (irq_service_dce120.c:82-85) wires .set = dce110_vblank_set.
  • dce110_vblank_set (irq_service_dce110.c:195-219) re-derives the source from the table entry: dc_interrupt_to_irq_source(dc, info->src_id=0, info->ext_id=0) β†’ to_dal_irq_source_dce110(0,0) (dce110:315-393) hits the default case β†’ DC_IRQ_SOURCE_INVALID (0); then uint8_t pipe_offset = dal_irq_src - IRQ_TYPE_VBLANK (dce110:205) = (uint8_t)(0 - 78) = 178; then core_dc->current_state->res_ctx.pipe_ctx[178].stream_res.tg (dce110:207-208) β€” a far-OOB read on the MAX_PIPES=6 array (core_types.h:239-241, hw_shared.h:37) β€” and the very next statement calls through the garbage pointer: tg->funcs->arm_vert_intr(tg, 2) (dce110:210-211).
  • Double-baked upstream: even a correctly remapped VUPDATE source underflows (VUPDATE1=72 βˆ’ 78 β†’ 250), because the subtraction base is IRQ_TYPE_VBLANK, not IRQ_TYPE_VUPDATE (printed by the replica).
  • For contrast, the dce120 vblank entries DO set .src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 + reg_num = 19..24 (consecutive, verified against ivsrcid_vislands30.h:66-156), so the in-tree-reachable VBLANK path arms pipes 0..5 correctly β€” the defect is confined to the VUPDATE wiring (whose true src ids are the non-consecutive 7,9,11,13,15,17, ivsrcid_vislands30.h:30-61).

Reproduction (this run, on the audit guest)

Verbatim userland replica (dce120_vupd.c, adapted from DF-5055's harness): dce120 vupdate/vblank macros + the dce120-local vblank funcs table, dce110_vblank_set, to_dal_irq_source_dce110, the set dispatch (irq_service.c:77-124), and the dc→current_state→res_ctx.pipe_ctx[] walk against a 0x5a-poisoned 8MiB mapping with valid timing generators in pipes 0..5. Results (run.log, run.2.log — deterministic x2):

  • table: vupdate1 src_id=0 ext_id=0 | vblank1 src_id=19
  • double-baked: (uint8_t)(72-78) = 250
  • controls: VBLANK1..6 arm pipes 0..5 through the REAL conversion path
  • trigger: set(DC_IRQ_SOURCE_VUPDATE1) β†’ pipe_ctx[178] β†’ garbage tg β†’ wild arm_vert_intr β†’ SIGBUS exit 138
  • fixed mode (fix.diff): index-derived pipe β€” all 12 VUPDATE/VBLANK sources arm pipes 0..5, exit 0

Kernel console corroborates (dmesg.txt: pid ... (dce120_vupd_base) ... exited on signal 10 x2 per run).

Reachability (traced honestly β€” latent)

No in-tree caller ever .sets a VUPDATE source: suspend/resume touch HPD/HPDRX only (amdgpu_dm_irq.c:353/380/406, 625-645); vblank enable is IRQ_TYPE_VBLANK + otg_inst (amdgpu_dm.c:3014-3015); pflip is IRQ_TYPE_PFLIP (amdgpu_dm_irq.c:535-556); the IH converter returns VUPDATE only for src ids 7,9..17 which amdgpu_dm never registers. Service exists only on DCE12 hardware (dce120_resource.c:1011); guest has no AMD GPU.

Ceiling

Far-OOB read feeding a wild indirect call β€” a groomed heap around the dc_state allocation could in principle aim tg (not demonstrated; honest ceiling is a kernel panic, family-consistent with DF-5055).

Fix validation

fix.diff (two hunks): (1) dce120 vupdate_int_entry gains .src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + 2 * reg_num (7,9,..,17); (2) dce110_vblank_set derives the pipe from the TABLE INDEX via the vblank_pipe_offset() helper β€” provably 0..5 for both ranges, the exact DF-4933/DF-5055 fix shape (the dce110 hunk is byte-identical to DF-5055's, the shared-function owner). Applied to guest /usr/src together with DF-5078's diff: hunks succeeded at :192/:226 (dce110) and :140 (dce120); full make -j6 amdgpu.ko rebuild rc=0, 0/0 under -Werror (amdgpu_module_build.log; irq_service_dce110.c compiled at :572); replica fixed mode exit 0 with all 12 sources arming the correct pipes; sources reverted md5-verified pristine (35607b1a.../51bd6ea8...), make clean. Guest returned to clean snapshot afterwards.

Artifacts

dce120_vupd.c (code_hash 44d0edf9...), build.log, run.log, run.2.log, dmesg.txt, env.txt, fix.diff, amdgpu_module_build.log, fix_build.log, fix_run.log, manifest.json, verdict.json.

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

fixed
baseline reproduced→ patch + rebuild →patched reproduced

fix.diff applied to guest /usr/src together with DF-5078's (hunks at dce110:192/:226, dce120:140); full make -j6 amdgpu.ko rebuild rc=0 with 0 errors/0 warnings under -Werror (amdgpu_module_build.log; irq_service_dce110.c compiled at :571-572); replica fixed mode (identical shape) arms the correct pipe for all 12 VUPDATE/VBLANK sources, exit 0 where baseline exits 138; sources reverted md5-verified pristine (35607b1a.../51bd6ea8...), make clean.

fix_build.log (=amdgpu_module_build.log, rc=0, zero diagnostics); fix_run.log (fixed mode: all 12 sources arm pipes 0..5, exit 0); run.log (baseline exit=138)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 (amdgpu.ko module rebuilt with fix; not booted β€” no AMD GPU on guest)

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log + run.2.log (deterministic x2: vupdate1 src_id=0/ext_id=0 vs vblank1 19; double-baked 250; VBLANK1..6 controls arm pipes 0..5; set(VUPDATE1) -> pipe_ctx[178] -> SIGBUS exit=138; fixed exit=0); dmesg.txt (kernel-observed signal-10 exits); dce120_vupd.c header (path:line map); amdgpu_module_build.log (rc=0, 0/0 under -Werror, irq_service_dce110.o at :571-572)

PoC changes

Self-authored by adapting DF-5055's dce80 harness to the dce120 macros: swapped in the dce120 vupdate/vblank entry forms (SRI/IRQ_REG_ENTRY with per-instance stub mask defines), the dce120 funcs/table names, and added the double-baked underflow print ((uint8_t)(VUPDATE1-IRQ_TYPE_VBLANK)=250) plus a fixed-mode loop proving all six VUPDATE sources arm pipes 0..5.

Verified recommended fix

Derive the pipe from the table INDEX in dce110_vblank_set (vblank_pipe_offset helper, provably 0..5 for both ranges β€” DF-4933/DF-5055's shape) and give the dce120 vupdate entries their true src ids (.src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + 2*reg_num) β€” fix.diff carries both hunks.

Verdict

Reproduced at logic level with a verbatim userland replica of the dce120 vupdate/vblank entry macros + the dce120-local vblank_irq_info_funcs {.set=dce110_vblank_set} (irq_service_dce120.c:82-85,138-153), dce110_vblank_set itself (irq_service_dce110.c:195-219), to_dal_irq_source_dce110, and the set dispatch (irq_service.c:77-124), run on the audit guest against a 0x5a-poisoned 8MiB current_state with valid timing generators in pipes 0..5. The dce120 vupdate entries carry src_id=0/ext_id=0 (vupdate_int_entry initializes no .src_id, unlike the vblank entries' consecutive 19..24), so dce110_vblank_set's re-derivation to_dal_irq_source_dce110(0,0) returns DC_IRQ_SOURCE_INVALID and 'uint8_t pipe_offset = dal_irq_src - IRQ_TYPE_VBLANK' underflows to 178; core_dc->current_state->res_ctx.pipe_ctx[178] reads the poisoned mapping and the very next 'tg->funcs->arm_vert_intr(tg,2)' faults β€” SIGBUS exit 138, deterministic x2. VBLANK1..6 controls arm pipes 0..5 through the REAL conversion path, proving the in-tree-reachable vblank path healthy β€” this is the DF-4933 (dce110) / DF-5055 (dce80) twin wired independently by the DCE120 table's own macros. The replica also prints the double-baked note: even a correctly remapped VUPDATE source underflows ((uint8_t)(72-78)=250), since the subtraction base is IRQ_TYPE_VBLANK not IRQ_TYPE_VUPDATE (true V_UPDATE src ids are the non-consecutive 7,9,11,13,15,17 per ivsrcid_vislands30.h:30-61). Latent like the family: no in-tree caller ever .set's a VUPDATE source (suspend/resume HPD/HPDRX-only per amdgpu_dm_irq.c:353/380/406,625-645; vblank enable = IRQ_TYPE_VBLANK+otg_inst per amdgpu_dm.c:3014; pflip = IRQ_TYPE_PFLIP; the IH converter returns VUPDATE only for src_ids 7,9..17 which amdgpu_dm never registers), the service exists only on DCE12 hardware (dce120_resource.c:1011), and the guest has no AMD GPU. Ceiling if a future caller appears: far-OOB read feeding a wild indirect call β€” a groomed heap around the dc_state allocation could in principle aim tg (not demonstrated; honest ceiling is a kernel panic). FIX VALIDATED: fix.diff (dce120 macro gains .src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + 2*reg_num = 7,9,..,17; plus the vblank_pipe_offset() table-index helper in dce110_vblank_set β€” byte-identical to DF-4933/DF-5055's fix shape, the shared-function owner) applied to guest /usr/src together with DF-5078's (hunks at dce110:192/:226 and dce120:140); full amdgpu.ko rebuild under -Werror rc=0, zero errors, zero warnings, irq_service_dce110.o recompiled and linked (amdgpu_module_build.log:572); replica fixed mode arms the correct pipe 0..5 for all 12 VUPDATE/VBLANK sources, exit 0 vs baseline 138; sources reverted md5-verified pristine, make clean, guest returned to clean snapshot.