# DF-4535 — lptwrite() ignores uiomove() failure: stale-kernel-heap transmission + unkillable livelock (system-wide DoS in polled mode)

File: `sys/dev/misc/lpt/lpt.c` lines 761-764 (pass 2)

```
while ((n = (unsigned)szmin(BUFSIZE, uio->uio_resid)) != 0) {
        sc->sc_cp = sc->sc_inbuf;
        uiomove(sc->sc_cp, (size_t)n, uio);      /* return value DROPPED */
        sc->sc_xfercnt = n;
```

`uiomove()` copies at most `n` bytes and returns EFAULT when the user
buffer faults.  lptwrite() ignores the error and sets `sc_xfercnt = n`
regardless, so the driver:

1. **transmits bytes beyond what was copied** — the remainder of the
   1024-byte kernel buffer `sc_inbuf` (stale content of the previous
   write; uninitialized slab memory on first use) is strobed out the
   parallel data port (kernel heap -> physical port info leak), and
2. **never terminates** when the fault persists: the next loop
   iteration calls uiomove() at the faulting address again, copies 0
   bytes, `uio_resid` never shrinks, and the loop spins forever, each
   pass re-transmitting the full stale 1024-byte chunk.

In polled mode the inner sleep (`lpt_pushbytes`, lpt.c:657) has no
PCATCH, and the whole `d_write` runs under the kernel mplock
(lpt's dev_ops flags = 0, so `dev_needmplock()` is true,
sys/kern/kern_device.c:119-122): the livelocked write sleeps *holding
Giant*, starving every non-MPSAFE path in the kernel — on the test
guest this killed the ability to establish new ssh sessions
(system-wide denial of service).

## Reproduce (as root on the guest, lpt0 attached)

Build:
```
cc -O -Wall -o lpt_uioleak lpt_uioleak.c
```
Run (IRQ-driven port — default when the parallel port has an IRQ):
```
./lpt_uioleak 10
```
Polled variant (unkillable + mplock starvation):
```
./polled_switch      # ioctl(LPT_IRQ, 0) on /dev/lpt0.ctl; next open is polled
./lpt_uioleak 10     # then: kill -9 does NOT work; new ssh logins fail
```

## Expected / observed (baseline, unpatched lpt.ko)

* IRQ mode: `write()` never returns; process state R4 (kernel);
  `kill -9` is required and works (PCATCH at lpt.c:796).
  Host-side port capture (QEMU `-parallel chardev:file`): after the
  4096 valid 'B' bytes, **130+ extra 1024-byte blocks of stale buffer
  content (133,120 bytes, all 0x42)** — see `leak_sample_irq.bin`
  (offsets 4117..141333 after the 21-byte smoke write; analysis in
  `run.log`).
* Polled mode: same, plus `kill -9` cannot terminate the writer and
  the system stops accepting new ssh sessions ("Connection timed out
  during banner exchange"); 2,183,288 stale bytes captured before the
  guest was force-reset (`leak_sample_polled_head64k.bin`,
  `bootlog-wedge-tail.txt`).

## Fix validation (fix.diff applied to guest /usr/src, lpt.ko rebuilt)

* `./lpt_uioleak 10` → phase-2 `write()` returns -1/EFAULT after ~1 s.
* Port capture delta = exactly 8192 bytes = 'A'*4096 + 'B'*4096 —
  **zero stale bytes** (`fix_capture_patched.bin`).
* Normal write still works ("NORMAL-WRITE-STILL-OK" captured).

## Environment

DragonFly 6.5-DEVELOPMENT X86_64_GENERIC #0 Thu Jul 2 06:02:54 UTC 2026,
QEMU/KVM x86_64, 6 vCPU, isa-parallel (0x378, irq 7) with
`-chardev file` backend; kernel modules ppbus.ko/lpt.ko/ppc.ko built
from the guest's /usr/src/sys (see `env.txt`).
