DF-4145 / verdict.json
{ "finding_id": "DF-4145", "status": "reproduced", "verdict": "REPRODUCED with the findings MECHANISM CORRECTED by the rig. Confirmed exactly as claimed: a pplib state with ucNumDPMLevels==0 parses with num_levels==0 (trinity_parse_pplib_clock_info:1726 never runs for it); when that state is selected, the verbatim trinity_dpm_pre_set_power_state -> trinity_apply_state_adjust_rules executes :1603-:1618 (proved by Dpm0PgNbPsLo 1->2 / DpmXNbPsLo 2->2 overwritten at :1615/:1617) and then the :1619 store ps->levels[ps->num_levels - 1].allow_gnb_slow = 0 executes OUT OF BOUNDS. Corrected mechanism: num_levels is u32, so the index is 0xFFFFFFFF and the store addresses ps + 4 + 0xFFFFFFFF*12 + 7 = ps + 0xBFFFFFFFF -- a WILD 1-byte-zero store ~48 GiB past the object, NOT the claimed ps-1 window write into the MSB of pi->requested_rps.ps_priv (C unsigned arithmetic; gdb computes the exact fault address 0x140047b1af == ps + 0xBFFFFFFFF, and a MAP_FIXED canary page at that exact offset receives precisely one changed byte, 0xaa->0x00, of 12288). The read side (trinity_dpm_get_sclk:2102 levels[num_levels-1].sclk) addresses ps + 0xBFFFFFFF8 -- same wild region (rig get_sclk returns the canary bytes), not an intra-object garbage read. In-kernel consequence: page fault -> panic at the first state transition (certain floor), or a silent 1-byte zeroing of unrelated kernel memory if the +48GiB offset happens to be mapped; NO pointer-surgery capability (constant offset, constant 0x00 value) -- the findings non-canonical-ps_priv chain is refuted. fix.diff (reject zero-level states at parse + num_levels guard on the store) A/B-validated: fixed build rejects the crafted table at init (-22, power state 1 has no dpm levels), the write is unreachable, good tables unaffected.", "reproduced": true, "impact": "panic", "confidence": "certain", "exploit_chain": "Crafted VBIOS: IntegratedSystemInfo v1_7 with ucNBDPMEnable=1 (verbatim-parsed by trinity_parse_sys_info_table, enable_nbps_policy defaults true) + a pplib state carrying an SDSTATE/HDSTATE/UI_BATTERY classification and ucNumDPMLevels=0 -> parse leaves that states num_levels==0 -> state selection (radeon_pm machinery) -> trinity_dpm_pre_set_power_state -> trinity_apply_state_adjust_rules:1619 -> wild byte-zero store at pi->requested_ps + 0xBFFFFFFFF and OOB read at +0xBFFFFFFF8 (get_sclk feeding watermark programming) -> kernel page fault -> panic, or 1-byte zero of unrelated kernel VA where mapped. No unprivileged-userland trigger; attacker data is the GPU ROM.", "evidence": "run.log (4145-guard: FAULT addr=0x14004bdfff == ps+0xBFFFFFFFF with the rig printing the computed wild offset; 4145-fullflow: canary byte 0xaa->0x00 written by the verbatim store, 1/12288 bytes changed, Dpm0/DpmX reachability proof, get_sclk returning canary bytes; FIXED: parse rejected rc=-22), run.2.log, build.log (68x identical + DF-4145 fix lines == fix.diff additions (11 lines)), rig/, VERDICT.md (full corrected-arithmetic derivation), env.txt", "kernel_refs": [ "sys/dev/drm/radeon/trinity_dpm.c:1619", "sys/dev/drm/radeon/trinity_dpm.c:1726", "sys/dev/drm/radeon/trinity_dpm.c:1603", "sys/dev/drm/radeon/trinity_dpm.c:2102", "sys/dev/drm/radeon/trinity_dpm.c:974", "sys/dev/drm/radeon/trinity_dpm.c:988" ], "poc_changes": "Seed pack had no runnable PoC. Built the verbatim rig from scratch. Two rig-driven corrections vs the seed claim set: (1) the :1619 store target is ps+0xBFFFFFFFF (u32 index wrap x sizeof(trinity_pl)=12), not ps-1 -- proven by fault-address arithmetic, gdb inspection and a MAP_FIXED canary page catching exactly one byte; (2) the levels[num_levels-1].sclk reads are the SAME wild offset, not intra-object garbage. The trigger/reachability/OOB-write class of the finding stand unchanged.", "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": 150, "guest_dirty": 0, "build_cmd": "cd rig && sh ./build.sh /usr/src DF-4145", "run_cmd": "cd rig && sh ./run.sh", "code_hash": "trinity_rig.c a68eb1438bc82fb79063491af6293aeddf208ec2cf4f46f0c6040a1aed69e1a3; trinity_slice_verbatim.c 6afb17099769a0c4c9cd500356a757a12cf22d8e9bd67a710b452236019ba7c6", "notes": "Mechanism correction is the material delta of this verification: severity class (attacker-reachable kernel OOB write -> panic floor) is unchanged, but the claimed precise pointer-corruption primitive (ps-1 -> ps_priv MSB -> non-canonical deref) does not exist; exploitability ceiling is panic/DoS plus a speculative 1-byte zero at a fixed wild offset. Adjacent same-pattern sites confirmed in the slice: trinity_set_uvd_clock_before/after_set_eng_clock (:974/:988) read levels[num_levels-1].sclk at the same wild offset. Guest left up and clean.", "recommended_fix": "Reject pplib states whose idx loop yields k==0 (full unwind, -EINVAL) in trinity_parse_power_table and guard the :1619 store with ps->num_levels > 0 -- fix.diff, A/B validated.", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "A/B validated in the verbatim rig on the identical crafted image: baseline executes the wild store (guard-mode fault at ps+0xBFFFFFFFF; fullflow canary byte written) and the wild read; fixed build rejects the zero-level state at parse (init rc=-22, power state 1 has no dpm levels) so :1619 is unreachable, and the defense-in-depth num_levels guard covers any other zero-level path. Good-table regression unchanged. Kernel-rebuild validation not performed (AMD-only path, guest has no AMD GPU); validated in the rig per the established protocol.", "fix_evidence": "run.log FIXED sections (RESULT: OK (fixed) parse rejected the crafted table (init rc=-22) before the vulnerable site); build.log (DF-4145 fix lines == fix.diff additions (11 lines)); fix.diff" } |