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

r600_dma_ib_test leaks the IB (suballocator chunk + fence ref) on both fence-wait error paths

Summary

radeon_ib_get is taken and the two early fence-fail/timeout returns skip radeon_ib_free (the later -EINVAL path frees correctly) - each failed test permanently removes one 64KB IB-pool sa_bo chunk plus the fence ref until reload. Init/resume-time on wedged r6xx-class hw; no unpriv trigger; hw-gated (Bochs-only guest). Add-the-missing-frees fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4870 Β· 7 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 3.1 KB ↓ raw
fix.diff β€” 659 B view raw
env.txt β€” 1.3 KB view raw
run.log β€” 1.3 KB view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 2.7 KB view raw

DF-4870 β€” r600_dma_ib_test leaks the IB on fence-wait failure/timeout

File: sys/dev/drm/radeon/r600_dma.c (pass 2, GLM 5.3) Severity: Low Β· CWE-401 (Missing Release of Resource on Effective Error Path)

What

r600_dma_ib_test() acquires an IB with radeon_ib_get() (r600_dma.c:351), schedules it, then waits on its fence. The two fence-wait error paths return without radeon_ib_free():

369:    r = radeon_fence_wait_timeout(ib.fence, false, usecs_to_jiffies(
370:        RADEON_USEC_IB_TEST_TIMEOUT));
371:    if (r < 0) {
372:        DRM_ERROR("radeon: fence wait failed (%d).\n", r);
373:        return r;                       /* <-- ib leaked (no radeon_ib_free) */
374:    } else if (r == 0) {
375:        DRM_ERROR("radeon: fence wait timed out.\n");
376:        return -ETIMEDOUT;              /* <-- ib leaked (no radeon_ib_free) */
377:    }

while the later -EINVAL path does free it (r600_dma.c:391) β€” proving the omission on the earlier paths.

radeon_ib_get() (radeon_ib.c:57-93) takes a chunk from the IB suballocator (radeon_sa_bo_new, 64 KB GTT slots out of a RADEON_IB_POOL_SIZE*64 KB pool) and radeon_ib_schedule() sets ib.fence; only radeon_ib_free() (radeon_ib.c:73-81) returns the sa_bo chunk and drops the fence ref.

Trigger / preconditions

  • Caller chain: radeon_ib_ring_tests() (radeon_ib.c:257-287) β†’ radeon_ib_test() β†’ asic ib_test = r600_dma_ib_test for the DMA rings of R600/RV770/evergreen/northern-islands asics (radeon_asic.c:925, 1329, 1639, 1908). Called from radeon_device_init/resume/late_init (radeon_device.c:1486, 1748, 1932).
  • Fires when the DMA engine fence fails or times out during driver init/resume β€” i.e. a hung/absent DMA engine on real r6xx+ hardware.
  • Each occurrence permanently removes one 64 KB IB slot (plus a fence reference) from the pool until radeon_ib_pool_fini(). Repeated suspend/resume cycles with a wedged DMA ring progressively exhaust the IB pool β†’ later GEM submissions fail (-ENOMEM from radeon_ib_get), an availability degradation localized to the broken GPU.

Verdict

Not runtime-verified: this bug requires real r6xx-class Radeon hardware hung at init. The audit QEMU guest has only a Bochs emulated VGA (chip 0x1111:1234, see env.txt), no /dev/dri, and radeon.ko is not loaded. The defect is unambiguous from source (the sibling error path frees; these two do not). Status: untested (hardware-gated), confidence certain by reading.

Fix

See fix.diff β€” add radeon_ib_free(rdev, &ib) before both returns (mirrors the existing -EINVAL path at r600_dma.c:391). Same shape as the upstream Linux error handling for radeon_ib_schedule failure.

VERDICT.md
↓ download raw

DF-4870 VERDICT β€” r600_dma_ib_test IB leak on fence-wait error paths

Bottom line

Not reproduced on the audit guest: the defect requires an r6xx-class Radeon DMA engine whose fence fails/times out during radeon_ib_ring_tests() at driver init or resume. The guest has no Radeon hardware (Bochs VGA 0x1111:1234, no /dev/dri, radeon.ko not loaded β€” env.txt), so the code path cannot be entered at runtime there. The bug itself is certain from source reading; impact ceiling is Low (one 64 KB IB-pool slot + one fence reference leaked per failed test; pool shrinks until driver reload).

Why the code path is real (line-by-line trace)

  1. r600_dma.c:351 radeon_ib_get(rdev, ring->idx, &ib, NULL, 256) β€” takes ib.sa_bo from the ring_tmp_bo suballocator (radeon_ib.c:57-60 radeon_sa_bo_new(...); pool sized RADEON_IB_POOL_SIZE*64*1024, radeon_ib.c:200-212).
  2. r600_dma.c:363 radeon_ib_schedule(...) β€” on success ib.fence now owns a fence reference (radeon_ib.c:165).
  3. r600_dma.c:369-377 β€” radeon_fence_wait_timeout() returns < 0 (error) or 0 (timeout); both branches return immediately. ib.sa_bo and ib.fence are only released by radeon_ib_free() (radeon_ib.c:73-81: radeon_sync_free + radeon_sa_bo_free + radeon_fence_unref).
  4. r600_dma.c:385-392 β€” the later failure branch (wb value mismatch, -EINVAL) does call radeon_ib_free(rdev, &ib) β€” the two earlier returns simply omit it.
  5. Caller: radeon_ib.c:268 r = radeon_ib_test(rdev, i, ring) from radeon_ib_ring_tests(), invoked at radeon_device.c:1486 (init), 1748 and 1932 (resume/late init). A failed test disables the ring (radeon_ib.c:271) but the leaked sa_bo slot stays allocated in rdev->ring_tmp_bo until radeon_ib_pool_fini().
  6. Net effect per occurrence: the IB pool permanently loses one slot. With repeated hibernate/resume cycles on a wedged GPU, GEM command submission eventually fails with "failed to get a new IB (-ENOMEM)" (radeon_ib.c:61) β€” availability impact confined to the already-broken GPU; no kernel memory corruption, no cross-process data exposure.

Why it is not the known DF-1942/DF-1943

DF-1942 = size_in_dw overflow in the copy math; DF-1943 = ring dword under-reservation. This finding is the ib_test error-path resource leak β€” different function (r600_dma_ib_test vs r600_copy_dma), different mechanism, different fix.

Exploit chain

None: no path to uid0, kernel memory corruption, or cross-user disclosure. Maximum impact is IB-pool exhaustion on an already-degraded GPU (A:L).

Fix validation

Not possible on this guest (no hardware). fix.diff authored from the line-accurate trace above: add radeon_ib_free(rdev, &ib) before both returns, matching the existing free at r600_dma.c:391. 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 authored from line-accurate source reading (mirrors the existing free at r600_dma.c:391); kernel fix-build validation impossible on this guest β€” no Radeon hardware to drive the ib test fence path

findings/poc/DF-4870/fix.diff
↓ fix.diffper-fix-DF-4870

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4870/VERDICT.md (line-by-line trace r600_dma.c:351-392, radeon_ib.c:57-81, caller chain radeon_device.c:1486/1748/1932); env.txt (guest GPU inventory proving hardware gate); fix.diff (adds the two missing radeon_ib_free calls, mirroring r600_dma.c:391)

PoC changes

n/a β€” no PoC seed existed; no userland trigger is constructible (init-time kernel path on real hardware)

Verified recommended fix

Add radeon_ib_free(rdev, &ib) before both fence-wait error returns in r600_dma_ib_test()

Verdict

Code-evident resource leak, certain by source reading: r600_dma_ib_test() returns on both fence-wait error paths (r600_dma.c:371-377) without radeon_ib_free(), while the later -EINVAL path frees (r600_dma.c:391) β€” one 64KB IB-pool sa_bo chunk plus the ib.fence reference leak per failed DMA ib test. Runtime verification is hardware-gated: the bug requires an r6xx-class Radeon DMA engine hung at init/resume; the audit QEMU guest has only a Bochs emulated VGA (0x1111:1234), no /dev/dri, and radeon.ko is not loaded (env.txt). Impact ceiling: progressive IB-pool exhaustion on an already-degraded GPU (A:L); no kernel memory corruption, no privilege escalation path.