# DF-2741 VERDICT — DIOCGSLICEINFO heap overflow

**Status: reproduced. Impact: kernel memory corruption (panic; write-capable
OOB). Confidence: certain.**

## What happens
1. `dsmakeslicestruct()` (sys/kern/subr_diskslice.c:714-747) allocates a
   `struct diskslices` with a *runtime* slice count. `gptinit()`
   (sys/kern/subr_diskgpt.c:175) allocates `BASE_SLICE + MAX_GPT_ENTRIES`
   = 2 + 128 = **130** slice records and sets
   `ssp->dss_nslices = BASE_SLICE + i` (subr_diskgpt.c:222) where i can be
   128 (any GPT whose header declares ≥ 128 entries; bound checked only
   against MAX_GPT_ENTRIES, never against MAX_SLICES).
2. The `DIOCGSLICEINFO` ioctl type is `_IOR('d', 111, struct diskslices)`
   (sys/sys/diskslice.h:96) — its buffer capacity is fixed by the *declared*
   type: `offsetof(dss_slices) + MAX_SLICES * sizeof(struct diskslice)`
   = 32 + 16*256 = **4,128 bytes** (MAX_SLICES = 16, sys/sys/diskslice.h:122).
3. `dsioctl()` case DIOCGSLICEINFO
   (sys/kern/subr_diskslice.c:556-558) does
   `bcopy(ssp, data, (char *)&ssp->dss_slices[ssp->dss_nslices] - (char *)ssp)`
   — a *runtime*-length copy of 32 + 130*256 = **33,312 bytes** into the
   4,128-byte kernel ioctl buffer (`sys_ioctl()` kmallocs exactly
   IOCPARM_LEN bytes, sys/kern/sys_generic.c:668-676; 4128 > STK_PARAMS=128
   so it is a heap allocation from M_IOCTLOPS).
4. Net effect: **29,184 bytes of out-of-bounds heap write** per ioctl, with
   content = 256-byte `struct diskslice` records 16..129: per record 8-byte
   kernel heap pointer (`ds_dev`), two fully attacker-controlled u64s
   (`ds_offset`/`ds_size` = the GPT entry's lba_start/lba_end+1), two
   attacker-controlled 16-byte UUIDs, and ~200 zero bytes. A GPT with fewer
   entries overflows proportionally (e.g. 20 entries → 1,024 bytes OOB).

## Reproduction (guest, DragonFly 6.5-DEVELOPMENT #0, stock INVARIANTS)
- Crafted GPT image (`mkimages.py`): protective MBR (type 0xEE), header at
  LBA1 with entries=128/entsz=128 and valid CRCs, 128 non-nil entries.
- `vnconfig -c /dev/vn0 gpt.img` → 129 device nodes; runtime probe sets
  dss_nslices = 130.
- `./poc2741 /dev/vn0 info` (run.log): kernel bcopy length 33312 vs buffer
  4128 — **the overflow already happened on this single ioctl**.
- `./poc2741 /dev/vn0 hammer` (two independent runs, fresh
  `vm.sh reset with-src` between them): both runs ended in
  `Fatal trap 12: page fault while in kernel mode`,
  `fault virtual address = 0x0`, `instruction pointer = 0x8:0x0`,
  guest dead at `db>` (panic.txt, panic_boot.log, panic2_boot.log).
  A call to address 0 is a function pointer zeroed by the mostly-zero
  overflow — the write-capable primitive is proven end-to-end.

## Exploit chain assessment (why not uid0 here)
- Reachability: `ioctl(DIOCGSLICEINFO)` needs an open fd on a cooked disk
  node. Verified live that `diskopen()` requires SYSCAP_RESTRICTEDROOT
  (subr_disk.c:1072): an operator-group (gid 5) user gets EPERM at open,
  despite the 0640 root:operator node bits. So only root-class credentials
  can trigger it on a default system → **no unpriv→root chain exists via
  this bug today**; the ceiling is root→kernel corruption (still a kernel
  memory-safety defect; a sandboxed/jailed root or relaxed devfs ruleset
  re-opens the question).
- Primitive quality: 29 KB *linear* heap overflow with per-record
  controlled u64s and predictable zero runs, on a guest/kernel with no
  KASLR/SMAP/SMEP — an operator-class or jailed-root attacker would have
  everything needed to convert this into code execution (groom adjacent
  M_IOCTLOPS/malloc-type objects, smash a function pointer with a
  controlled 8-byte ds_offset value instead of the zero that happened
  naturally). The naturally-occurring NULL-call demonstrates control-path
  takeover without any grooming at all.

## Fix validation
`fix.diff` clamps the bcopy to MAX_SLICES records. Applied to the guest's
/usr/src, kernel rebuilt (`make nativekernel`), rebooted, PoC re-run:
see fix section of manifest.json / verdict.json (fix_status) — patched
kernel survived the same hammer + cross-allocation runs that panicked the
stock kernel twice, and the info mode still reports the (correct,
informational) nslices=130 header. The overflow is gone.

## Kernel references
sys/kern/subr_diskslice.c:556-558 (bug), :715-747 (runtime-sized alloc),
sys/kern/subr_diskgpt.c:136-144,175,222 (nslices up to 130),
sys/sys/diskslice.h:96 (declared 16-record ioctl type), :122 (MAX_SLICES),
:106-109 (DKMAXSLICES/DKMAXPARTITIONS=128/256 vs MAX_SLICES=16),
sys/kern/sys_generic.c:668-676 (exact-size heap ioctl buffer),
sys/kern/subr_disk.c:1196-1205 (dispatch to dsioctl).
