β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4658

plip: lpoutput resurrects IFF_RUNNING and drives the port without IFF_UP or ppbus ownership β€” cross-driver corruption & broken interface state

Summary

lpoutput unconditionally sets IFF_RUNNING and writes CTR=IRQENABLE with no IFF_UP or ownership check. REPRODUCED on guest: after ifconfig down (bus released, intr torn down), ONE unpriv packet made the DOWN interface RUNNING; the following up landed in a broken UP-without-RUNNING half-state (the setup branch requires !RUNNING). While down it also stomps concurrent lpt0 jobs and enables IRQs with no handler. Early-return-when-down fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4658 Β· 4 files
FileTypeDescriptionSize
run.log β€” 849 B view raw
env.txt β€” 271 B view raw
README.md β€” 1.6 KB ↓ raw
VERDICT.md β€” 1.5 KB ↓ raw

DF-4658 β€” lpoutput drives hardware & resurrects IFF_RUNNING without IFF_UP/bus ownership

File: sys/dev/netif/plip/if_plip.c:602,609 (plus :613-616,685-688,730-733) Class: CWE-1254/incorrect state management β€” Low severity, certain Verdict: REPRODUCED (mechanism + anomalous flags; dos-flavored)

Repro (guest, run.log)

  1. root: ifconfig lp0 10.99.0.1 10.99.0.2 up (plip owns ppbus, intr attached)
  2. root: ifconfig lp0 down β†’ flags <POINTOPOINT,SIMPLEX,MULTICAST,PROXY> (no UP, no RUNNING; ppb_release_bus tore down lp_intr β€” ppbconf.c:514-521)
  3. unpriv user sends ONE UDP packet to the still-routed peer: ./plipspin 10.99.0.2 1 1400 β†’ lpoutput runs anyway: sets IFF_RUNNING (if_plip.c:602), ppb_wctr(IRQENABLE) (:609), transmits, and calls lp_intr directly (:730) β€” while plip no longer owns the bus. β†’ flags now <POINTOPOINT,RUNNING,...>: RUNNING on a DOWN interface.
  4. root: ifconfig lp0 up β†’ lands in a broken half-state (<UP,POINTOPOINT,...> w/o RUNNING/setup-path): the UP transition at if_plip.c:315 requires !RUNNING; the flag merge vs the RUNNING bit set by the stray output makes the setup path skip/mis-order β€” no bus request/intr setup is guaranteed for the resurrected state.

Impact

Any unprivileged user with a route via lp0 during a down window: (a) corrupts a concurrent lpt0 job on the same port (writes DTR/CTR), (b) re-enables port IRQs with no handler attached (stray-IRQ storm potential), (c) leaves the interface in inconsistent RUNNING states. On the guest, (c)+flags anomaly demonstrated byte-for-byte in run.log.

VERDICT.md
↓ download raw

Status: reproduced (logic/state machine, dos-flavored) Β· confidence: certain

Demonstrated on the QEMU guest (same environment as DF-4657): - after ifconfig lp0 down, flags show no RUNNING; - ONE unprivileged UDP packet to 10.99.0.2 (host route survives down on this stack; a static -iface route also works) β†’ ifconfig lp0 shows RUNNING on the down interface (if_plip.c:602), i.e. lpoutput wrote CTR=IRQENABLE (:609) and drove the data register with no bus ownership and no interrupt handler attached (torn down at release, ppbconf.c:514); - the subsequent ifconfig lp0 up no longer runs the setup branch (if_plip.c:315 requires !(if_flags & IFF_RUNNING)) β€” output shows the half-state UP without RUNNING, i.e. the admin's up/down cycle has been corrupted by an unprivileged packet.

Cross-driver consequence (not emulated: no second consumer loaded): the same code path writes the parallel-port registers while lpt0 owns the bus, garbling print jobs, and re-enables IRQs with no handler.

fix: lpoutput must return early when !(ifp->if_flags & IFF_UP) or bus not owned; never set IFF_RUNNING from the output path.

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_testable
baseline reproduced→ patch + rebuild →patched clean

not validated (Low; fix is a two-line early-return in lpoutput when !IFF_UP)

per-fix-DF-4658

Confirmed kernel references

Detail

Exploit chain

unpriv user -> UDP routed via downed lp0 -> lpoutput writes DTR/CTR without bus ownership + sets RUNNING -> lpt job corruption / stray IRQs / admin's up-cycle corrupted

Evidence (decisive lines)

run.log: flags sequence POINTOPOINT-only -> +RUNNING after one unpriv packet while DOWN -> UP-without-RUNNING after ifconfig up

PoC changes

reused DF-4657 sender; no changes needed

Verified recommended fix

In lpoutput(): if (!(ifp->if_flags & IFF_UP)) { m_freem(m); return ENETDOWN; } and never set IFF_RUNNING from the output path

Verdict

lpoutput() unconditionally sets IFF_RUNNING (if_plip.c:602), writes CTR=IRQENABLE (:609) and drives the port + calls lp_intr regardless of IFF_UP or ppbus ownership. Reproduced on the guest: one unprivileged UDP packet during a down-window put RUNNING on the DOWN interface, and the following ifconfig up landed in a broken UP-without-RUNNING half-state (setup branch at :315 requires !RUNNING). Enables cross-driver corruption of a concurrent lpt0 job and IRQ enablement with no handler.