radeon_uvd_send_msg NULL-deref panic: radeon_fence_ref(ib.fence) executed when radeon_ib_schedule() failed and left ib.fence NULL
Summary
send_msg guards the fence handout on the caller's out-param, not ib.fence; every ib_schedule error path returns before fence_emit assigns it, so *fence = radeon_fence_ref(NULL) executes - and &NULL->base is a small NON-NULL constant that defeats dma_fence_get's NULL check, so kref_get increments a near-NULL kernel address: panic. Reachable via handle-persist + fd-close -> free_handles -> get_destroy_msg when the UVD ring cannot schedule (reset windows), and at suspend. Panic-only. Guard-on-!r fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4501 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| uvd_sendmsg_nullfence.c | β | 6.3 KB | view raw | |
| build.sh | β | 116 B | view raw | |
| run.sh | β | 212 B | view raw | |
| build.log | β | 285 B | view raw | |
| run.log | β | 275 B | view raw | |
| env.txt | β | 473 B | view raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| README.md | β | 2.9 KB | β raw | |
| fix.diff | β | 582 B | view raw | |
| fault-injection.diff | β | 639 B | view raw |
DF-4501 β radeon_uvd_send_msg() NULL-deref panic when radeon_ib_schedule() fails
Root cause
sys/dev/drm/radeon/radeon_uvd.c:763-766:
r = radeon_ib_schedule(rdev, &ib, NULL, false);
if (fence)
*fence = radeon_fence_ref(ib.fence);
radeon_ib_get() sets ib->fence = NULL (sys/dev/drm/radeon/radeon_ib.c,
"ib->fence = NULL;"). Every error return of radeon_ib_schedule()
(sys/dev/drm/radeon/radeon_ib.c:122-181: !ring->ready,
radeon_ring_lock failure, radeon_sync_rings failure,
radeon_fence_emit failure) happens BEFORE radeon_fence_emit() assigns
ib->fence β on failure ib.fence is still NULL.
radeon_fence_ref(NULL) (sys/dev/drm/radeon/radeon_fence.c:699) executes
dma_fence_get(&fence->base). With fence == NULL the expression
&fence->base evaluates to the small constant
offsetof(struct radeon_fence, base) β a NON-NULL address β so
dma_fence_get()'s if (fence) guard (sys/dev/drm/include/linux/dma-fence.h:90-95)
PASSES and kref_get() performs an atomic increment at a near-NULL
kernel address β kernel panic (write to low canonical kernel memory).
Trigger chain (no-rollback precondition)
radeon_uvd_cs_msg()allocates a UVD handle at CS parse time (radeon_uvd.c:522-526:atomic_cmpxchg(&handles[i], 0, handle)+filp[i] = p->filp). Nothing rolls this back when the ioctl's laterradeon_ib_schedule()fails.- Preconditions where the UVD ring cannot schedule while the UVD
object/handles exist: UVD ring failed to come up (ring->ready false,
e.g. firmware/startup failure after
radeon_uvd_init()allocated the vcpu_bo), GPU reset/teardown windows, or ring lock pressure. - User submits one create-msg CS β error returned, handle stays.
- User closes the DRM fd β postclose β
radeon_uvd_free_handles()(radeon_uvd.c:328-352) sees handle != 0 βradeon_uvd_get_destroy_msg()(radeon_uvd.c:813) βradeon_uvd_send_msg()βradeon_ib_schedule()fails again βradeon_fence_ref(NULL)β panic.
The same crash is reachable via radeon_uvd_suspend() (radeon_uvd.c:249)
and the uvd_v1_0.c call sites, all of which pass &fence unconditionally.
Build / run
cc -O2 -Wall -o uvd_sendmsg_nullfence uvd_sendmsg_nullfence.c ./uvd_sendmsg_nullfence /dev/dri/card0
The userspace half (create handle via raw CS ioctls, ignore the error,
close fd) is in uvd_sendmsg_nullfence.c; the kernel-side precondition
(dead UVD ring) can be forced deterministically with fault-injection.diff
(force radeon_ib_schedule to return -EINVAL in send_msg's path) on any
radeon+UVD box: with injection β immediate panic at fd close; without β
normal operation. This guest has no radeon hardware (env.txt), so the
fault-injection run could not be executed here.
VERDICT β DF-4501
finding_id : DF-4501 status : untested (hardware-gated: needs a radeon ASIC with UVD; guest has no /dev/dri at all β env.txt) reproduced : 0 impact : none demonstrated (expected on hardware: panic β near-NULL kernel write from kref_get under dma_fence_get) confidence : likely
Mechanism, verified line-by-line
- radeon_uvd.c:740-770 radeon_uvd_send_msg(): after
r = radeon_ib_schedule(rdev, &ib, NULL, false);the code doesif (fence) *fence = radeon_fence_ref(ib.fence);β the guard tests the caller's out-param, notib.fence. - radeon_ib.c radeon_ib_get():
ib->fence = NULL;(initial state). - radeon_ib.c radeon_ib_schedule(): all four error returns
(
!ib->length_dw || !ring->ready,radeon_ring_lockfail,radeon_sync_ringsfail,radeon_fence_emitfail) precede the single success-path assignment of ib->fence by radeon_fence_emit. On any of them ib.fence is NULL when send_msg dereferences it. - radeon_fence.c:699 radeon_fence_ref():
dma_fence_get(&fence->base);β with fence==NULL,&fence->baseis the constant offsetof(struct radeon_fence, base) (small, NON-NULL), so dma-fence.h:90-95if (fence) kref_get(&fence->refcount);takes the true branch and atomically increments a near-NULL address β kernel panic (low-address write fault in kernel mode). - Callers that always pass &fence: radeon_uvd.c:263-264 (radeon_uvd_suspend), radeon_uvd.c:338-339 (radeon_uvd_free_handles, invoked from drm postclose), radeon_uvd.c:808/837 via get_create/destroy_msg, plus uvd_v1_0.c call sites.
- Why a handle can exist while the ring is dead: radeon_uvd.c:522-526 allocates handles at CS parse time; radeon's CS ioctl has no UVD-handle rollback on the later ib_schedule failure.
Why untested on this guest
Guest is QEMU std-VGA DragonFly 6.5-DEVELOPMENT with no /dev/dri (env.txt); the radeon ioctl surface and the UVD ring do not exist. Compile check of the userspace trigger only (build.log). Deterministic on-hw validation path is documented in fault-injection.diff (NOT applied to sys/): force radeon_ib_schedule failure inside send_msg, run ./uvd_sendmsg_nullfence, expect immediate near-NULL panic at fd close; baseline without injection runs clean.
Fix (fix.diff, git-apply-verified)
- if (fence) - *fence = radeon_fence_ref(ib.fence); + if (!r && fence) + *fence = radeon_fence_ref(ib.fence);
Only hand out a fence when the IB actually scheduled. (Belt-and-braces: make radeon_fence_ref() NULL-safe like its dma_fence counterpart.)
fix_status: not_testable (no radeon hardware in the audit guest).
kernel_refs
sys/dev/drm/radeon/radeon_uvd.c:740-770 (unguarded ib.fence use) sys/dev/drm/radeon/radeon_uvd.c:263,338,808,837 (callers passing &fence) sys/dev/drm/radeon/radeon_uvd.c:522-526 (handle alloc at parse time) sys/dev/drm/radeon/radeon_ib.c (ib_get: fence=NULL; schedule error paths) sys/dev/drm/radeon/radeon_fence.c:699 (ref without NULL check) sys/dev/drm/include/linux/dma-fence.h:89-95 (guard defeated by &NULL->base)
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 and git apply --check verified; could not be kernel-built/behavior-tested on the audit guest (no radeon hardware). The one-line !r guard is provably sufficient by inspection: radeon_ib_schedule() assigns ib->fence only on the success path that returns 0.
findings/poc/DF-4501/fix.diff (git apply --check OK); radeon_ib.c radeon_ib_get/radeon_ib_schedule trace in VERDICT.md.
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (mechanism trace radeon_uvd.c:740-770 + radeon_ib.c + radeon_fence.c:699 + dma-fence.h:89-95, and the no-rollback handle precondition); uvd_sendmsg_nullfence.c (create-msg CS + fd close); build.log/run.log; fault-injection.diff (deterministic kernel-side emulation, not applied to sys/); fix.diff (one-line guard, git-apply-verified).
PoC changes
N/A (authored fresh; userspace half only β the decisive faulting write happens in-kernel)
Verified recommended fix
In radeon_uvd_send_msg() only hand out the fence when the IB actually scheduled: if (!r && fence) *fence = radeon_fence_ref(ib.fence); (optionally also make radeon_fence_ref NULL-safe).
Verdict
radeon_uvd_send_msg() (radeon_uvd.c:765-766) calls radeon_fence_ref(ib.fence) guarded only by 'if (fence)' β the caller's out-param β while ib.fence is NULL on every radeon_ib_schedule() error path (radeon_ib.c: ib_get initializes fence=NULL; all error returns precede radeon_fence_emit). radeon_fence_ref(NULL) evaluates &((struct radeon_fence*)NULL)->base, a small NON-NULL constant, which defeats dma_fence_get()'s NULL guard (dma-fence.h:90-95) and makes kref_get() atomically increment a near-NULL kernel address -> panic. Reachable because UVD handles are allocated at CS parse time (radeon_uvd.c:522-526) with no rollback on ib_schedule failure, so on a board whose UVD ring cannot schedule, a user submitting one create-msg CS and closing the fd drives radeon_uvd_free_handles() (postclose) into the crash; suspend path radeon_uvd.c:263 equally affected. Mechanism verified line-by-line; NOT executed here β the audit guest has no DRM device (env.txt). Trigger compile-verified on the guest (build.log); deterministic on-hw procedure documented in fault-injection.diff.
No comments yet.