# 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)

1. **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** on `length_dw`.

2. **Non-VM path skips the size guard.**
   `sys/dev/drm/radeon/radeon_cs.c:634` checks
   `ib_chunk->length_dw > RADEON_IB_VM_MAX_SIZE`, but this check is **inside
   the `if (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 to `sys/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.

3. **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);`
   where `size = ib_chunk->length_dw * 4`.

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);
   ```
   and `sys/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)
   ```

5. **The SA pool is fixed at 1 MiB.**
   `sys/dev/drm/radeon/radeon.h:135`:
   `#define RADEON_IB_POOL_SIZE 16`
   `sys/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 = 1048576` bytes.

   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`):
```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 assertions
- `sys/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 bound
- `sys/dev/drm/radeon/radeon_cs.c:627-642` — VM-only bound check skipped on non-VM path
- `sys/dev/drm/radeon/radeon_ib.c:61` — size = length_dw * 4 passed to radeon_sa_bo_new
- `sys/dev/drm/radeon/radeon.h:135` — RADEON_IB_POOL_SIZE = 16
- `sys/dev/drm/radeon/radeon_ib.c:200-201` — SA pool = 16 * 64K = 1 MiB
