# DF-4974 VERDICT

## Bottom line

**Not reproduced on this guest — the guest has no radeon GPU (no /dev/dri at
all).** The finding is a *statically proven missing validation*, with impact
confined to real radeon hardware: the display engine fetches up to
`64*64*4 = 16 KiB` starting at the pinned cursor BO's VRAM address while the
driver never requires the BO to be that large, so an undersized BO makes the
cursor scanout read adjacent VRAM and paint it on screen. `status:
not_reproduced`, `reproduced: 0`, `impact: none (on this guest)`. Confidence
in the missing check itself: certain (code); in the OOB scanout: likely
(inferred from the programmed CUR_SIZE / CUR_OFFSET registers).

## Root cause (path:line)

- `sys/dev/drm/radeon/radeon_cursor.c:301-316` — `radeon_crtc_cursor_set2()`
  looks up any GEM handle (`drm_gem_object_lookup`), checks only
  `width > max_cursor_width || height > max_cursor_height` (radeon_cursor.c:295-299),
  then pins the object in VRAM via `radeon_bo_pin_restricted()` and stores
  `cursor_addr`. **There is no check that the BO covers the cursor image
  (`width*height*4`, up to 16 KiB) — or any minimum size at all.**
- `sys/dev/drm/radeon/radeon_cursor.c:215-224` — `radeon_cursor_move_locked()`
  programs the fetch window: `CUR_SIZE = ((w-1)<<16) | (h-1)` with
  `w,h` = user-supplied width/height (≤ 64x64). The display engine fetches
  `w*h*4` bytes from `cursor_addr`.
- `sys/dev/drm/radeon/radeon_cursor.c:241-243` — pre-AVIVO path adds
  `yorigin * 256` (up to ~16 KiB more) to the fetch base address, so even
  small cursors can fetch past a 1-page BO on legacy ASICs.

Contrast with the universal-cursor path (drm_plane.c:831-846) which wraps the
handle in a framebuffer whose creation validates object size against
`pitch*height` — radeon does not use that path: it registers only legacy
`.cursor_set2`/`.cursor_move` funcs (radeon_display.c:661-662) and no cursor
plane (`drm_crtc_init`, radeon_display.c:679, passes no planes), so
`crtc->cursor == NULL` in `drm_mode_cursor_common` (drm_plane.c:935-938) and
the raw user width/height/handle flow straight into the driver with **no
core-side validation**.

## Reachability

`DRM_IOCTL_MODE_CURSOR` / `MODE_CURSOR2` are `DRM_MASTER`-gated
(sys/dev/drm/drm_ioctl.c:659,680) — reachable by the unprivileged user who
owns the graphical seat (console/X session gets DRM master), which matches the
audit context. Requires radeon KMS hardware.

## Why not reproduced / why not uid=0

This is not a memory-corruption primitive: the OOB read is performed by the
display engine in VRAM and the data surfaces as cursor pixels on the physical
screen — an information-exposure primitive (adjacent VRAM, e.g. other
windows'/users' framebuffer content on multiseat), not a kernel-memory write.
No escalation chain exists from it. The guest (QEMU/KVM `dfbsd 6.5-DEVELOPMENT
X86_64_GENERIC`) has no GPU: `open("/dev/dri/card0")` → `ENOENT`, no drm/radeon
modules, `sysctl hw.drm` unknown (run.log). Fix validation on hardware is
therefore impossible here; the proposed fix is a pure input-validation clamp
with no behavioral risk.

## Killed/considered while here (negative results with weight)

- Refcount/lifetime balance in `radeon_crtc_cursor_set2` — every path balances
  (lookup ref transferred to `cursor_bo`; old bo unpinned + put once; pin
  arithmetic symmetric even when the same handle is set twice, via the
  `pin_count` early-return in radeon_object.c:333-334).
- `radeon_bo_reserve` failure on the old bo (radeon_cursor.c:350-354): leaves
  `pin_count` elevated and drops the last ref → the BO is freed by TTM anyway;
  worst case `rdev->vram_pin_size` accounting drifts (consumed only by
  radeon_gem.c:65,232-234 reporting/suballoc caps). Benign; not filed.
- width==0 / height==0 / hot_x overflow (radeon_cursor.c:332-333, 147, 219-224):
  sink is `(w-1)<<16`-style **MMIO register** garbage, upstream-identical code,
  display-only effect. No kernel memory write. Not filed.
- `radeon_show/hide/move` crtc derefs: crtc comes from `drm_crtc_find`;
  `crtc_id` default-cases return early (radeon_cursor.c:81-82,132-133). Clean.
- Pin-restricted early-return on an already-pinned (e.g. GTT) BO ignoring the
  27-bit legacy restriction: would need a user BO pre-pinned in GTT — no such
  user-reachable path. Killed.

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