dce80 vupdate entries carry no .src_id, so dce110_vblank_set (wired by dce80'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 twin on the DCE80 table wiring
Summary
vupdate entries init no .src_id but use vblank_irq_info_funcs whose .set re-maps (0,0)->INVALID then computes (uint8_t)(0-78)=178 - OOB pipe_ctx index feeding a wild tg->funcs->arm_vert_intr call; even a remapped VUPDATE source still underflows (wrong base). Latent (no in-tree VUPDATE .set caller). Replica: SIGBUS through poisoned tg x2; index-derived-offset fix in the shared callee (fixes both dce80 and dce110 tables); amdgpu.ko rebuild clean.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5055 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.2 KB | β raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| dce80_vupd.c | β | 22.9 KB | view raw | |
| build.sh | β | 128 B | view raw | |
| run.sh | β | 117 B | view raw | |
| build.log | β | 365 B | view raw | |
| run.log | β | 2.9 KB | view raw | |
| env.txt | β | 381 B | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| amd_fix_apply.log | β | 850 B | view raw | |
| amdgpu_module_build.log | β | 1.2 MB | β download | |
| manifest.json | β | 2.3 KB | view raw | |
| verdict.json | β | 5.7 KB | view raw |
DF-5055 β dce80 vupdate entries carry no .src_id, so dce110_vblank_set (wired by dce80's own vblank_irq_info_funcs) underflows pipe_offset to 178 β far-OOB pipe_ctx[] read feeding a wild arm_vert_intr call β the DF-4933 twin on the DCE80 table
What
irq_service_dce80.c defines its own
vblank_irq_info_funcs = { .set = dce110_vblank_set, .ack = NULL }
(dce80:82-85) and assigns it to BOTH the vupdate entries
(vupdate_int_entry, dce80:132-146) and the vblank entries
(vblank_int_entry, dce80:148-163). The vblank macro sets
.src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 + reg_num
(19..24) β correct. The vupdate macro initializes no .src_id:
for DC_IRQ_SOURCE_VUPDATE1..6 the info has src_id=0/ext_id=0.
dce110_vblank_set (irq_service_dce110.c:195-219) then re-derives the
source via dc_interrupt_to_irq_source(dc, 0, 0) β
to_dal_irq_source_dce110 default β DC_IRQ_SOURCE_INVALID (0), and
uint8_t pipe_offset = dal_irq_src - IRQ_TYPE_VBLANK; /* 0 - 78 = 178 */
struct timing_generator *tg =
core_dc->current_state->res_ctx.pipe_ctx[pipe_offset].stream_res.tg;
if (enable) {
if (!tg || !tg->funcs->arm_vert_intr(tg, 2)) { ... }
}
becomes pipe_ctx[178] on the MAX_PIPES(6) array β a far-OOB read whose
garbage tg is immediately called through tg->funcs->arm_vert_intr.
DF-4933 filed this for the dce110 table; the DCE80 table's macros wire
the identical trap independently.
Reproduce (on the QEMU guest; no AMD GPU needed β verbatim userland replica)
# build: sh build.sh # cc -O2 -Wall -std=gnu11 [-DFIX_DF5055] # run: sh run.sh # baseline then fixed
The replica carries the dce80 vupdate/vblank entry macros + funcs table,
dce110_vblank_set, to_dal_irq_source_dce110, and the set dispatch
verbatim, with a poisoned (0x5a) 8MiB current_state mapping: pipes
0..5 carry real timing generators, everything past the array is garbage.
Expected output
Baseline (run.log): VBLANK1..6 controls arm pipes 0..5 (the
in-tree-reachable path is fine), then
dal_irq_service_set(DC_IRQ_SOURCE_VUPDATE1) prints
pipe_offset = (uint8_t)(0-78) = 178 and SIGBUS exit 138 inside
tg->funcs->arm_vert_intr (tg = 0x5a5a5a5a5a5a5a5a from the poisoned
pipe_ctx[178]). Deterministic x2 (second run in run.log).
Fixed (-DFIX_DF5055, index-derived pipe_offset): all 12 VUPDATE/VBLANK
sources arm the correct pipes 0..5, exit 0. Also validated by the full
-Werror amdgpu.ko rebuild with the fix applied (RC=0,
irq_service_dce110.o linked, amdgpu_module_build.log).
Severity rationale
Low (family rating, matching DF-4933): latent β no in-tree caller ever
.sets a VUPDATE source (suspend/resume touch HPD/HPDRX only; vblank
enable is IRQ_TYPE_VBLANK+otg_inst; the IH path converts through
to_dal_irq_source_dce110 and amdgpu_dm never registers the V_UPDATE
src_ids 7,9,..,17). Guest has no AMD GPU. The memory-safety shape β OOB
read feeding an indirect call β is one future caller away, with heap
grooming around the dc_state allocation a conceivable route to a
controlled call (not demonstrated; honest ceiling is a kernel panic).
Note the defect is double-baked: even a correctly remapped VUPDATE
source would still underflow, because the subtraction base is
IRQ_TYPE_VBLANK, not IRQ_TYPE_VUPDATE.
DF-5055 β VERDICT
Status: reproduced (panic-equivalent SIGBUS in the verbatim replica), fix validated (module -Werror rebuild + replica fixed mode). Impact: far-OOB read feeding a wild indirect call (Low, latent). Confidence: certain.
The bug (path:line)
irq_service_dce80.c:82-85β the dce80 file's OWNvblank_irq_info_funcs = { .set = dce110_vblank_set, .ack = NULL }.irq_service_dce80.c:145βvupdate_int_entryassigns those funcs to DC_IRQ_SOURCE_VUPDATE1..6 (designators 72..77) while initializing no.src_id/.ext_id(macro body dce80:132-146).irq_service_dce80.c:161-162βvblank_int_entryDOES set.src_id = VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 + reg_num(19..24, consecutive β ivsrcid_vislands30.h:66-156), so only the vupdate side is broken.irq_service_dce110.c:195-219βdce110_vblank_set:dal_irq_src = dc_interrupt_to_irq_source(dc, 0, 0)βto_dal_irq_source_dce110(0,0)β default βDC_IRQ_SOURCE_INVALID;uint8_t pipe_offset = dal_irq_src - IRQ_TYPE_VBLANKβ (uint8_t)(0-78) = 178;pipe_ctx[178]on the 6-entry array β garbagetg;!tg || !tg->funcs->arm_vert_intr(tg, 2)loads and CALLS through the garbage pointer.- Twin of DF-4933 (dce110's own vupdate entries have the same shape); this filing documents the DCE80 table's independent wiring of it.
Reproduction (verbatim userland replica on the audit guest)
dce80_vupd.c: dce80 vupdate/vblank macros + funcs table,
dce110_vblank_set (body verbatim; only the #ifdef'd pipe_offset line
differs in fixed mode), to_dal_irq_source_dce110 (verbatim, real
VISLANDS30 constants), dc_interrupt_to_irq_source (dc.c:1672-1678
collapse), the set dispatch (irq_service.c:77-124), and a poisoned
8MiB current_state (0x5a) with valid tgs in pipes 0..5.
run.log (baseline):
- table: vupdate1 src_id=0 ext_id=0 | vblank1 src_id=19 β the
asymmetry, straight out of the table;
- controls: VBLANK1..6 set arm pipes 0..5 via the real conversion path
(in-tree-reachable path proven fine);
- trigger: set(VUPDATE1) β prints pipe_offset = (uint8_t)(0-78) = 178
β Bus error, exit 138 (pipe_ctx[178].stream_res.tg =
0x5a5a5a5a5a5a5a5a β tg->funcs load faults);
- ---- SECOND BASELINE RUN ---- β second=138 (deterministic x2).
Fixed (-DFIX_DF5055): [fixed] VUPDATE1 -> pipe 0 armed via
index-derived offset; exit 0.
Fix validation (fix_status: fixed)
fix.diff(DF-4933's shape, authored against the real dce110 tree): avblank_pipe_offset(irq_service, info)helper derives the pipe from the TABLE INDEX β(info - irq_service->info), VUPDATE range β source - DC_IRQ_SOURCE_VUPDATE1, else source - DC_IRQ_SOURCE_VBLANK1 β provably 0..5 for both ranges, independent of src_id. The one-line call-site change replaces the underflowing subtraction. Applies clean (patch -p1, Hunk #1 at 192 + Hunk #2 at 226).- Together with DF-5054's diff: full
make -j6amdgpu.ko rebuild under-Werror: RC=0, zero diagnostics, irq_service_dce110.o linked (amdgpu_module_build.log untrimmed, 777 lines). - Behavioral: replica fixed mode arms the correct pipe for all 12 VUPDATE/VBLANK sources, exit 0 (re-run post-build).
- Revert: reverse patch, md5 back to pristine
35607b1aeb1dc71fa3108f6573b6c5fd;
make clean; guest reset to the with-src snapshot afterwards.
Reachability / ceiling (honest)
Latent (identical to DF-4933's verdict): no in-tree caller .set's a
VUPDATE source. Traced: amdgpu_dm_irq.c:353/380/406 (HPD/HPDRX only),
amdgpu_dm.c:3014-3015 (VBLANK+otg_inst), amdgpu_dm_irq.c:570 (PFLIP);
the IH conversion path can return VUPDATE only for V_UPDATE src_ids
7,9,..,17 (odd, non-consecutive β see the to_dal switch) which
amdgpu_dm never registers handlers for. Guest has no AMD GPU β
replica-harness methodology (family precedent). If a future caller
appears: OOB read β wild indirect call; a groomed heap around the
dc_state allocation could in principle aim tg (not demonstrated β
honest ceiling is a kernel panic).
Also checked on this file (pass-2 negatives)
- See DF-5054's VERDICT for the dce80 table sweep (VLINE0 gap = its own finding; hpd_rx/pflip NULL member funcs are safe; irq_service_funcs type has a single member; hpd/pflip/gpio/crtc pin ranges in range; DAL_VALID_IRQ_SRC_NUM off-by-one noted, not re-filed).
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 (vblank_pipe_offset index-derived helper + one-line call-site change in dce110_vblank_set) applied to guest /usr/src (patched md5 f6754cc7d3bac717925e9e1cc398a902, together with DF-5054's diff); full make -j6 amdgpu.ko rebuild under -Werror RC=0, zero diagnostics, irq_service_dce110.o linked (amdgpu_module_build.log); replica fixed mode arms the correct pipe for all 12 VUPDATE/VBLANK sources (exit 0) vs baseline SIGBUS 138; sources reverted md5-verified pristine (35607b1aeb1dc71fa3108f6573b6c5fd), module objects cleaned, guest reset to the with-src snapshot.
fix.diff; amd_fix_apply.log; amdgpu_module_build.log; run.log (baseline crash + fixed exit 0); VERDICT.md
Confirmed kernel references
- sys/dev/drm/amd/display/dc/irq/dce80/irq_service_dce80.c:82
- sys/dev/drm/amd/display/dc/irq/dce80/irq_service_dce80.c:132
- sys/dev/drm/amd/display/dc/irq/dce80/irq_service_dce80.c:145
- sys/dev/drm/amd/display/dc/irq/dce80/irq_service_dce80.c:162
- sys/dev/drm/amd/display/dc/irq/dce110/irq_service_dce110.c:195
- sys/dev/drm/amd/display/dc/irq/dce110/irq_service_dce110.c:205
- sys/dev/drm/amd/display/dc/irq/dce110/irq_service_dce110.c:207
- sys/dev/drm/amd/display/dc/irq_types.h:154
- sys/dev/drm/amd/include/ivsrcid/ivsrcid_vislands30.h:30
- sys/dev/drm/amd/display/dc/core/dc.c:1672
Detail
Evidence (decisive lines)
run.log (decisive: 'table: vupdate1 src_id=0 ext_id=0 | vblank1 src_id=19'; controls 'control VBLANK1..6 set' arming pipes 0..5; trigger 'pipe_offset = (uint8_t)(0-78) = 178' -> Bus error exit 138; '---- SECOND BASELINE RUN ---- second=138'; fixed '[fixed] VUPDATE1 -> pipe 0 armed via index-derived offset; exit 0'); dce80_vupd.c (verbatim replica); amd_fix_apply.log + amdgpu_module_build.log (-Werror amdgpu.ko rebuild RC=0); fix.diff; VERDICT.md
PoC changes
Authored fresh (no seed), mirroring DF-4933's dce110_vupd replica onto the DCE80 wiring: two compile/logic iterations (forward-decl ordering; stub signature mismatch; fixed-mode exit logic initially treated a successful return as BAD and was corrected to expect index-derived success).
Verified recommended fix
Derive pipe_offset from the table index instead of re-mapping info->src_id: source = (enum dc_irq_source)(info - irq_service->info); pipe = (source in VUPDATE1..VUPDATE6) ? source - DC_IRQ_SOURCE_VUPDATE1 : source - DC_IRQ_SOURCE_VBLANK1 (covers dce80 and dce110 tables alike).
Verdict
Reproduced at logic level with a verbatim userland replica of the dce80 vupdate/vblank entry macros + the dce80-local vblank_irq_info_funcs {.set=dce110_vblank_set} (irq_service_dce80.c:82-85,132-163), 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 vupdate entries carry src_id=0/ext_id=0 (macro initializes no .src_id, unlike the vblank entries' 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 (tg=0x5a5a5a5a5a5a5a5a) and the very nexttg->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) twin wired independently by the DCE80 table's own macros. Latent like DF-4933: no in-tree caller ever .set's a VUPDATE source (suspend/resume HPD/HPDRX-only, vblank enable = IRQ_TYPE_VBLANK+otg_inst, pflip = IRQ_TYPE_PFLIP; the IH converter returns VUPDATE only for V_UPDATE src_ids 7,9,..,17 which amdgpu_dm never registers), and the guest has no AMD GPU. The defect is double-baked upstream (even a correctly remapped VUPDATE source underflows, since the base is IRQ_TYPE_VBLANK not IRQ_TYPE_VUPDATE). 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 (vblank_pipe_offset helper deriving the pipe from the TABLE INDEX β provably 0..5 for both ranges, DF-4933's fix shape) -> replica fixed mode arms the correct pipe for all 12 VUPDATE/VBLANK sources, exit 0; full amdgpu.ko rebuild with the fix on guest /usr/src under -Werror RC=0, zero diagnostics, irq_service_dce110.o linked; sources reverted md5-verified pristine.
No comments yet.