ppiwrite() ignores the uiomove() return value: stale ppi_buffer bytes are handed to byte_peripheral_write() and a fully-faulting write never terminates (DF-4535 lpt twin, IEEE1284-peripheral side)
Summary
uiomove's rc is dropped and len bytes are transmitted regardless of what was copied - a faulting user buffer transmits stale prior-write content and a zero-copy fault re-sends the stale chunk indefinitely (PCATCH-killable, unlike lpt). Exact line-shape twin of runtime-verified DF-4535; binary-proven in the shipped module (uiomove rc never tested); write-path entry demonstrated live (blocks S3 wchan=ppiwrite). Stale-byte facet needs a physical 1284 master peer - honestly not_reproduced for that facet. check-and-bail fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4807 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ppiwrite_uio.c | β | 2.2 KB | view raw | |
| ppiwrite.dis | β | 2.8 KB | β download | |
| run.sh | β | 164 B | view raw | |
| run.log | β | 1.1 KB | view raw | |
| build.log | β | 693 B | view raw | |
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 3.2 KB | β raw | |
| verdict.json | β | 2.7 KB | view raw |
DF-4807 β ppiwrite() ignores the uiomove() return value: stale ppi_buffer bytes are handed to byte_peripheral_write() and a fully-faulting write never terminates (DF-4535 lpt twin, 1284-peripheral side)
File: sys/dev/misc/ppi/ppi.c line 478 (pass 2)
/* negotiation done, write bytes to master host */
while ((len = (int)szmin(uio->uio_resid, BUFSIZE)) != 0) {
uiomove(ppi->ppi_buffer, (size_t)len, uio); /* rc DROPPED */
if ((error = byte_peripheral_write(ppbus,
ppi->ppi_buffer, len, &sent)))
goto error;
}
This is the exact DF-4535 shape (lpt.c:763, runtime-verified): a faulting
user buffer leaves the tail of the 512-byte kernel ppi_buffer stale
(previous write's data; probe-time zeros on first use), and
byte_peripheral_write transmits the full len bytes to the IEEE1284
master host. A fully-faulting uiomove (0 bytes copied) leaves
uio_resid unchanged, so the loop re-sends the stale chunk forever β
though unlike lpt's polled mode, this loop's exits are the wire handshake
timeouts in byte_peripheral_write (ppb_poll_bus PPB_FOREVER waits on the
remote master, PCATCH-interruptible) rather than an unkillable Giant-held
livelock.
Reproduce boundary (honest status: not_reproduced for the leak facet)
The transmit loop (ppi.c:477) is only reachable after the local host
enters IEEE1284 peripheral mode β PPB_PERIPHERAL_IDLE β which
requires a remote master host to complete the 1284 negotiation
handshake (ppi.c:450-471). The QEMU guest's parallel port is an SPP-style
emulation with no 1284 master peer, so on this guest:
write()demonstrably enters the path and blocks in the negotiation wait: process stateS3, wchanppiwrite(tsleep ppi.c:459, PCATCH), SIGKILL terminates it (run.log).- The stale-byte transmission itself needs physical peer hardware.
What IS proven beyond the source line:
- the module binary drops the check β
objdump -dr(ppiwrite.dis):callq uiomoveat 0x58e/0x59e, return in%eaxnever tested, the very next call isbyte_peripheral_writeat 0x5a0 whose return IS tested (test %eax,%eax; jneat 0x5a8); - the identical code shape was runtime-verified for lpt (DF-4535: 133,120 stale bytes strobed out the port in IRQ mode on this same guest).
Build / run (root, ppi.ko with PERIPH_1284 loaded)
cc -O -Wall -o ppiwrite_uio ppiwrite_uio.c ./ppiwrite_uio & # blocks in the 1284 negotiation wait ps ax -o pid,stat,wchan,comm | grep ppiw kill -9 <pid> # PCATCH: terminated
Impact
Low: device node 0600 root:wheel + options PERIPH_1284 (LINT64-only)
+ a physical 1284 master peer. Stale kernel-buffer bytes (not kernel
heap at large β ppi_buffer is only ever filled by uiomove/ppb_1284_read)
leave the machine over the parallel cable; a faulting write spins the
retransmit loop (bounded by wire timeouts, killable). Defense-in-depth:
check uiomove and bail.
DF-4807 β VERDICT
Status: not_reproduced (the stale-byte transmission facet is hardware-gated on this guest: it requires an IEEE1284 master peer to complete the peripheral-side negotiation before ppiwrite's data loop is reachable). reproduced=0, impact=none as demonstrable on this guest; the code defect is certain, and its runtime consequence is proven by the line-identical, runtime-verified twin DF-4535 (lpt).
Root cause (path:line)
sys/dev/misc/ppi/ppi.c:478 β uiomove(ppi->ppi_buffer, len, uio);
return value discarded; byte_peripheral_write(ppbus, ppi->ppi_buffer,
len, &sent) (ppi.c:479-480) transmits len bytes regardless of how
many uiomove actually copied. On EFAULT: stale ppi_buffer content
(prior write's bytes) is transmitted to the 1284 master; if uiomove
copies 0 bytes, uio_resid never shrinks and the loop re-sends the
stale chunk indefinitely (bounded only by the wire handshake timeouts /
PCATCH). Same defect class as DF-4535 (sys/dev/misc/lpt/lpt.c:763,
reproduced live: 133,120 stale bytes out the port).
What was verified live (ppi.ko built with PERIPH_1284, pristine
sources, guest 6.5-DEVELOPMENT X86_64_GENERIC, ppc0 0x378 irq7)
- Path entry + block:
write(fd, faulting_buf, 8192)blocks in the kernel at ppi.c:459 βps:S3 wchan=ppiwrite; SIGKILL terminates (PCATCH sleep, unlike lpt's polled-mode unkillable loop). The data loop at ppi.c:477 requiresPPB_PERIPHERAL_IDLE, which requires a remote 1284 master host β absent on QEMU (SPP emulation only). - Binary proof of the dropped check (
ppiwrite.dis, objdump -dr of the loaded module):58e: callq ... R_X86_64_PLT32 uiomove-0x4 593: mov %r15,%rsi <- no test of %eax (uiomove rc) ... 5a0: callq ... R_X86_64_PLT32 byte_peripheral_write-0x4 5a5: mov %eax,%r15d 5a8: test %eax,%eax <- THIS rc is checked - Sibling verification cross-reference: DF-4535 evidence pack (same guest, same port) demonstrates the identical dropped-uiomove shape transmitting stale kernel bytes out the parallel port in lpt.
Exploit chain
None. Privileged-only (0600 root:wheel), PERIPH_1284-gated, peer-hardware-gated; impact ceiling is stale-buffer disclosure over the parallel cable plus a killable retransmit spin. Fix is defensive.
Fix (not hardware-validated on this guest β trivial check-and-bail)
while ((len = (int)szmin(uio->uio_resid, BUFSIZE)) != 0) {
- uiomove(ppi->ppi_buffer, (size_t)len, uio);
+ if ((error = uiomove(ppi->ppi_buffer, (size_t)len, uio)))
+ goto error;
if ((error = byte_peripheral_write(ppbus,
ppi->ppi_buffer, len, &sent)))
goto error;
(Validating this end-to-end requires a 1284 master peer; the fix is the same check-and-bail validated for DF-4535.)
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableEnd-to-end fix validation requires IEEE1284 master-peer hardware (absent on the QEMU guest); the proposed check-and-bail is identical to the fix validated for DF-4535.
VERDICT.md 'Fix' section
Confirmed kernel references
Detail
Evidence (decisive lines)
ppiwrite.dis (objdump -dr of the loaded module: uiomove rc dropped); run.log (write() blocks at ppi.c:459 S3/wchan=ppiwrite, SIGKILL_TERMINATED); README.md cross-reference to the DF-4535 evidence pack
PoC changes
PoC authored fresh (no seed); note signal(3) installs SA_RESTART on DF so a caught signal restarts the blocked write - the block/kill demo must use SIGKILL.
Verified recommended fix
Check the uiomove() return at ppi.c:478 and goto error on failure (same check-and-bail as the validated DF-4535 fix).
Verdict
ppiwrite() discards the uiomove() return (ppi.c:478) and hands the full len bytes of the kernel ppi_buffer to byte_peripheral_write regardless of how many were copied - the DF-4535 (lpt.c:763) twin on the 1284-peripheral side, proven in the module binary (objdump: uiomove call at 0x58e with %eax never tested before byte_peripheral_write at 0x5a0, whose rc IS tested at 0x5a8). The stale-byte transmission facet could not be exercised on the QEMU guest: reaching the data loop requires PPB_PERIPHERAL_IDLE, i.e. a remote IEEE1284 master host to complete the peripheral negotiation, and the SPP emulation provides none - the write demonstrably enters the driver and blocks in the negotiation wait (S3, wchan=ppiwrite, SIGKILL-killable via PCATCH; run.log). Confidence certain: the dropped check is in the shipped binary and the identical code shape was runtime-verified transmitting 133,120 stale bytes for lpt (DF-4535) on this same guest/port.
No comments yet.