# DF-4531 VERDICT

**Classification:** REAL BUG — `inconclusive` at runtime only because the QEMU
audit guest has no DRM hardware. Source-level certainty is **certain**
(every lock edge and every line of the control flow verified in-tree, plus a
deterministic algorithmic proof of the overflow run on the guest 3/3).

## Root cause chain (path:line)

1. `sys/dev/drm/drm_sysctl.c:268` — `DRM_LOCK(dev)` =
   `lockmgr(&dev->struct_mutex, LK_EXCLUSIVE)` (`drmP.h:114`). This is the ONLY
   synchronization the handler has.
2. `drm_sysctl.c:271-272` — pass 1 counts `privcount` entries of `dev->filelist`.
3. `drm_sysctl.c:274-275` — `kmalloc(..., M_WAITOK|M_NULLOK)`: **sleeps**, unbounded
   duration under memory pressure; even without sleeping, holding a lockmgr
   exclusive lock does not stop other CPUs.
4. `drm_sysctl.c:281-282` — pass 2 copies `*priv` into the count-sized buffer with
   **no bound check** — `list_for_each_entry` simply runs to the end of the list.
5. `sys/dev/drm/drm_file.c:514-516` — `drm_open_helper()` adds entries under
   `mutex_lock(&dev->filelist_mutex)` (`mutex_lock` = `lockmgr(LK_EXCLUSIVE)`,
   `include/linux/mutex.h:38`) — a *different* lock object than `struct_mutex`.
   Nothing serializes it against steps 2-4.
6. `sys/dev/drm/drm_file.c:380-382` + `:430` — `drm_close()` removes under
   `filelist_mutex` and then `kfree(file_priv)` under *no* list lock at all
   (only `drm_global_mutex`): a shrinking/deleting race turns the walker into a
   use-after-free read.

Growth ⇒ OOB write of `growth × sizeof(struct drm_file)` (≈ 0x3c0+ bytes per
entry on x86_64) past a kmalloc'd region whose size the attacker chose via the
entry count at pass-1 time. The attacker fully controls:

- the base count `N` (open N fds before reading the sysctl),
- the growth (open fds from another thread during the read),
- the repeat rate (loop the sysctl read),
- part of the written content (`pid`, `magic`, capability bitfields), the rest
  being kernel heap pointers (idr roots, list heads) — useful fake-object
  material on a guest with no SMAP/SMEP/KASLR.

Shrink ⇒ UAF read (not directly user-visible; secondary).

## Why High severity

Kernel heap buffer overflow, write primitive, reachable by a local
unprivileged user in the `video` group (default desktop membership), with no
DRM authentication and no master rights — just `open(2)` and `sysctl(3)`.
On the standard audit-guest configuration (no SMAP/SMEP/KASLR) a
slab-groom → victim-object overwrite chain toward `uid=0` is the expected
escalation route; that end-to-end chain could not be developed here only
because the guest lacks a DRM device (QEMU std-VGA, no i915/amdgpu, no
virtio-gpu driver in the tree — verified: `pciconf -l` shows only
`vgapci0` with no drm child; `/dev/dri` absent; `sysctl hw.dri` ENOENT).

## What was run (honest ledger)

| Step | Result |
|---|---|
| `logicproof.c` on guest (x3) | **OOB WRITES DETECTED 3/3** (11.9MB / 26.8MB / 29.0MB past-buffer writes in the modelled window) |
| `trigger.c` build on guest | OK (`-Wall -Wextra` clean except 2 unused-param warnings) |
| `trigger.c` run on guest | hardware-gated: `/dev/dri/card0: No such file or directory`; `sysctl hw.dri.0.clients` → `unknown oid` |
| `fix.diff` applies to pristine `sys/dev/drm/drm_sysctl.c` | verified with `patch --dry-run` on a scratch copy |
| patched `drm_sysctl.c` compiled with exact kernel CFLAGS (`X86_64_GENERIC` obj dir) | **RC=0**, `drm_sysctl_patched.o` produced (`fix_build.log`) |
| guest `/usr/src/sys` restored | md5s match the repo after restore; guest otherwise untouched |

## Fix

`fix.diff`: hold `dev->filelist_mutex` (the lock all mutators already use)
across both passes, release on the ENOMEM path, and bound pass 2 by the count
with `privcount = i` after the copy so the print loop can never read
uninitialized slots. Lock order is safe: no code path takes
`filelist_mutex` before `struct_mutex`, so `struct_mutex → filelist_mutex`
(the patched order) introduces no inversion. The `M_WAITOK` sleep under
`filelist_mutex` is legal (lockmgr-based, sleepable) and bounded.
`fix_status: not_testable` — runtime validation needs a DRM device; the patch
compiles clean under the real kernel flags and makes the overflow unreachable
by construction.

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