# DF-0107 — dkcksum32 OOB read via DIOCSDINFO32 (PoC)

`l32_setdisklabel()` (`sys/kern/subr_disklabel32.c:264-265`) calls
`dkcksum32(nlp)` on a user-supplied disklabel **without** the
`d_npartitions > MAXPARTITIONS32` guard that protects the read path
(`l32_readdisklabel`, `subr_disklabel32.c:225-226`). `dkcksum32()`
(`sys/sys/disklabel32.h:150-161`) computes its end pointer from the
attacker-controlled `d_npartitions` field and XOR-walks `u_int16_t` values to
it. With `d_npartitions = 0xFFFF` the walk goes ~1 MiB past the label buffer
(`0xFFFF * 16 = 1048560` bytes), reading kernel memory until it faults a
thread-stack guard page → **fatal trap 12 / kernel panic**.

## Privilege model

Opening the slice device `O_RDWR` is required (`FWRITE`, see
`subr_diskslice.c:583-584`). On DragonFlyBSD, disk device nodes are
`crw-r----- root:operator` (verified: `/dev/md0`, `/dev/vn0s0`). The `maxx`
test user (uid 1001) is **not** in `operator`, so this is a **root / operator
group** trigger — exactly the threat model in the finding ("local user with
write access to a disk device node"). The PoC must be run as root to open the
device.

## Build

On the guest, as the unprivileged build user:

```
cd poc/DF-0107
sh build.sh        # cc -O0 -o poc_diocsdinfo poc_diocsdinfo.c
```

## Run

On the guest, **as root** (creates a memory disk + issues the ioctl):

```
cd poc/DF-0107
sh run.sh          # may need a few invocations -- the panic is heap-layout dependent
# or, loop until panic:
for i in $(seq 1 30); do sh run.sh >/dev/null 2>&1 || true; done
```

The PoC:
1. `dd` an 8 MiB image, `vnconfig -c vn0 /tmp/oob.img` → `/dev/vn0s0`.
2. Builds a `disklabel32` with `d_magic = d_magic2 = DISKMAGIC32`,
   `d_npartitions = 0xFFFF` (everything else zero).
3. `open("/dev/vn0s0", O_RDWR)` then `ioctl(fd, DIOCSDINFO32, &label)`.

## Expected on a vulnerable kernel (reproduced)

The panic is probabilistic: the ioctl label is a `kmalloc(404, M_IOCTLOPS)`
buffer (`sys/kern/sys_generic.c:674-676`, since `sizeof(disklabel32)=404 >
STK_PARAMS=128`), and the ~1 MiB `dkcksum32` walk faults only when that buffer
lands within ~1 MiB **before** a thread-stack guard page in kernel VM. On a
freshly booted guest it fires within the first few invocations; on a churned
heap it may take more. Observed live twice (fresh-boot run 2 and run 3, then
run 1 of a later fresh boot):

```
panic: vm_fault: fault on stack guard, addr: 0xfffff800ab221000
cpuid = 1
...
--- trap 000000000000000c, rip = ffffffff80694ff7, rsp = ..., rbp = ... ---
l32_setdisklabel() at l32_setdisklabel+0x57 0xffffffff80694ff7
dsioctl() at dsioctl+0x721 0xffffffff806976f1
Debugger("panic")
Stopped at Debugger+0x7c: ...
db>
```

trap `0xc` = 12 = page fault while in kernel mode, inside `dkcksum32` (inlined
into `l32_setdisklabel+0x57`), called from the `DIOCSDINFO32` handler
`dsioctl`. The full signature is in `panic.txt`; the decisive loop output +
panic is in `run.log`.

## Fix

`fix.diff` clamps `dkcksum32`'s end pointer to `MAXPARTITIONS32` in
`sys/sys/disklabel32.h` — the root-cause fix that closes both DF-0106 and
DF-0107 (and any other caller) at the source. Applies with `git apply -p1`.

## Files

| file | purpose |
|------|---------|
| `poc_diocsdinfo.c` | minimal trigger (open slice device, DIOCSDINFO32 with d_npartitions=0xFFFF) |
| `build.sh` / `run.sh` | exact build / run invocations |
| `build.log` | final successful build, full output |
| `run.log` | decisive run: ioctl issued, then kernel panic (signature appended) |
| `panic.txt` | fatal-trap / panic / `db>` excerpt from `dfbsd-qemu/boot.log` |
| `env.txt` | guest `uname`, `cc` version, device permissions, ids |
| `fix.diff` | git-apply-able root-cause fix (clamp `dkcksum32`) |
| `VERDICT.md` | full narrative |
| `manifest.json` | machine-readable catalog |
