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

radeon_cs_parser_relocs: unchecked radeon_vm_get_bos() NULL return silently drops the VM page directory and page tables from the CS reservation/validation set β€” submission then updates the PD/PTs without holding their reservations (stale-address GPU writes, unsynchronized eviction); latent on DF as compiled, live upstream and after any DF-4580-style allocator fix

Summary

The vm_bos assignment ignores the NULL return: the VM PD/PT BOs are never ttm_eu-reserved, validated, or fenced - yet the CS proceeds to submit a DMA IB writing PDEs at the now-unheld BOs' offsets; a concurrent eviction can relocate/free them, yielding GPU writes to stale addresses. Distinct from DF-4412 (max_pde_used TOCTOU inside radeon_vm.c) - this is the missing NULL check at the call site. Latent on DF today (GFP_KERNEL never returns NULL here - it panics per DF-4580) and live upstream. Check-and-unwind fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4581 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.2 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
DF-4581-trace.c β€” 1.9 KB view raw
fix.diff β€” 473 B view raw
manifest.json β€” 802 B view raw
verdict.json β€” 2.9 KB view raw

DF-4581 β€” radeon_cs_parser_relocs: unchecked radeon_vm_get_bos() NULL return omits VM PD/PTs from the CS reservation set

Where

sys/dev/drm/radeon/radeon_cs.c:190-192:

if (p->cs_flags & RADEON_CS_USE_VM)
        p->vm_bos = radeon_vm_get_bos(p->rdev, p->ib.vm, &p->validated);

No NULL check. radeon_vm_get_bos() (sys/dev/drm/radeon/radeon_vm.c:128-138) returns NULL when its kvmalloc_array() fails β€” after having added nothing to &p->validated.

Consequence on failure

  • VM page directory + page tables are missing from p->validated: never ttm_eu_reserve_buffers-reserved, never validated/pinned, and never fenced by ttm_eu_fence_buffer_objects (radeon_cs.c:427).
  • Submission proceeds anyway: radeon_cs_ib_vm_chunk β†’ radeon_bo_vm_update_pte (radeon_cs.c:565) β†’ radeon_vm_update_page_directory builds a DMA IB writing PDEs at pd_addr = radeon_bo_gpu_offset(pd) β€” a BO whose placement is not held for this submission. Concurrent eviction can move the PD/PT BOs (their reservations are free) β†’ the GPU writes PDEs at a stale address (freed/reused VRAM/GTT memory of another client), and the PTs can be evicted while the IB still executes. Race-grade GPU memory corruption, trigger-gated behind an allocation failure.

Reachability on this tree (important, honest caveat)

On DragonFly as compiled, kvmalloc_array(..., GFP_KERNEL) cannot return NULL: GFP_KERNEL = M_WAITOK without M_NULLOK (sys/dev/drm/include/linux/gfp.h:40), so failure panics or hangs instead (see DF-4580). The NULL path is therefore latent on DF today and a live upstream bug on Linux; it must be fixed together with any DF-4580 allocator-semantics fix, or fixing one bug silently activates the other.

Run

No runtime PoC is possible on the audit guest (no AMD GPU, no /dev/dri β€” radeon_cs_ioctl unreachable) and the NULL path cannot even be forced without allocator changes. DF-4581-trace.c documents the trace and the fix; verification is by code inspection only.

Expected result of the fix

radeon_cs_parser_relocs returns -ENOMEM when radeon_vm_get_bos fails, the ioctl unwinds via radeon_cs_parser_fini β€” no submission with an unreserved page directory.

VERDICT.md
↓ download raw

DF-4581 VERDICT

Status: not_reproduced β€” code-proven defect, runtime-gated twice over (no AMD GPU on the guest; NULL path latent on DF as compiled).

Trace (by inspection, line-accurate)

  1. radeon_cs_ioctl (sys/dev/drm/radeon/radeon_cs.c:655) β†’ radeon_cs_parser_relocs (radeon_cs.c:90-201).
  2. radeon_cs.c:190-192: p->vm_bos = radeon_vm_get_bos(...) β€” return ignored.
  3. radeon_vm_get_bos (sys/dev/drm/radeon/radeon_vm.c:135-138): list = kvmalloc_array(...); if (!list) return NULL; β€” nothing has been added to head at that point (the first list_add is at radeon_vm.c:147).
  4. Failure result: p->validated contains only the user reloc BOs. The VM page directory (vm->page_directory) and page-table BOs are never reserved by radeon_bo_list_validate (radeon_cs.c:196 β†’ ttm_eu_reserve_buffers) and never fenced by ttm_eu_fence_buffer_objects (radeon_cs.c:427).
  5. Submission continues: radeon_cs_ib_vm_chunk (radeon_cs.c:537) under vm->mutex β†’ radeon_bo_vm_update_pte (radeon_cs.c:492) β†’ radeon_vm_update_page_directory (radeon_vm.c) submits a DMA IB that writes PDEs at radeon_bo_gpu_offset(pd) β€” with the PD/PT BOs unreserved and unpinned for this CS, so a concurrent eviction (their reservations are not held) can relocate/free them β†’ stale-address GPU writes + unsynchronized eviction mid-IB = GPU memory corruption.

Distinct from DF-4412: DF-4412 is a TOCTOU inside radeon_vm_get_bos (max_pde_used re-read racing GEM_VA, OOB write in radeon_vm.c's fill loop). DF-4581 is the unchecked NULL return at the radeon_cs.c:191 call site β€” different root cause, different file, different fix.

Why not reproduced on the guest

  • Guest has no AMD GPU (pciconfig -l has no 1002: device; /dev/dri absent) β†’ radeon_cs_ioctl unreachable at runtime.
  • Even with the hardware, on DragonFly as compiled kvmalloc_array with GFP_KERNEL never returns NULL (M_WAITOK without M_NULLOK, sys/dev/drm/include/linux/gfp.h:40; see DF-4580's reproduced panic mechanism) β€” the NULL branch is latent until the allocator semantics are fixed (which is exactly when this missing check would become a live corruption race).

Impact ceiling

On a kernel where kvmalloc can fail (upstream Linux; DF after an M_NULLOK-style fix): race-grade GPU memory corruption behind an allocation failure β€” Medium-class, hardware- and race-gated. On DF today: latent.

Fix

One line + error unwind (fix.diff): check p->vm_bos == NULL β†’ DRM_ERROR + return -ENOMEM; the ioctl's error path (radeon_cs_parser_fini(parser, r, false)) already unwinds correctly with an empty/partial validated list (relocs GEM refs are put at radeon_cs.c:435-443; kvfree(NULL) is a no-op).

Artifacts

  • DF-4581-trace.c β€” annotated trace + fix
  • fix.diff β€” git-apply-check-clean unified diff
  • README.md, manifest.json, verdict.json

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 authored and git-apply-check clean; not runnable on this guest (no radeon hardware, NULL path latent on DF as compiled).

findings/poc/DF-4581/fix.diff; git apply --check passes.
↓ fix.diffper-fix-DF-4581

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (line-accurate trace radeon_cs.c:190->radeon_vm.c:135-138->unreserved PD/PT update path radeon_cs.c:565); fix.diff (NULL check + -ENOMEM) git-apply-check clean.

PoC changes

No seed existed; authored DF-4581-trace.c as an annotated code-trace scaffold (no runtime possible: hardware absent + NULL path latent on DF).

Verified recommended fix

Check radeon_vm_get_bos() return at radeon_cs.c:191: if NULL, DRM_ERROR and return -ENOMEM so the ioctl unwinds instead of submitting with an unreserved page directory.

Verdict

Defect certain by inspection: radeon_cs.c:190-192 ignores the return of radeon_vm_get_bos(); on NULL (kvmalloc_array failure, radeon_vm.c:135-138) the VM page directory and page tables are silently omitted from p->validated, so they are neither reserved, validated/pinned, nor fenced for the submission, while radeon_cs_ib_vm_chunk proceeds to update the page directory via a DMA IB using radeon_bo_gpu_offset() of the now-unheld BOs -> stale-address GPU writes and unsynchronized eviction (race-grade GPU memory corruption). Not reproduced at runtime: the audit guest has no AMD GPU (no /dev/dri, no 1002: PCI device) so radeon_cs_ioctl is unreachable, and on DragonFly as compiled the NULL branch is itself unreachable because GFP_KERNEL kvmalloc never returns NULL (M_WAITOK without M_NULLOK; failure panics/hangs - see DF-4580 whose sink panic was reproduced on this guest). The bug is live upstream-shaped code and activates on DF as soon as allocator NULL semantics are introduced; fix must land together with DF-4580.