# DF-4184 VERDICT

**Status: reproduced** (impact: `leak` — kernel stack disclosure to a crafted ATAPI device)
Confidence: certain.

## What was proven, and how

The stock audit guest (DragonFly 6.5-DEVELOPMENT x86_64, QEMU 7.2 IDE DVD-ROM,
`/dev/acd0` root:operator 0640) cannot by itself emit a MODE SENSE page-0x05
response (QEMU answers ILLEGAL REQUEST asc=0x24 — verified live, `run.log`).
Following the DF-3690..3696 crafted-disk methodology, the guest kernel was
rebuilt with a **device-response emulator hook** in `ata_atapicmd()`
(`ata-queue.c`): when `df_audit.mode=1` it (a) rewrites the MODE SENSE(0x05)
response to `page_length=0xff`, (b) accepts the resulting MODE SELECT and
CLOSE TRACK, and (c) dumps the exact data-out bytes the device would receive.
`sys/dev/disk/nata/atapi-cd.c` itself was **not modified** for the baseline
reproduction (only `kprintf` tracing of the ioctl entry), so the vulnerability
demonstrated is the pristine driver's behavior under a malicious drive.

Observed (kernel #5, `df_audit.mode=1`, `df_audit.capfail=0`):

1. `CDRIOCFIXATE` reaches `acd_fixate()` (traced: `acd_ioctl cmd=8004636a`,
   `CDRIOCFIXATE entered`, `acd_fixate entered, calling mode_sense(0x05)`).
2. `acd_mode_sense(..., sizeof(param))` runs as `ata_atapicmd` with
   `count=64` (kernel-computed `sizeof(struct write_param)`).
3. The crafted response sets `param.page_length = 0xff` (atapi-cd.c:1150 reads
   it back unvalidated).
4. `acd_mode_select(dev, &param, param.page_length + 10)` issues MODE SELECT
   with **count=265** — a 265-byte DATA-OUT transfer sourced from the 64-byte
   stack object: **201 bytes over-read** of `acd_fixate`'s kernel stack frame.
5. The over-read bytes (captured in `run.log` / `leak_sample.txt`) contain
   canonical kernel pointers: `0xfffff800xxxxxxxx` heap/stack pointers and
   `0xffffffff80xxxxxx` kernel-text pointers (`+0x38: 0xffffffff8062cf93`,
   `+0xb8: 0xffffffff8069471f`, `+0xd0: 0xffffffff806946c3`) — direct
   KASLR-defeating material delivered to the device.
6. The ioctl completed (`ret=0`) after the normal fixate wait; the guest stayed
   up. A second independent sample of the same leak was captured in the
   `capfail=1` panic run (`../DF-4185/panic_serial_full.log`).

Direction note: MODE SELECT (opcode 0x55) with `flags=0` is host→device
(`ata-queue.c:204` `request->flags = flags | ATA_R_ATAPI` with no `ATA_R_READ`).
On the QEMU guest the (emulated) device rejects the command before the data
phase, so the bytes were captured by the hook reading the same source buffer
the DMA engine reads; on crafted hardware the device simply accepts the data
phase and receives them. The over-read length (265 vs 64) is proven by the
command itself, independent of the device's acceptance.

## Threat model honesty

* `/dev/acd0` opens are gated by `caps_priv_check_self(SYSCAP_RESTRICTEDROOT)`
  in `diskopen()` (`sys/kern/subr_disk.c:1071-1073`) — verified live (a plain
  `operator`-group user gets EPERM). So the attacker on stock DF is root /
  RESTRICTEDROOT software (e.g. cdrtools) combined with a malicious drive, or
  any configuration that relaxes that gate. The device-side input is the
  attacker-controlled element; the kernel must not trust it.
* Secondary variant: because MODE SENSE may transfer fewer bytes than
  `sizeof(param)`, a short-but-valid response leaves part of `param`
  uninitialized, and the follow-up MODE SELECT sends it — same disclosure
  family, subsumed by the same fix (bzero the struct or clamp+bound).

## Fix validation (both fixes exercised)

Kernel rebuilt with `fix.diff` on top of the same emulation:

* `df_audit.pl=255` + `capfail=1` (identical attack): `CDRIOCFIXATE ->
  ret=-1 errno=22` (EINVAL). **No MODE SELECT issued at all**, no over-read
  dump, no panic (`fix_run.log`).
* `df_audit.pl=50` (valid page) + `capfail=1`: MODE SELECT `count=60`
  (in-bounds), driver proceeds through CLOSE TRACK / TEST_UNIT_READY / the
  timeout division, ioctl returns EIO after the normal wait — no trap
  (`fix.2.log`; this run also validates the DF-4185 divisor clamp).

## Path references

* `sys/dev/disk/nata/atapi-cd.c:1140-1150` — mode_sense into 64-byte stack
  `param`; `acd_mode_select(dev, &param, param.page_length + 10)`
* `sys/dev/disk/nata/atapi-cd.c:1674-1681` — `acd_mode_select` passes pagesize
  straight to `ata_atapicmd` (DATA-OUT)
* `sys/dev/disk/nata/ata-queue.c:188-212` — `ata_atapicmd` sets
  `bytecount = count` for the transfer
* `sys/dev/disk/nata/atapi-cd.h` — `struct write_param` (64-byte packed,
  `page_length` at offset 9)

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