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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3666 Β· 9 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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) sets rdev->rlc.reg_list = that array and reg_list_size = 81 for every RADEON_IS_IGP evergreen-family part, then calls sumo_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 into dst_ptr[j], j = ((i-1)*3)/2 β†’ writes at indices 0, 3, 6, ..., 120; epilogue (evergreen.c:4205-4206) writes the end marker at j = (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 (see run.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:2204 passes the ARUBA tn_rlc_save_restore_register_list through 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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-3666

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'.