# DF-5626 VERDICT — reproduced (Low, impact: dos / CPU livelock)

## Root cause

`sys/bus/ppbus/ppb_base.c:60-67`:

```c
for (j = 0; j < ((how & PPB_POLL) ? max : 1); j++) {
        for (i = 0; i < 10000; i++) {
                r = ppb_rstr(bus);
                DELAY(1);
                if ((r & mask) == status)
                        return (0);
        }
}
```

With `PPB_POLL` set the whole wait is `max` x 10000 busy iterations of
`DELAY(1)` + a port read: no sleep, no signal checkpoint, no preemption
point, no clamp on `max` (a caller-controlled int, exported via
`ppbus.ko`'s `EXPORT_SYMS`, Makefile:13).  `PPB_NOINTR | PPB_POLL` —
what all in-tree poll callers pass (ppb_1284.c:58, :64, :704) — is
numerically just `PPB_POLL` (ppbconf.h:126,129: PPB_NOINTR is 0), so the
author's "don't use PCATCH" intent silently becomes "busy-spin the entire
window".

## Verification (production code, measured)

The shared `ppbbasehx` KLD compiles the guest's **unmodified**
`/usr/src/sys/bus/ppbus/ppb_base.c` and calls `ppb_poll_bus()` against a
never-matching scripted peer (`status=0xD0`, poll requires SELECT==0).
The fake controller counts status-register reads:

| call | rstr reads | wall | interpretation |
|---|---|---|---|
| `max=5,  PPB_NOINTR\|PPB_POLL` | **exactly 50000** | 1.00 s | 5 x 10000 busy iterations, zero sleeps |
| `max=100, PPB_NOINTR\|PPB_POLL` | **exactly 1000000** | **19.67 s** | do_peripheral_wait's argument: ~20 s CPU pin |
| `max=3,  PPB_NOINTR` (control) | exactly 10002 | 0.24 s | 10 ms busy prologue + 2 condition checks + 2 tsleeps |

The read-count is the decisive discriminator: a sleeping poll of the same
nominal duration performs ~10000x fewer register reads.  Nominal duration
is `max` x 10 ms; the measured overshoot (~20x) is the cost of
`DELAY(1)` + newbus method dispatch per iteration in this guest — i.e.
the livelock window is *worse* under virtualization, exactly the
environment parallel-port-enabled VMs run in.

Stability: identical structure across 3 loads (run.log, run2.log, earlier
build), wall times within 3%.

## Impact ceiling

- Root-gated local CPU-DoS windows: reachable on GENERIC via
  `do_1284_wait` (max=4 → 40 ms busy per IEEE1284 event wait; callers
  lptread (lpt.c:695), ppiread (ppi.c:342), vpo/immio negotiate) from
  0600 root:wheel device nodes.
- `do_peripheral_wait` (max=100 → 1 s nominal, ~20 s measured here) and
  the negotiate PERIPH branch (max=40) are compiled into every kernel but
  gated behind the non-GENERIC `PERIPH_1284` option; the ppi entry to the
  peripheral path is additionally dead per DF-4806.
- The exported API accepts any `int max` from KLD consumers (third-party
  ppbus controllers), including `INT_MAX` (≈ 8 months of busy-spin).
- Uninterruptible *during* each 10 ms-to-seconds window; SIGKILL cannot
  shorten it, other threads on the pinned CPU starve at equal priority.

Low severity (root trigger; DF-5565 livelock family).

## Fix

`fix.diff`: after each 10 ms busy window (when further windows remain),
`tsleep(bus, PCATCH, "ppbpoll", 1)` and abort on non-EWOULDBLOCK — keeps
the total poll duration, makes the wait preemptible and
signal-cancellable, kills the pin.  Not fix-validated on the guest: the
behavioral delta is scheduler-observable (preemption during the window),
which a same-thread harness cannot self-measure honestly, and a
single-fix kernel rebuild is disproportionate for a Low finding;
`rstr_reads`/window arithmetic of the patched function re-verified by
inspection (loop body unchanged, only inter-window yield added).
fix_status: not_testable.

## Environment

DragonFly 6.5-DEVELOPMENT x86_64 (X86_64_GENERIC), cc 8.3, 6 vCPU QEMU
guest, hz=100, no parallel port.

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