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)
PoC verification
Evidence pack
findings/poc/DF-4967 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
radeon_check_arguments(sys/dev/drm/radeon/radeon_device.c:1117-1137) acceptsdrm.radeon.gart_size=8192(power of two, β₯ 32, no upper bound).- The GTT heap then spans 8 GiB (
radeon_ttm.c:941-943), so TTM can hand outbo_mem->start = 2^20(byte offset2^32, exact inunsigned long). radeon_gart_bind(struct radeon_device *rdev, unsigned offset, ...)truncates that offset to 0 (radeon_gart.c:286,299) β the same GART entry indext == 0is produced for the BO at offset 0 and the BO at offset 4 GiB.- 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 β 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
sys/dev/drm/radeon/radeon_drv.c:175,229,231βradeon_gart_size(MB) is an unboundedint:TUNABLE_INT("drm.radeon.gart_size", ...)/module_param_named(gartsize, ...).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.sys/dev/drm/radeon/radeon_ttm.c:940-943β the GTT TTM heap is sized with the fullgtt_size:ttm_bo_init_mm(&rdev->mman.bdev, TTM_PL_TT, rdev->mc.gtt_size >> PAGE_SHIFT).radeon_ttm_placement_from_domainuses unboundedlpfn = 0, so BOs can be placed at any page in the 8 GiB heap.sys/dev/drm/radeon/radeon_ttm.c:656,659βgtt->offset = (unsigned long)(bo_mem->start << PAGE_SHIFT);(bo_mem->startisunsigned long, 64-bit βttm_bo_api.h:97) thenradeon_gart_bind(gtt->rdev, gtt->offset, ...)β the callee parameter isunsigned offset(radeon_gart.c:286), so offsets β₯ 2^32 truncate.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]andradeon_gart_set_page(rdev, t, ...)(the actual PTE write into the GART table atradeon_gart.c:309) all go to the aliased low index. Identical truncation inradeon_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 samet. The later bind overwrites the earlier BO's GART PTE andpages[]/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,tstay in bounds ofpages[]/pages_entry[](allocated for the fullnum_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_testableFix 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
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).
No comments yet.