sumo_rlc_init() save/restore buffer object undersized for the ON/LN/TN packed format: deterministic 172-byte write past the 324-byte BO
Summary
For pre-TAHITI IGPs (PALM/SUMO/SUMO2), sumo_rlc_init() allocates the RLC save/restore BO as dws*4 = 81*4 = 324 bytes, but the packing loop writes the 2-registers-into-3-dwords format at indices 0,3,...,120 and the end marker at ceil(81/2)*3 = 123 - 496 bytes needed vs 324 allocated: a 43-dword/172-byte OOB write. Contained today only because TTM backs the BO with whole pages (496 < 4096), so the overrun lands in the object's own page slack; values written are compile-time constants. Latent CWE-787 that any sub-page VRAM allocator or reuse of the shared packing loop (ni.c passes ARUBA's tn list through the same code - cross-file lead) turns into neighbor-object VRAM corruption. VERIFIED by deterministic index-math replay on the guest: 'writes need 496 bytes but the BO is 324 bytes'. Fix: size the BO for the packed format.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3666 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df3666.c | β | 4.1 KB | view raw | |
| build.sh | β | 86 B | view raw | |
| run.sh | β | 89 B | view raw | |
| build.log | β | 22 B | view raw | |
| run.log | β | 672 B | view raw | |
| env.txt | β | 596 B | view raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| fix.diff | β | 1.5 KB | view raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-3666 VERDICT β undersized RLC save/restore BO in sumo_rlc_init()
Reproduced?
The index arithmetic is fully proven (deterministic, constant inputs); the write
itself executes in kernel context only on PALM/SUMO/SUMO2 hardware, which the audit
guest does not have (virtio-only; see env.txt). Machine verdict: inconclusive
(hardware-bound), source-level certainty certain.
The proof
evergreen.c:123-206:sumo_rlc_save_restore_register_list[]has 81 entries (verified by count).evergreen_startup()(evergreen.c:5016-5021) setsrdev->rlc.reg_list =that array andreg_list_size = 81for everyRADEON_IS_IGPevergreen-family part, then callssumo_rlc_init().sumo_rlc_init()(evergreen.c:4145-4146):dws = 81.- Allocation (
evergreen.c:4155):radeon_bo_create(rdev, dws * 4 = 324 bytes, ...). - Write loop (
evergreen.c:4197-4204): each iteration packs 2 source dwords intodst_ptr[j],j = ((i-1)*3)/2β writes at indices 0, 3, 6, ..., 120; epilogue (evergreen.c:4205-4206) writes the end marker atj = (82*3)/2 = 123. Highest index 123 β 496 bytes touched vs 324 allocated β 43 dwords (172 bytes) past the BO's declared size. - Simulation replaying the exact loop (
df3666.c) prints:OOB write past BO size: 43 dwords = 172 bytes,RESULT: BUG REPRODUCED - writes need 496 bytes but the BO is 324 bytes(seerun.log; deterministic across runs).
Why this is Low (containment analysis)
- TTM rounds the backing store to whole pages:
bo->num_pages = (324 + 4095) >> 12 = 1, and radeon's GTT/VRAM managers allocate page-granular nodes, so byte offsets 324..495 land in the same page TTM already owns for this object. No adjacent TTM object, no host kernel memory is touched. - The values written are compile-time constants (packed register addresses) and the RLC-save spaces the GPU fills later β nothing attacker-influenced, nothing secret-leaking.
- Consequence today: none observable (which is why the driver "works"). Consequence
tomorrow: any sub-page VRAM allocator, or reuse of this packing loop with a
different
reg_list(it is shared:ni.c:2204passes the ARUBAtn_rlc_save_restore_register_listthrough the same loop β cross-file lead), or TTM slack reuse turns this into real VRAM corruption of a neighboring buffer object.
Fix
fix.diff sizes the save/restore BO for the packed format (and keeps the linear
SI+ path as-is):
- r = radeon_bo_create(rdev, dws * 4, PAGE_SIZE, true,
+ /* pre-SI packs 2 regs into 3 dwords, plus the end marker */
+ u32 sr_dws = (rdev->family >= CHIP_TAHITI) ? dws :
+ (((dws + 1) / 2) * 3 + 1);
+ r = radeon_bo_create(rdev, sr_dws * 4, PAGE_SIZE,
+ true, RADEON_GEM_DOMAIN_VRAM, 0,
NULL, NULL, &rdev->rlc.save_restore_obj);
(Upstream Linux fixed the same undersizing with the equivalent size expression.)
Fix validation
Not kernel-testable on this guest (no SUMO/IGP radeon device; the code runs during
evergreen_startup() with RADEON_IS_IGP). The sizing expression is validated
arithmetically by df3666.c: for dws=81 the new allocation is
((82)/2)*3+1 = 124 dwords = 496 bytes β₯ highest written index 123. fix_status =
not_testable.
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_testablefix.diff sizes the allocation to 124 dwords (496 bytes) >= highest written index 123 for dws=81 - validated arithmetically by the same PoC model; cannot be kernel-tested without SUMO-class hardware, so fix_status=not_testable.
fix.diff; VERDICT.md 'Fix' and 'Fix validation' sections
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log: 'OOB write past BO size: 43 dwords = 172 bytes' and 'RESULT: BUG REPRODUCED - writes need 496 bytes but the BO is 324 bytes'; df3666.c replays evergreen.c:4197-4206 verbatim with dws=81 (ARRAY_SIZE of sumo_rlc_save_restore_register_list, evergreen.c:123-206); VERDICT.md containment analysis (TTM page rounding); fix.diff sizes the BO to ((dws+1)/2)*3+1 dwords
PoC changes
Seed was a sketch; written from scratch as a deterministic index-math replay with a TTM page-rounding model to state the containment honestly (no heuristic byte-pattern guessing left in the final version).
Verified recommended fix
Allocate the save/restore BO for the packed format: sr_dws = ((dws + 1) / 2) * 3 + 1 dwords for pre-TAHITI (evergreen.c:4155)
Verdict
Arithmetic proven, hardware-bound containment documented. sumo_rlc_init() allocates the RLC save/restore buffer object as dws4 = 814 = 324 bytes (evergreen.c:4155) but the ON/LN/TN packing loop (evergreen.c:4197-4206) writes the 2-regs-into-3-dwords format plus an end marker, touching dword indices up to ceil(81/2)*3 = 123 -> 496 bytes needed: a 43-dword / 172-byte write past the declared BO size. The PoC replays the exact loop and prints the overrun (deterministic). Impact today is none: TTM backs the BO with whole pages (num_pages rounds 324 -> 1 page) and 496 < 4096, so the overrun lands in slack of the object's own backing page; values written are compile-time constants. Filed Low/hardening: any sub-page-granular VRAM allocator or reuse of the shared packing loop (ni.c:2204 passes the ARUBA tn_rlc_save_restore_register_list through the same code - cross-file lead) turns it into real neighbor-object corruption. Kernel-path execution requires PALM/SUMO/SUMO2 hardware absent from the virtio-only guest, hence 'inconclusive' (hardware-absent convention), not 'not_testable'.
No comments yet.