# DF-0074 PoC — DIOCGSLICEINFO heap overflow via crafted GPT disk image

## What this proves

The `DIOCGSLICEINFO` handler (`sys/kern/subr_diskslice.c:557`) `bcopy()`s
`dss_nslices`-worth of `struct diskslice` (256 B each) into the ioctl `data`
buffer, which `mapped_ioctl` sizes at `sizeof(struct diskslices)` = 4128 B
(only `MAX_SLICES = 16` slots). A GPT disk whose header advertises 128 entries
makes the kernel set `dss_nslices = BASE_SLICE + 128 = 130`
(`sys/kern/subr_diskgpt.c:175,222`), so the `bcopy` writes
`32 + 130*256 = 33312` bytes into the 4128-byte buffer — a deterministic
**29184-byte (~28 KB) overrun** of kernel heap on every call. With slab
churn the overrun reliably surfaces as a kernel panic; the underlying memory
corruption is weaponizable for local privilege escalation.

## Verified on

DragonFly 6.5-DEVELOPMENT master DEV
(`v6.5.0.1712.g89e6a-DEVELOPMENT #1`, X86_64_GENERIC). See `VERDICT.md` for
the full narrative and `env.txt` for the guest environment.

## Reproduce

```sh
# 1. On a host with python3: build the crafted GPT image (128 entries).
python3 build_gpt.py overflow.img

# 2. Copy the whole folder + overflow.img to the guest (as maxx).
#    scp -r . dfbsd-maxx:poc/DF-0074/

# 3. On the guest, build the triggers (as maxx).
sh ./build.sh                      # -> trigger, trigger_stress

# 4. Attach the image and fire the overflow (as root; see Reachability).
#    sh ./run.sh vn0
#
#    The single trigger prints "DIOCGSLICEINFO returned nslices=130"
#    (proof the oversized bcopy executed). The stress/flood section then
#    forces the slab corruption to surface. The panic is ASYNCHRONOUS and
#    may land a few seconds after the script returns; capture it from the
#    serial console log (dfbsd-qemu/boot.log on the QEMU host).
```

`run.sh` must run as **root** (or a principal in the `operator` group / a
devfs class that exposes slice devices). On the default devfs ruleset,
`/dev/vn0*` are `root:operator crw-r-----` and `operator` contains only
`root`, so an unprivileged user gets `EACCES`.

## Expected result (bug present)

- The trigger returns `nslices=130` (the 28 KB heap overflow executes).
- The guest panics within the run (asynchronous, heap-layout dependent).
  Observed signatures on master DEV (all in `panic.txt`):
  - `panic: slaballoc: corrupted zone` in `_kmalloc <- fork1`
  - `Fatal trap 12 ... slab_cleanup+0x1c9` (NULL deref, idle reclaimer)
  - `panic: ... hammer2_chain_create` preceded by `dscheck(vbd0s1d): slice too large`
    (the overrun corrupted the *root* disk's slice metadata)

## Expected result (bug fixed)

With `fix.diff` applied, the `bcopy` is capped at `MAX_SLICES`; the trigger
returns `nslices=130` (the live count is unchanged) but **no** heap overrun
occurs and no panic follows under any amount of churn.

## Files

| File | Purpose |
|------|---------|
| `build_gpt.py` | host-side GPT image generator (128 entries) |
| `trigger.c` | minimal proof: single `DIOCGSLICEINFO`, prints `nslices` |
| `trigger_stress.c` | ioctl + slab churn + fork/flood to surface the panic |
| `build.sh` / `run.sh` | self-contained reproduce scripts |
| `build.log` / `run.log` | full untrimmed build / decisive-run output |
| `panic.txt` | the three panic signatures from `dfbsd-qemu/boot.log` |
| `env.txt` | guest uname, cc, devfs perms, reachability caveat |
| `VERDICT.md` | full root-cause + reproduction narrative |
| `fix.diff` | `git apply`-able bounds fix (cap copy at `MAX_SLICES`) |
| `manifest.json` | machine-readable artifact catalog |
| `PANIC.txt` | archived capture from the prior 6.4.2-RELEASE run |

## Notes

- `python3` is not on the DragonFly guest by default; generate `overflow.img`
  on a host and ship it.
- Only `>= 15` GPT entries are needed for `dss_nslices > MAX_SLICES`
  (`dss_nslices = 2 + i`); the PoC uses the maximum 128 for the largest
  overrun.
- The overflow source (`dsmakeslicestruct`, 130 slots) is correctly
  allocated; only the `DIOCGSLICEINFO` destination is undersized.
