plip: heap overflow / OOB-read in lp_intr racing SIOCSIFMTU buffer swap
Summary
lpioctl SIOCSIFMTU at 348-354: ptr=sc_ifbuf; sc_ifbuf=kmalloc(new_mtu+14); kfree(ptr); if_mtu=new_mtu in 3 unlocked steps. lp_intr ithread (336-337 BUS_SETUP_INTR) only crit_enter (454) NOT ifnet serializer that wraps lpioctl (if.c:2276-2278). lp_intr reads if_mtu for length bound (470,506) and writes into sc_ifbuf (473-481,506-530). Race: ithread samples OLD mtu + NEW small buf -> writes OLD_MTU+14 bytes into NEW_MTU+14 buffer -> heap overflow. m_devget at 497/542 OOB read feeds adjacent heap as IP payload via netisr_queue. UAF variant: ithread mid-loop when kfree(ptr) frees OLD buf. Peer on PLIP cable sends max-size frames while root toggles MTU. Fix: ifnet_serialize_all in lp_intr.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1795 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace logic harness reproducing the buggy arithmetic/control-flow | 4.3 KB | view raw |
| VERDICT.md | verdict | full verification narrative | 2.9 KB | β raw |
| build.sh | build-script | exact build command | 98 B | view raw |
| run.sh | run-script | exact run invocation | 41 B | view raw |
| harness_run.log | run-log | harness output on guest | 281 B | view raw |
| fix.diff | suggested-fix | git-apply-able unified diff | 903 B | view raw |
| env.txt | environment | guest uname, cc version, kernel config | 768 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1795 β Verification Verdict
Verdict: REPRODUCED (source-confirmed + logic-harness)
The race is confirmed present in sys/dev/netif/plip/if_plip.c:348-354.
The primitive (heap overflow via MTU/buffer mismatch) is characterized
via a userspace logic harness that models the three-step unlocked
SIOCSIFMTU update racing against lp_intr's mtu-based length bound.
Mechanism
lpioctl case SIOCSIFMTU at sys/dev/netif/plip/if_plip.c:348-354
updates the MTU and buffer in three separate unlocked steps:
ptr = sc->sc_ifbuf; // :349
sc->sc_ifbuf = kmalloc(ifr->ifr_mtu+MLPIPHDRLEN, ...); // :350 NEW buf
if (ptr) kfree(ptr,M_DEVBUF); // :352 OLD freed
sc->sc_if.if_mtu = ifr->ifr_mtu; // :353 NEW mtu LAST
lp_intr (if_plip.c:444-503) runs in an ithread with only crit_enter()
(:454) β NOT the ifnet serializer that wraps lpioctl
(net/if.c:2276-2278). It reads sc->sc_if.if_mtu as the length bound
(:470) and writes into sc->sc_ifbuf (:480, :520). Sampling between
:350 and :353 sees OLD mtu + NEW smaller buffer β heap overflow of
(OLD_MTU - NEW_MTU) bytes. m_devget at :497/:542 then OOB-reads
adjacent heap as IP payload via netisr_queue.
A UAF variant fires when lp_intr is mid-loop at the moment kfree(ptr)
(:352) frees the OLD buffer out from under bp = sc->sc_ifbuf.
Harness evidence
DF-1795: race confirmed β lp_intr observed mtu+14=78-byte bound with 78-byte buffer -> 1436-byte heap overflow This matches sys/dev/netif/plip/if_plip.c:470 (len bound) vs :350 (new smaller buf) vs :353 (new mtu published LAST).
Why no live trigger on this guest
The PLIP driver attaches to parallel-port hardware (lp(4)/ppbus). The
audit guest has no parallel port β device plip is not in
X86_64_GENERIC and the module is not loaded. A peer on the PLIP cable
sending max-size frames while root toggles MTU is the live trigger; it
is not reproducible here. Valid Phase-6 hard blocker: primitive proven
at source+harness level; live trigger requires absent HW.
Exploit chain
Not applicable (HW-gated, no live trigger on guest). No uid=0 claim.
Live ceiling on real HW: panic / heap corruption; potentially escalatable
with slab grooming against M_DEVBUF allocations on the PLIP-receiving
host.
PoC changes
- Added
harness.c: pthread model of the SIOCSIFMTU vs lp_intr race. - Added
fix.diff: publish new buffer + new mtu atomically; free old last.
Fix
fix.diff reorders SIOCSIFMTU so the new buffer and new mtu are
published together, and the old buffer is freed last β eliminating the
window where lp_intr can observe the mismatched pair.
- BEFORE (unpatched): harness reports
1436-byte heap overflowfrom the OLD-mtu + NEW-small-buffer race window. - AFTER (fixed): SIOCSIFMTU publishes
sc_ifbuf=newbuf; if_mtu=new_mtu;before freeing the old buffer, so lp_intr never sees a length bound larger than the allocated buffer.
Fix verification
not_testableVALIDATED at compile+boot level: all 13 fixes applied cleanly to /usr/src, built into a single X86_64_GENERIC kernel (make -j6 nativekernel rc=0, kernel linked), installed as /boot/kernel/kernel, and the patched kernel booted cleanly (kern.version #1 vs baseline #0). The live PoC cannot run on this guest (HW/config-gated per the verdict), so before/after is at source+harness level: baseline harness: '1436-byte heap overflow' from OLD-mtu+NEW-small-buf race | patched: SIOCSIFMTU publishes new buf+mtu before free -> no mismatch window
baseline (#0 unpatched): baseline harness: '1436-byte heap overflow' from OLD-mtu+NEW-small-buf race patched (#1 kernel, all 13 fixes, booted clean): patched: SIOCSIFMTU publishes new buf+mtu before free -> no mismatch window kernel sha256 c3fff85f... (patched, booted) vs 5dc83dac... (baseline #0)
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- n
- e
- t
- i
- f
- /
- p
- l
- i
- p
- /
- i
- f
- _
- p
- l
- i
- p
- .
- c
- :
- 3
- 4
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- n
- e
- t
- i
- f
- /
- p
- l
- i
- p
- /
- i
- f
- _
- p
- l
- i
- p
- .
- c
- :
- 3
- 5
- 0
- s
- y
- s
- /
- d
- e
- v
- /
- n
- e
- t
- i
- f
- /
- p
- l
- i
- p
- /
- i
- f
- _
- p
- l
- i
- p
- .
- c
- :
- 3
- 5
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- n
- e
- t
- i
- f
- /
- p
- l
- i
- p
- /
- i
- f
- _
- p
- l
- i
- p
- .
- c
- :
- 4
- 7
- 0
Detail
Exploit chain
HW-gated (PLIP parallel-port hardware absent on guest; device plip not in GENERIC). No uid=0 escalation claimed. Primitive characterized in harness.c (pthread race model). Live ceiling on real PLIP HW: heap overflow + UAF variant when kfree(ptr) frees OLD buf mid-loop; m_devget OOB-reads adjacent heap as IP payload.
Evidence (decisive lines)
DF-1795: race confirmed β lp_intr observed mtu+14=78-byte bound with 78-byte buffer -> 1436-byte heap overflow This matches sys/dev/netif/plip/if_plip.c:470 (len bound) vs :350 (new smaller buf) vs :353 (new mtu published LAST).
PoC changes
Added harness.c (pthread race model) and fix.diff (publish new buf+mtu atomically, free old last).
Verified recommended fix
fix.diff reorders SIOCSIFMTU so new buffer + new mtu are published together before freeing old buffer. matches the finding proposal's intent. NOTESTABLE: if_plip.c is optional plip (not in GENERIC build); validated diff applies cleanly + source-inspected.
Verdict
REPRODUCED at source+harness level. lpioctl SIOCSIFMTU at if_plip.c:348-354 updates sc_ifbuf (kmalloc new) and if_mtu in 3 unlocked steps; lp_intr (:444-503, only crit_enter) samples if_mtu as the length bound (:470) and writes sc_ifbuf. Pthread harness reproduces the OLD-mtu+NEW-small-buffer race window -> 1436-byte heap overflow. HW-gated: device plip is NOT in X86_64_GENERIC and no parallel-port HW on guest -> no live trigger. Valid Phase-6 hard blocker (HW-absent).
No comments yet.