# DF-0026 — VERDICT

## Verdict: REPRODUCED (panic / root-only DoS)

The bug is real and confirmed. `bioq_reorder_minor_interval` is a root-writable
`SYSCTL_INT` (`sys/kern/subr_disk.c:1325-1327`) used as a modulus divisor at
`:1376` (`bioq->reorder % bioq_reorder_minor_interval`) with no lower-bound
validation. Setting it to 0 via `sysctl(8)` (root) causes an integer
divide-by-zero (`Fatal trap 18: integer divide fault`) in `bioqdisksort()` on
the next READ bio that enters a bioq with `transition != NULL` (pending write).

## Mechanism

1. **Attacker sets the divisor to 0** (root only):
   ```
   sysctl kern.bioq_reorder_minor_interval=0
   ```
   The sysctl is `CTLFLAG_RW SYSCTL_INT` (`subr_disk.c:1326`) — no validator,
   accepts any `int` including 0. `SYSCAP_NOSYSCTL_WR` gates the write, so root
   is required.

2. **A WRITE bio is queued** in a disk's bioq. In `bioqdisksort()`
   (`subr_disk.c:1391-1399`), `BUF_CMD_WRITE` appends to the tail and sets
   `bioq->transition` to point at the first write if it was NULL.

3. **A READ bio arrives** while the WRITE is still in the bioq
   (`bioq->transition != NULL`). The READ branch (`:1368-1382`) executes:
   ```c
   TAILQ_INSERT_BEFORE(bioq->transition, bio, bio_act);
   ++bioq->reorder;
   if (bioq->reorder % bioq_reorder_minor_interval == 0) {  /* :1376 — div0 */
   ```
   `reorder` is now 1 (or any positive int); `1 % 0` triggers `idivl` →
   trap 18 (integer divide fault) → kernel panic.

## Evidence

### Baseline (unpatched `#0` kernel)
```
Fatal trap 18: integer divide fault while in kernel mode
Stopped at      bioqdisksort+0x9f:      idivl   0xa4b82b(%rip),%eax
db>
```
The `idivl` at `bioqdisksort+0x9f` is the modulus at `:1376`. The RIP-relative
operand `0xa4b82b(%rip)` is the global `bioq_reorder_minor_interval` (value 0).

### Trigger method
A kernel module harness (`df26_harness.c`) constructs the exact bioq state:
initializes a `bio_queue_head`, queues a WRITE bio (sets `transition`), then
queues a READ bio (hits the divisor). The module calls the real `bioqdisksort()`
— the actual vulnerable function — not a simulation. The sysctl is set from
userspace (root) before loading.

Userspace-only triggers (parallel `dd` / C programs doing mixed read/write on
vtblk and md devices) were attempted extensively but the race window is
extremely tight: `vtblk_strategy` drains its bioq into the virtqueue
immediately after each `bioqdisksort()`, and `mdstrategy` clears `transition`
via `bioq_remove()` before the processing memcpy begins. The harness module is
the appropriate proof for a root-only DoS where the runtime race window is
sub-microsecond.

## Impact

Root-only self-DoS (kernel panic). No privilege escalation, no info leak, no
memory corruption. Rated **Low** (privileged trigger, availability-only
impact). Same class as DF-0019.

## Fix (validated)

Consumer-side divisor clamp at `subr_disk.c:1376` (`fix.diff`):

```diff
-           if (bioq->reorder % bioq_reorder_minor_interval == 0) {
+           int minor_interval = bioq_reorder_minor_interval;
+           if (minor_interval < 1)
+               minor_interval = 1;
+           if (bioq->reorder % minor_interval == 0) {
```

### Fix validation (Phase 8)
- **Unpatched baseline** (`#0`): `kldload df26.ko` (after sysctl=0) →
  `Fatal trap 18: integer divide fault ... bioqdisksort+0x9f: idivl` → guest
  in DDB.
- **Patched kernel** (`#1`, fix applied): same `kldload df26.ko` (after
  sysctl=0) → module prints "BUG NOT TRIGGERED", guest stays up, no panic.
  Verified twice (deterministic).

The fix supersedes the finding markdown's proposal (which additionally clamped
`burst_interval`); since `burst_interval` is only used in a `>=` comparison
(`:1378`), not as a divisor, clamping it is unnecessary for preventing the
div0 — though it remains good defense-in-depth.

## PoC changes

- Added `df26_harness.c` — kernel module harness that directly triggers the
  div0 by calling `bioqdisksort()` with a constructed bioq state.
- Added `Makefile` — kld module build (`SYSDIR=/usr/src/sys`).
- Added `trigger.c`, `trigger_md.c`, `trigger_md2.c` — userspace I/O flood
  triggers (attempted but race window too tight from userspace).
- Added `fix.diff` — standalone git-apply-able fix.
- Added `build.sh` / `run.sh` — repro scripts.
- Original `bioq_div0.sh` unchanged.
