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

radeon GART table_size / num_gpu_pages 32-bit wrap (DF-2012's radeon twin): missing table-size invariant check in radeon_gart_table_{vram,ram}_alloc β€” power-of-two validator currently forces the wrapped size to 0 (fail-closed)

Summary

The u32 num_pages/table_size math wraps like DF-2012 but radeon's power-of-two validator forces the wrap to exactly 0 (fail-closed: init failure, not undersized table). Latent OOB sinks at the restore/set_page loops noted. Arithmetic-proven; invariant-check fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4968 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 3.0 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.4 KB view raw
verdict.json β€” 2.9 KB view raw
manifest.json β€” 1.0 KB view raw

DF-4968 β€” radeon GART table_size/num_gpu_pages 32-bit wrap (DF-2012's radeon twin): power-of-two validation forces the wrapped size to 0, so init fails closed β€” missing consistency check in radeon_gart_table_{vram,ram}_alloc

File: sys/dev/drm/radeon/radeon_gart.c:127-140,68-87,345-350 Class: integer wrap β†’ undersized GART table (hardening; fail-closed in the current code) Severity: Info (defense-in-depth gap; no demonstrated runtime impact) Status: untested on this guest (no radeon hardware); arithmetic validated by gart_math.c.

Build / Run / Expected

cc -O2 -Wall -o gart_math gart_math.c && ./gart_math

ALL CHECKS PASSED (0 failures) β€” shows gart_size=2097152 MB passing radeon_check_arguments, num_gpu_pages = 2^29, and table_size = num_gpu_pages * 8 wrapping to 0 (and the Γ—4 asic variant wrapping at 4 TiB, plus num_gpu_pages itself truncating to 0 at β‰₯ 16 TiB).

Why Info and not Medium (unlike DF-2012)

DF-2012 (amdgpu) had a usable small wrapped table (2048 bytes) because amdgpu.gart_size is not constrained to powers of two. The radeon validator (sys/dev/drm/radeon/radeon_device.c:1126-1134) requires a power of two, so the wrapped table_size is always exactly 0 for every accepted value β‰₯ 2 TiB (Γ—8 asics) / β‰₯ 4 TiB (Γ—4 asics). A 0-size table yields a failed/zero-length BO (radeon_gart_table_vram_alloc β†’ radeon_bo_create(0)) or a NULL pci_alloc_consistent(0) β€” gart init fails and the device fails to initialize (fail-closed). The residual hazard: nothing rejects the configuration before allocation, and radeon_gart_table_vram_pin's restore loop (radeon_gart.c:178-179) would write num_gpu_pages PTEs through whatever (possibly 0-length) mapping radeon_bo_kmap returned, if that ever succeeded. The consistency check that upstream added for amdgpu-style bugs is simply absent here.

VERDICT.md
↓ download raw

VERDICT β€” DF-4968

Status: untested (no radeon hardware on this guest). Arithmetic certain and validated by gart_math.c (11/11 PASS, run.log). Filed as Info/hardening: the current code fails closed, but only by accident of the power-of-two validator, and the missing table-size consistency check is the exact gap DF-2012 exposed in amdgpu.

Chain

  1. sys/dev/drm/radeon/radeon_device.c:1117-1134 β€” power-of-two + β‰₯32 MB validation, no upper bound; line 1137 gtt_size = (uint64_t)radeon_gart_size << 20.
  2. sys/dev/drm/radeon/radeon_gart.c:345-346 β€” num_cpu_pages/num_gpu_pages are unsigned (u32) fields assigned from the u64 with no overflow check (radeon.h:659-660); they truncate for gtt β‰₯ 16 TiB.
  3. Per-asic table sizing (32-bit unsigned multiply, no overflow check): - Γ—8: r600.c:1115, rs600.c:546 β†’ wraps to 0 for gtt β‰₯ 2 TiB - Γ—4: r100.c:647, r300.c:136 (rv370), rs400.c:101 β†’ wraps to 0 for gtt β‰₯ 4 TiB (rs400 additionally is bounded 32..2048 MB by its own switch at rs400.c:86-93; r100/rv370/rs600/r600 are not).
  4. sys/dev/drm/radeon/radeon_gart.c:68-87 (radeon_gart_table_ram_alloc) and :127-140 (radeon_gart_table_vram_alloc) use table_size directly with no check that table_size >= num_gpu_pages * sizeof(entry).
  5. Write sinks if a wrapped/short table ever mapped successfully: radeon_gart_bind β†’ radeon_gart_set_page (radeon_gart.c:305-313) and the radeon_gart_table_vram_pin restore loop (radeon_gart.c:178-179) iterate to num_gpu_pages-1.

Why fail-closed today

The power-of-two constraint makes every accepted radeon_gart_size β‰₯ 2 TiB produce table_size == 0 exactly (2^k Γ— 2^11 ≑ 0 mod 2^32 for k β‰₯ 21). A 0-size table cannot be usefully mapped: radeon_bo_create(..., 0, ...) / pci_alloc_consistent(..., 0, ...) give a zero-page BO or NULL, radeon_bo_kmap of 0 pages returns an error or NULL mapping (ttm_bo_util.c:599-627, ttm_bo_ioremap returns -ENOMEM when ioremap yields NULL), and gart enable fails β†’ device init fails. No kernel OOB write is constructible from the tunable alone in this tree; hence Info.

Fix

fix.diff adds the missing invariant check at both allocation sites in sys/dev/drm/radeon/radeon_gart.c (64-bit multiply): reject when the computed table_size cannot hold num_gpu_pages entries. This converts any future relaxation of the validator (e.g. someone "fixing" the power-of-two requirement the way amdgpu's validator works) from a silent OOB into an -EINVAL at init.

Fix validation

Not testable on this guest (no radeon hardware; the trigger is additionally a root boot tunable). Baseline and patched kernels cannot be exercised.

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 adds the missing table_size/num_gpu_pages consistency check at both allocation sites.

fix.diff; hardware absence documented in env.txt
↓ fix.diffper-fix-DF-4968

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (gart_math.c on guest: table_size wrap to 0 at gart=2TiB/4TiB, num_gpu_pages u32 truncation at 16TiB); VERDICT.md (chain + fail-closed reasoning); env.txt (hardware absence)

PoC changes

arithmetic validator instead of kernel PoC (hardware absent)

Verified recommended fix

Add a radeon_gart_table_size_ok() invariant check (64-bit multiply/divide) at the top of radeon_gart_table_vram_alloc and radeon_gart_table_ram_alloc, rejecting undersized/wrapped tables.

Verdict

Kernel behavior untestable on this guest (no radeon PCI device, radeon not in the guest kernel config). Arithmetic certain, validated on the guest by gart_math.c (11/11 PASS): with drm.radeon.gart_size a power-of-two >= 2 TiB, num_gpu_pages8 (or 4 at >= 4 TiB) wraps table_size to exactly 0 because the validator only accepts powers of two - unlike amdgpu (DF-2012), where non-power-of-two values produced a small-but-usable 2048-byte table. A 0-size table cannot be usefully mapped (radeon_bo_create(0)/pci_alloc_consistent(0) -> zero-page BO/NULL; kmap of 0 pages errors or returns NULL), so gart enable fails closed. Filed Info/hardening: radeon_gart_table_vram_alloc (radeon_gart.c:127) and radeon_gart_table_ram_alloc (:68) never validate table_size against num_gpu_pages, and radeon_gart_init (:345-346) stores gtt_size/PAGE_SIZE into u32 fields without an overflow check - the same missing invariants DF-2012 fixed in amdgpu. Any future relaxation of the power-of-two validator turns this into the DF-2012 OOB write (sinks: radeon_gart.c:305-313 set_page and the :178-179 vram_pin restore loop).