# DF-5418 — VERDICT

**Status: reproduced** (KLD-harness-with-production-code; guest
DragonFly 6.5-DEVELOPMENT x86_64, `vm.sh` QEMU/KVM, no parallel-port
hardware — the evidence class prescribed for this file).

## What was run

`ppbharness.ko` compiles the **unmodified** production
`/usr/src/sys/bus/ppbus/ppb_1284.c` (sha256 `88eaa04ce517…`, identical
to the audited repo copy) and `ppb_base.c` (which provides
`ppb_poll_bus`/`ppb_set_mode`) with `-Werror` against a scripted
malicious IEEE 1284 peer: a `fakeppc` controller on nexus implementing
the `ppbus` INTERFACE (`io`, `setmode`), and a `ppbshim` child device
whose softc is a real `struct ppb_data`. All 1284 code under test —
`ppb_peripheral_negotiate()`, `ppb_1284_read_id()`,
`ppb_peripheral_terminate()`, the register macros — is the production
code; only the register source is synthetic.

## Root cause (static)

`ppb_1284.c:201-206`:

```c
spin = 10;
while (spin-- && (ppb_rstr(bus) & nBUSY))
        DELAY(1);
if (!(ppb_rstr(bus) & SELECT) || !spin) {   /* ← ppb_1284.c:206 */
```

`spin--` yields the pre-decrement value and always decrements:

* loop exits by **counter exhaustion** → `spin == -1` → `!spin` false →
  the `ENODEV` timeout branch this clause was written for can never fire
  ("Event 4" timeout detection is dead code);
* loop exits because the **host answered on poll #10** → `spin == 0` →
  `!spin` true → spurious `ENODEV` for a handshake that succeeded in
  time. (Answer on any earlier poll leaves `spin > 0` and succeeds — a
  pure timing flip.)

## Observed (run.log, baseline)

```
T1 host-answers-poll-10: ret=19 state=1  error=0   ← WRONG: peer completed the handshake; ret should be 0
T2 host-answers-poll-5 : ret=0  state=12 error=0   ← control: same handshake one poll earlier succeeds
T3 host-silent-timeout : ret=22 state=0  error=1   ← WRONG: silent peer; should be ENODEV(19) timeout,
                                                     is EINVAL(22) + error=PPB_MODE_UNSUPPORTED(1)
```

T1 vs T2 is the decisive pair: identical peer intent, one poll of
timing difference, success↔failure flips. T3 proves the dead timeout
branch: a completely silent peer is *not* reported as a timeout — the
code falls through to Event 4, reads the (peer-controlled / floating)
data register, mismatches every request mode, and records
`PPB_MODE_UNSUPPORTED` instead.

## Fix validation (fix_run.log / ppb_1284.fixA.c)

One-line fix at `ppb_1284.c:206` — `!spin` → `spin < 0`:

```
T1: ret=0  state=12 error=0   ← success restored
T2: ret=0  state=12 error=0   ← control unchanged
T3: ret=19 state=1  error=0   ← timeout correctly detected (ENODEV)
```

Baseline reproduced → patched behavior gone → **fixed**. The patched
production file rebuilt clean with `-Werror`; the pristine source was
restored and `cmp`-verified afterwards.

## Impact ceiling

Peer-controlled logic corruption of the peripheral-side negotiation
state machine: wrong success/failure decisions and a dead timeout
branch. No memory-safety consequence — the peer controls only status
bits and (via a fall-through) which error path runs; no length, buffer
or pointer derives from peer input on this path. In-tree the function
is only called from `ppiwrite()` under a `PERIPH_1284` kernel config
(not in GENERIC; LINT64-only option), so real-world exposure requires
that non-default configuration plus a malicious/buggy IEEE 1284 host
on the cable. Severity: **Low** (robustness/logic defect in a niche
mode), confidence **certain** (static proof + reproduced both halves on
production code).

## Attempts / changes vs. seed

PoC written fresh for this finding (no seed existed). Four build/run
iterations were needed on the harness plumbing (not the finding):
DRIVER_MODULE arity, `devclass_t` lvalues, and DF's
`BUS_ADD_CHILD(parent,parent,…)` idiom for nexus children (first
attempt panicked in `nexus_print_child` with ivars-less
`device_add_child` — diagnostic only, unrelated to the finding). The
production sources compiled clean on the first try.

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