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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1795 Β· 9 files
FileTypeDescriptionSize
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
VERDICT.md verdict full verification narrative
↓ download 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 overflow from 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_testable
baseline reproduced→ patch + rebuild →patched clean

VALIDATED 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)
↓ fix.diffDragonFly 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Mon Jul 20 19:12:20 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

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).