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

radeon_gart_bind/unbind take a 32-bit `unsigned offset`: GTT aperture offsets >= 4 GiB truncate and alias GART entries across buffer objects (cross-process GPU data redirection)

Summary

The entry index derives from a 32-bit offset while the GTT heap is sized from the unbounded gart_size tunable - BOs at X and X+4GiB produce identical t, so the later bind overwrites the earlier BO's pages/PTE: the GPU translates the low BO's addresses to the high BO's physical pages (cross-process GPU data theft/corruption between unpriv users after a root misconfiguration). Arithmetic 11/11 on guest harness; hw-gated. Widen-to-uint64_t fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4967 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 4.6 KB ↓ raw
gart_math.c β€” 5.3 KB view raw
build.sh β€” 92 B view raw
run.sh β€” 92 B view raw
build.log β€” 52 B view raw
run.log β€” 1.1 KB view raw
env.txt β€” 249 B view raw
fix.diff β€” 1.8 KB view raw
verdict.json β€” 3.0 KB view raw
manifest.json β€” 969 B view raw

DF-4967 β€” radeon_gart_bind/unbind take a 32-bit offset: GTT aperture offsets β‰₯ 4 GiB truncate and alias GART entries across buffer objects

File: sys/dev/drm/radeon/radeon_gart.c:241,286 Class: wrong-index aliasing β†’ cross-process GPU memory redirection (not kernel OOB) Severity: Medium (requires root-configured oversized GTT + radeon hardware) Status: kernel-behavior UNTESTED on this guest (no ATI/AMD GPU present); code-path arithmetic validated by gart_math.c run on the guest (11/11 PASS).

Build

cc -O2 -Wall -o gart_math gart_math.c

Run

./gart_math

Expected output

Every check line prints PASS, ending with ALL CHECKS PASSED (0 failures), demonstrating:

  1. radeon_check_arguments (sys/dev/drm/radeon/radeon_device.c:1117-1137) accepts drm.radeon.gart_size=8192 (power of two, β‰₯ 32, no upper bound).
  2. The GTT heap then spans 8 GiB (radeon_ttm.c:941-943), so TTM can hand out bo_mem->start = 2^20 (byte offset 2^32, exact in unsigned long).
  3. radeon_gart_bind(struct radeon_device *rdev, unsigned offset, ...) truncates that offset to 0 (radeon_gart.c:286,299) β€” the same GART entry index t == 0 is produced for the BO at offset 0 and the BO at offset 4 GiB.
  4. Consequence: the high BO's bind overwrites pages[p]/pages_entry[t]/GART PTE of the low BO β†’ the GPU translates the low BO's aperture addresses to the high BO's physical page (cross-process GPU read/write redirection), while the high BO's true entries (β‰₯ 2^20) are never written.

Why not a kernel PoC here

The audit guest (QEMU std-VGA) has no PCI device with vendor 0x1002 (pciconf -l proof in env.txt), so the radeon driver never attaches. The finding's precondition is additionally a root-set boot tunable (drm.radeon.gart_size in /boot/loader.conf), i.e. a misconfiguration-assisted defect β€” same precondition class as DF-2012 (amdgpu twin).

VERDICT.md
↓ download raw

VERDICT β€” DF-4967

Status: untested (target hardware absent on this guest; no radeon PCI device, driver not compiled into the guest kernel). The arithmetic of the defect is certain from source inspection and validated by the guest-built gart_math.c (11/11 PASS, run.log). Impact therefore recorded as none on this guest; the code-level impact on a real radeon box with an oversized GTT is cross-process GPU memory redirection (Medium).

The defect, line by line

  1. sys/dev/drm/radeon/radeon_drv.c:175,229,231 β€” radeon_gart_size (MB) is an unbounded int: TUNABLE_INT("drm.radeon.gart_size", ...) / module_param_named(gartsize, ...).
  2. sys/dev/drm/radeon/radeon_device.c:1117-1134 β€” radeon_check_arguments() only validates power of two and β‰₯ 32; there is no upper bound. Line 1137: rdev->mc.gtt_size = (uint64_t)radeon_gart_size << 20; β€” e.g. 8192 β†’ 8 GiB.
  3. sys/dev/drm/radeon/radeon_ttm.c:940-943 β€” the GTT TTM heap is sized with the full gtt_size: ttm_bo_init_mm(&rdev->mman.bdev, TTM_PL_TT, rdev->mc.gtt_size >> PAGE_SHIFT). radeon_ttm_placement_from_domain uses unbounded lpfn = 0, so BOs can be placed at any page in the 8 GiB heap.
  4. sys/dev/drm/radeon/radeon_ttm.c:656,659 β€” gtt->offset = (unsigned long)(bo_mem->start << PAGE_SHIFT); (bo_mem->start is unsigned long, 64-bit β€” ttm_bo_api.h:97) then radeon_gart_bind(gtt->rdev, gtt->offset, ...) β€” the callee parameter is unsigned offset (radeon_gart.c:286), so offsets β‰₯ 2^32 truncate.
  5. sys/dev/drm/radeon/radeon_gart.c:299 β€” t = offset / RADEON_GPU_PAGE_SIZE; computes the GART entry index from the truncated offset; pages[p], pages_entry[t] and radeon_gart_set_page(rdev, t, ...) (the actual PTE write into the GART table at radeon_gart.c:309) all go to the aliased low index. Identical truncation in radeon_gart_unbind (radeon_gart.c:241,252), so bind/unbind stay consistent with each other (both wrong).

Why it matters (and why only Medium)

  • With gart_size β‰₯ 8192 (8 GiB GTT), two GTT buffer objects at aperture offsets X and X + 4 GiB compute the same t. The later bind overwrites the earlier BO's GART PTE and pages[]/pages_entry[] bookkeeping:
  • user A's GPU access to its BO at offset X is translated to user B's physical page (cross-process GPU data disclosure + corruption of B's buffer through A's writes);
  • B's BO at X + 4 GiB has no valid PTEs at its true entries (they hold the dummy/zero entry) β†’ GPU reads of dummy page / write loss.
  • It is driver index aliasing, not a kernel-memory OOB: p,t stay in bounds of pages[]/pages_entry[] (allocated for the full num_gpu_pages), and the set_page write goes into the properly sized GART table.
  • Precondition is a root-set boot tunable (misconfiguration-assisted), same class as DF-2012. After boot, any unprivileged DRM user inherits the aliasing β€” the security boundary violated is user↔user (GPU data), not userβ†’kernel.

What I ruled out / verified

  • rs400_gart_init (rs400.c:86-93) does bound gtt_size (32..2048 MB switch) β†’ RS400 cannot reach this; r100 (r100.c:633-652), rv370 (r300.c:121-140), rs600 (rs600.c:535-548), r600 (r600.c:1105-1117) have no bound β†’ reachable there.
  • radeon_gtt_location (radeon_device.c:597-620) only clamps gtt_size to the MC address space (≫ 8 GiB) β€” no effective mitigation.
  • The AGP path (radeon_agp.c:245) takes the aperture size from the northbridge (hardware-bounded), unaffected.
  • DF-2014 (unbind t/p desync) is a different defect (index desync between t and p), not re-reported here; this finding is about the 32-bit offset parameter shared by bind and unbind.

Fix validation

Not performable on this guest (no hardware, and the trigger is a boot-time tunable on real hardware). fix.diff widens the API to uint64_t offset (both functions + radeon.h declarations), which removes the truncation for every GTT size the u32 num_gpu_pages model can describe; combined with DF-4968's table-size consistency check the oversized-GTT misconfiguration space is closed.

Reproduce the arithmetic

cc -O2 -Wall -o gart_math gart_math.c && ./gart_math   # ALL CHECKS PASSED

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 not testable: baseline cannot be reproduced on this guest (no radeon GPU). fix.diff widens the API to uint64_t in both functions and the header declaration.

fix.diff; absent hardware documented in env.txt
↓ fix.diffper-fix-DF-4967

Confirmed kernel references

Detail

Exploit chain

root sets drm.radeon.gart_size=8192 (or larger power of two) in /boot/loader.conf -> GTT heap spans 8GiB -> unprivileged user A allocates GTT BO placed below 4GiB, user B above 4GiB -> B's radeon_gart_bind overwrites A's GART PTE and pages[] slot -> GPU translates A's aperture addresses to B's physical page (cross-process GPU data disclosure/corruption). Not reachable on this guest (no radeon hardware).

Evidence (decisive lines)

run.log (guest-built gart_math.c, ALL CHECKS PASSED); VERDICT.md (line-by-line trace); env.txt (no 0x1002 PCI device, no drm in kern.conftxt)

PoC changes

no kernel PoC possible (hardware absent); wrote a guest-buildable arithmetic validator reproducing the exact type chain (u64 heap -> unsigned long offset -> unsigned param) with kernel constants

Verified recommended fix

Widen radeon_gart_bind/unbind offset parameter to uint64_t (and matching radeon.h declarations) so aperture offsets >= 4GiB index the correct GART entries.

Verdict

Kernel-behavior untestable on this guest (no vendor 0x1002 PCI device, radeon not in the guest kernel config - proof in env.txt). Code-path analysis is certain: radeon_gart_bind/unbind take a 32-bit 'unsigned offset' (radeon_gart.c:241,286) while the GTT heap can span > 4 GiB because radeon_check_arguments (radeon_device.c:1117-1137) accepts the unbounded power-of-two tunable drm.radeon.gart_size and ttm_bo_init_mm sizes TTM_PL_TT from the full gtt_size (radeon_ttm.c:941). bo_mem->start << PAGE_SHIFT then truncates, so BOs at aperture offsets X and X+4GiB alias the same GART entry - cross-process GPU read/write redirection after a root misconfiguration. The truncation arithmetic and the validator acceptance were validated on the guest by gart_math.c (11/11 PASS).