vpoio: uninterruptible 500K/5M-iteration handshake spins (VP0_FAST_SPINTMO/VP0_LOW_SPINTMO) execute under vpo_intr's hard critical section β a malicious parallel-port ZIP emulator pins a CPU with interrupts masked for seconds per I/O (DF-4618 family, vpoio.c instance)
Summary
Every device wait is a raw MS_BRSET/MS_DBRA microseq spin (no-sleep port-I/O) with budgets of 500K/5M plus 5000 select iterations; all of it runs inside vpo_intr's crit_enter - worst case ~13M port-I/O iterations in ONE critical section against a device that withholds nBUSY: tens of seconds with preemption disabled and interrupts deferred, unkillable, repeatable per I/O. The interruptible ppb_poll_device path is stubbed '#if 0 broken'. Malicious-emulator/faulty-cable model. No ppbus on guest. Cut-ceilings (short-term) + drop-crit/interruptible-wait (proper) fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4637 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| trigger.sh | β | 934 B | view raw | |
| fix.diff | β | 988 B | view raw | |
| env.txt | β | 897 B | view raw | |
| verdict.json | β | 2.8 KB | view raw | |
| manifest.json | β | 1.0 KB | view raw |
DF-4637 β vpoio: uninterruptible 500K/5M-iteration handshake spins run under vpo_intr's hard critical section
File: sys/dev/disk/vpo/vpoio.c (pass 2, GLM 5.3)
Family: DF-4618 (immio.c instance, just filed) β this is the vpoio.c instance of the
same family; distinct code, distinct spin sites, shared caller crit_enter() wrapper.
Reproduce (requires hardware the audit guest does not have)
Build: nothing to build β the trigger is ordinary disk I/O.
Run (on a machine with a real or emulated parallel port, ppc attached, kldload vpo,
a ZIP drive behind it enumerating as /dev/daN):
# Malicious ZIP emulator: attach a parallel-port device emulator that answers
# the VP0 select/connect handshakes but then simply never asserts nBUSY during
# the data/status phases.
dd if=/dev/da1 of=/dev/null bs=512 count=1 # any I/O works
Expected (per source trace): each command phase wait spins up to 500,000 port reads
(VP0_FAST_SPINTMO), each completion-phase wait up to 5,000,000 (VP0_LOW_SPINTMO),
all inside crit_enter()/crit_exit() (vpo.c:215/323) β the issuing CPU runs with
preemption disabled and interrupts deferred for the full spin (order seconds per I/O on
LPC-class port I/O), unkillable, repeatable per command. Watchdog/soft-lockup messages,
missed timers, network RX drops on that CPU.
Status
untested β the audit guest has no parallel-port hardware at all
(dmesg/pciconf empty for ppc/ppbus/lpt; vpo.ko is present but cannot attach).
Mechanism is source-certain: see VERDICT.md for the line-by-line trace.
Family convention: DF-4618 / DF-4508 (hardware-gated, untested, attempts 0).
DF-4637 VERDICT β vpio handshake spins under vpo_intr's critical section
Status: untested (hardware-gated; no ppbus/ppc/lpt on the audit guest β env.txt). Mechanism: source-certain. Confidence: likely (same as DF-4618: the code path is unambiguous; the exploit premise β a malicious parallel-port ZIP emulator β is the driver's stated threat model and cannot be simulated on the QEMU guest).
Root cause (line-by-line)
vpoio_do_scsi() executes the entire SCSI-ish transaction synchronously in the
caller's thread. Its callers in vpo.c wrap it in a hard critical section:
sys/dev/disk/vpo/vpo.c:215βcrit_enter();sys/dev/disk/vpo/vpo.c:224-228βvpoio_do_scsi(&vpo->vpo_io, ...)(main I/O)sys/dev/disk/vpo/vpo.c:278-284β secondvpoio_do_scsi()for REQUEST_SENSE (still inside the same crit section)sys/dev/disk/vpo/vpo.c:323βcrit_exit();
Inside vpoio_do_scsi() every device handshake is a pure busy-wait microsequence
with no sleep, no yield, no interrupt escape:
sys/dev/disk/vpo/vpoio.c:59βVP0_FAST_SPINTMO 500000iterationssys/dev/disk/vpo/vpoio.c:60βVP0_LOW_SPINTMO 5000000iterationsvpoio_wait()(vpoio.c:525-558) executeswait_microseq(vpoio.c:103-113):MS_SET(tmo); loop:MS_BRSET(nBUSY, ready)+MS_DBRA(-2). In the ppbus engine (sys/dev/misc/ppc/ppc.c):MS_OP_BRSET(ppc.c:1453-1458) reads the status register each iteration (r_strβbus_space_read_1),MS_OP_DBRA(ppc.c:1447-1451) decrementsppc->ppc_accumand branches back. That iswhile (--tmo > 0) { if (status & nBUSY) break; }β nothing else.
Spin call sites inside vpoio_do_scsi, all reached under vpo_intr's crit:
- vpoio.c:705 β
vpoio_select()β select wait,VP0_SELTMO=5000 (vpoio.c:498-500) - vpoio.c:716 β command phase,
VP0_FAST_SPINTMOper CDB byte (β€16 per command) - vpoio.c:733 β data/completion phase,
VP0_LOW_SPINTMO(5,000,000 iterations) - vpoio.c:774 β status phase,
VP0_FAST_SPINTMO
Worst case per single I/O against a device that never asserts nBUSY:
5000 (select) + 16Γ500,000 (command) + 5,000,000 (data) β 13M port-I/O iterations
under one crit_enter(), plus a possible full second round for autosense
(vpo.c:278-284). At ~0.5β2 Β΅s per LPC port read that is tens of seconds per I/O
with preemption disabled and interrupts deferred on the issuing CPU. The wait loop in
vpoio.c:533-538 (#if 0 /* broken */) shows an interruptible ppb_poll_device()
variant was attempted and abandoned.
Threat model / reachability
- Stated threat: malicious parallel-port ZIP emulator (device-side attacker). It
only has to withhold nBUSY β no protocol violation needed. Every issued command
(any user I/O to the vpo-backed
dadevice β raw device nodes are group-readable byoperator; plus every scheduled backup/scan as root) pays the full spin. - Weaker variant: a faulty cable/dongle or a wedged drive produces the same stall.
- CAM serializes commands (VP0_OPENNINGS=1, devq maxopenings=1, vpo.c:151/156), so this
is a per-I/O livelock, not a concurrency bug β it is the duration under
crit_enter()that makes it a hard stall (interrupt blackout), not a race.
Why not higher severity
Requires physically-attached malicious hardware (or the same class of HW fault) β same reasoning as DF-4618 (Low) and the ig_hal stuck-semaphore family (DF-4306/DF-4508, Low). Availability impact only.
Fix
Short-term (this pack's fix.diff): cut the spin ceilings 10x as the family
mitigation. Proper fix: remove the crit_enter() around the synchronous transaction
in vpo_intr (vpo.c) β the ppbus ownership lock already serializes the bus β and/or
port vpoio_wait() to the interruptible ppb_poll_device() path the author stubbed
at vpoio.c:533-538.
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.
Fix verification
not_testablenot_testable: no parallel-port hardware on guest to baseline or patch-test; fix.diff is the family's short-term 10x ceiling cut, mirrors DF-4618 guidance, trivially reviewable
fix.diff
Confirmed kernel references
Detail
Exploit chain
[]
Evidence (decisive lines)
VERDICT.md (line-by-line trace: vpo.c:215 crit_enter; vpoio.c:59-60 ceilings; vpoio.c:103-113 wait_microseq; ppc.c:1447-1451 DBRA + 1453-1458 BRSET = raw spin; call sites vpoio.c:716/733/774); trigger.sh documents the hardware trigger; env.txt documents guest hardware absence; fix.diff = 10x ceiling cut (short-term family mitigation)
PoC changes
n/a β hardware-gated; trigger.sh documents the setup, never executed on guest
Verified recommended fix
Short-term: cut spin ceilings 10x (fix.diff). Proper: remove crit_enter/crit_exit around the synchronous transaction in vpo_intr (vpo.c) β ppbus ownership already serializes β and/or make vpoio_wait() interruptible via ppb_poll_device() (stub at vpoio.c:533-538).
Verdict
not_run: no ppbus/ppc/lpt hardware on the audit guest (env.txt: dmesg/pciconf empty, vpo.ko present but unattachable); mechanism source-certain β vpoio_do_scsi's wait/select handshakes are MS_BRSET/MS_DBRA no-sleep port-I/O spins (ppc.c:1447-1458) with budgets of 500000/5000000 iterations (vpoio.c:59-60), executed at vpoio.c:705/716/733/774 entirely inside vpo_intr's crit_enter/crit_exit (vpo.c:215/323, autosense round vpo.c:278-284 included). Worst case ~13M status-register polls per command under one critical section. Follows family convention of DF-4618/DF-4508.
No comments yet.