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)
PoC verification
Evidence pack
findings/poc/DF-4870 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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()β asicib_test=r600_dma_ib_testfor the DMA rings of R600/RV770/evergreen/northern-islands asics (radeon_asic.c:925, 1329, 1639, 1908). Called fromradeon_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 (-ENOMEMfromradeon_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.
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)
- r600_dma.c:351
radeon_ib_get(rdev, ring->idx, &ib, NULL, 256)β takesib.sa_bofrom thering_tmp_bosuballocator (radeon_ib.c:57-60radeon_sa_bo_new(...); pool sizedRADEON_IB_POOL_SIZE*64*1024, radeon_ib.c:200-212). - r600_dma.c:363
radeon_ib_schedule(...)β on successib.fencenow owns a fence reference (radeon_ib.c:165). - r600_dma.c:369-377 β
radeon_fence_wait_timeout()returns < 0 (error) or 0 (timeout); both branchesreturnimmediately.ib.sa_boandib.fenceare only released byradeon_ib_free()(radeon_ib.c:73-81:radeon_sync_free+radeon_sa_bo_free+radeon_fence_unref). - r600_dma.c:385-392 β the later failure branch (wb value mismatch,
-EINVAL) does callradeon_ib_free(rdev, &ib)β the two earlier returns simply omit it. - Caller: radeon_ib.c:268
r = radeon_ib_test(rdev, i, ring)fromradeon_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 inrdev->ring_tmp_bountilradeon_ib_pool_fini(). - 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_testablefix.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
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.
No comments yet.