# DF-4535 — VERDICT

**Status: reproduced** (impact: dos; leak facet proven; severity Low —
device node is 0600 root:wheel, so reaching lptwrite requires root or
equivalent on the node).

## Root cause (path:line)

`sys/dev/misc/lpt/lpt.c:763` — the return value of `uiomove()` is
discarded; `sc->sc_xfercnt = n` (lpt.c:764) is set unconditionally.
On a faulting user buffer, uiomove copies 0 (or a partial prefix),
and lptwrite:

* transmits the full `n` bytes from `sc_inbuf` — bytes beyond what was
  copied are stale heap content from the previous write (proven) or
  uninitialized slab memory (first use after open);
* loops forever because `uio_resid` never decreases once uiomove
  copies 0 bytes: `n = szmin(BUFSIZE, uio->uio_resid)` (lpt.c:761)
  stays non-zero.

Amplifier: lpt's `dev_ops` has flags 0 (lpt.c:191-198), so
`dev_needmplock()` (sys/kern/kern_device.c:119) is true and all
d_write/d_read/d_open/d_close run under the mplock (sys/kern/
kern_device.c:142-156).  In polled mode `lpt_pushbytes` sleeps with
Giant held (tsleep flags=0, lpt.c:657) — the livelocked syscall
starves every non-MPSAFE kernel path.

## How it was reproduced

Guest: DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC kernel + module
build of ppbus/ppc/lpt from the same tree (GENERIC does not compile
lpt in).  QEMU `-chardev file,id=par0,... -parallel chardev:par0`
gives a PC-style LPT at 0x378 irq 7 whose SW emulation (QEMU
hw/char/parallel.c) asserts the "printer ready" status pattern after
an INIT-low control write — `lptopen` succeeds and strobed data bytes
land verbatim in the host-side file = a perfect wiretap of the port.

Run 1 (IRQ mode, baseline module):

```
$ ./lpt_uioleak 10
[*] opened /dev/lpt0 (irq mode)
[*] phase1: wrote 4096 'A' bytes (kernel buffer now 'A')
[*] phase2: write(fd, 8192) with PROT_NONE guard page at 4096
[*] expecting syscall to NEVER return; will be killed after 10s
```
Process observed in state R4 (kernel) after 8 s; SIGKILL required.
Port capture (host /proc/<qemu>/fd/11): 21-byte smoke write, then
'A'*4096, 'B'*4096 (valid), then 133,120 bytes of stale content
(130 x 1024-byte blocks, all 0x42) — the buffer content from the last
successfully copied chunk, re-transmitted continuously until the kill.
Analysis transcript in `run.log`; raw bytes in `leak_sample_irq.bin`.

Run 2 (polled mode via `ioctl(LPT_IRQ, 0)` + reopen, baseline module):

```
kernel: lpt0: switched to polled standard mode
```
After ~90 s the guest stopped accepting ssh connections
("Connection timed out during banner exchange" — TCP accepted, sshd
child starved); the console showed no panic.  2,183,288 stale bytes
were strobed out before the wedge completed.  `kill -9` had been sent
8 s in and was ignored (no PCATCH on the polled path).  Guest had to
be force-reset.  Evidence: `bootlog-wedge-tail.txt`, first 64 KB of
the polled capture in `leak_sample_polled_head64k.bin`.

## Exploit chain

Not a corruption primitive.  As shipped, the node is 0600 root:wheel,
so this is a root-triggerable robustness/DoS/leak defect, not an
unprivileged escalation.  Impact if the node permissions are relaxed
(printer spoolers, lp groups): unkillable process + kernel mplock
starvation (system-wide DoS in polled mode) and an unbounded kernel
heap -> physical-port leak channel.

## Fix validation (mandatory-class: leak/DoS with reproduced behavior)

`fix.diff` (also applied to the guest's /usr/src copy — never to the
audit tree): check uiomove and bail out with the error.

* Baseline re-produced before the fix (Run 1/Run 2 above).
* Patched lpt.ko: `write()` returns -1/EFAULT after ~1 s; port capture
  delta exactly 8192 bytes = 'A'*4096 + 'B'*4096, ZERO stale bytes
  (`fix_capture_patched.bin`); a normal `printf > /dev/lpt0` write
  still works ("NORMAL-WRITE-STILL-OK" on the wire).
* fix_status: **fixed**.

## Kernel references

- sys/dev/misc/lpt/lpt.c:761-764 (bug: uiomove ignored)
- sys/dev/misc/lpt/lpt.c:657 (polled sleep without PCATCH)
- sys/dev/misc/lpt/lpt.c:191-198 (dev_ops flags 0 -> mplock held)
- sys/kern/kern_device.c:119-156 (dev_needmplock / dev_dopen mplock wrap)
- sys/kern/sys_generic.c:530-675 (write syscall -> devfs -> d_write)

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