[CROSS-FILE: if_lagg.c via ether_reinput_oncpu] lagg_input() reinputs a consumed NULL mbuf under IFF_MONITOR: deterministic first-packet NULL-deref panic (write to 0xa0) on any IFF_UP|IFF_MONITOR lagg
Summary
lagg_input() frees the received mbuf and sets m=NULL when the lagg master is in IFF_MONITOR mode, then falls through inside 'if (m != NULL)' to ether_reinput_oncpu(scifp, NULL, REINPUT_RUNBPF). ether_reinput_oncpu accepts no NULL: with the master IFF_UP it executes m->m_pkthdr.rcvif = ifp on a NULL mbuf. Enslaving sets the slave's if_type = IFT_IEEE8023ADLAG, which diverts ALL slave RX to lagg_input before any L3 processing - so ANY received frame (ambient broadcast suffices) panics the kernel on the first packet once a legitimate admin runs 'ifconfig laggX up monitor'. VERIFIED 2/2 on stock guest: 'Fatal trap 12, fault vaddr 0xa0, Stopped at ether_reinput_oncpu+0x1a: movq %rdi,0xa0(%rsi)' - exact predicted instruction. Privileged one-time setup (create lagg + enslave + monitor up - a normal traffic-observation mode), then unauthenticated remote trigger (single frame on any slave; also locally via /dev/tapN write). Fixed-address write of a kernel pointer to unmapped page 0 - clean DoS, no controllable primitive. Distinct from DF-0367 (unlocked ifp->if_lagg deref). Fix validated (return in the monitor-discard branch + defensive NULL guard in ether_reinput_oncpu): baseline panics, patched clean.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3387 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | β | 3.5 KB | view raw | |
| build.sh | β | 103 B | view raw | |
| run.sh | β | 122 B | view raw | |
| build.log | β | 643 B | view raw | |
| run.log | β | 200 B | view raw | |
| run.2.log | β | 236 B | view raw | |
| run.patched.log | β | 451 B | view raw | |
| panic.txt | β | 2.1 KB | view raw | |
| panic.2.txt | β | 1.2 KB | view raw | |
| env.txt | β | 418 B | view raw | |
| fix.diff | β | 818 B | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| verdict.json | β | 4.1 KB | view raw | |
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 4.7 KB | β raw |
DF-3387 β lagg_input() NULL-mbuf reinput panic (cross-file: sys/net/lagg/if_lagg.c via ether_reinput_oncpu contract)
Summary
lagg_input() (sys/net/lagg/if_lagg.c:1462-1471) frees the received mbuf and
sets m = NULL when the lagg master interface is in IFF_MONITOR mode β then
falls through and calls ether_reinput_oncpu(scifp, NULL, REINPUT_RUNBPF)
inside the if (m != NULL) block. ether_reinput_oncpu()
(sys/net/if_ethersubr.c:1303-1333) has no NULL check; with the lagg master
IFF_UP it dereferences m->m_pkthdr.rcvif (write of ifp to fixed offset
0xa0 of a NULL mbuf) β Fatal trap 12, page fault at vaddr 0xa0.
Deterministic on the FIRST packet received on any slave of an
IFF_UP|IFF_MONITOR lagg. Setup is privileged (ifconfig lagg0 create,
laggproto failover, laggport <slave>, ifconfig lagg0 up monitor); the
trigger is ordinary received traffic β broadcast noise on any slave is enough,
so once configured the panic is remotely triggerable by any host on the
network.
Reproduce (on the DragonFly guest)
# build cc -O -g -Wall -o /root/df3387_trigger trigger.c # run (root) /root/df3387_trigger
Expected (baseline, stock kernel)
Serial console / vm.sh log:
[df3387] + ifconfig lagg0 up monitor -> rc=0 Fatal trap 12: page fault while in kernel mode cpuid = 0; lapic id = 0 fault virtual address = 0xa0 fault code = supervisor write data, page not present current process = Idle kernel: type 12 trap, code=2 Stopped at ether_reinput_oncpu+0x1a: movq %rdi,0xa0(%rsi) db>
0xa0(%rsi) with rsi == NULL is exactly m->m_pkthdr.rcvif = ifp;
(if_ethersubr.c:1320) β write-only to a fixed near-NULL address, i.e. a clean
panic, not controllable corruption.
Fixed (fix.diff applied, rebuilt kernel)
Trigger runs to completion: all eight steps succeed, the injected frame is
discarded by the monitor path, the program prints NO PANIC β bug did not
reproduce, destroys lagg0/tap0 and exits 2. Guest stays up.
Files
trigger.cβ setup + RX injection (write(2) to /dev/tapN is an RX inject: tapwrite() βifp->if_input(ifp, top, NULL, -1)β ether_input)build.sh,run.shβ exact commandsbuild.logβ compile output (also kernel-build tail for the fix run)run.log,run.2.logβ baseline runs (panic)run.patched.logβ fix-validation run (no panic)panic.txt,panic.2.txtβ full serial-console panics (baseline + 2nd run)env.txtβ guest identityfix.diffβ git-apply-able two-hunk fix (if_lagg.c primary, if_ethersubr.c defensive)manifest.json,verdict.json
DF-3387 β VERDICT
Status: reproduced β deterministic kernel NULL-deref panic, exact predicted instruction. Impact: dos (panic) β write-only fault at fixed vaddr 0xa0, no controllable primitive. Fix: validated (fixed) β patched kernel boots, same PoC harmlessly discards the frame, guest stays up.
Root cause (line-accurate)
sys/net/lagg/if_lagg.c:1462-1471:
if (m != NULL) {
IFNET_STAT_INC(ifp, ipackets, 1);
IFNET_STAT_INC(ifp, ibytes, m->m_pkthdr.len);
if (scifp->if_flags & IFF_MONITOR) {
m_freem(m);
m = NULL; /* <-- m consumed */
}
ether_reinput_oncpu(scifp, m, REINPUT_RUNBPF); /* <-- NULL passed */
}
sys/net/if_ethersubr.c:1303-1321 (ether_reinput_oncpu) accepts no NULL β
first deref with the lagg master IFF_UP:
if ((reinput_flags & REINPUT_KEEPRCVIF) == 0 ||
m->m_pkthdr.rcvif == NULL) /* m == NULL -> fault at 0xa0 */
{
m->m_pkthdr.rcvif = ifp;
Attack chain (all reproduced on the guest):
ifconfig tap0 create && ifconfig tap0 upifconfig lagg0 create && ifconfig lagg0 laggproto failover && ifconfig lagg0 laggport tap0β enslaving sets the slave'sif_type = IFT_IEEE8023ADLAG(if_lagg.c:674), which routes its RX to the lagg branch ofether_demux_oncpu(if_ethersubr.c:1076) β before any L3 processing, so frame content beyond the 14-byte header is irrelevant.ifconfig lagg0 up monitorβ master getsIFF_UP|IFF_MONITOR.- One frame received on tap0.
write(2)to/dev/tap0is an RX injection:tapwrite()(sys/net/tap/if_tap.c) ends inifp->if_input(ifp, top, NULL, -1)βether_inputβether_input_handlerβether_input_oncpuβether_demux_oncpuβlagg_inputβ failover input returns the mbuf (tap0 is primary) β IFF_MONITOR branch NULLs it βether_reinput_oncpu(scifp, NULL, ...). Fatal trap 12, fault vaddr0xa0, stopped atether_reinput_oncpu+0x1a: movq %rdi,0xa0(%rsi)βm == NULL (rsi), the write ofifptom->m_pkthdr.rcvif.
Runs: 2/2 identical panics (run.log/run.2.log, panic.txt/panic.2.txt). The
ssh-side write() return line is lost both times because the panic freezes
the guest before sshd flushes; the serial-console mirror
([df3387] ... up monitor -> rc=0) pins the sequence β the only action the
program takes after that line is open("/dev/tap0") + write(fd, frame, 34).
Threat model
- Setup is privileged (ifconfig). After setup, the trigger is any received frame on any slave β ambient broadcast traffic suffices; a remote attacker on the L2 segment panics the box with a single packet.
- The faulting instruction writes
ifpto fixed address0xa0β not attacker-controlled, page 0 unmapped β clean DoS panic, no escalation path.
Why pass-1 missed it / where it sits
This is a callsite violation of the ether_reinput_oncpu contract
documented in the audited file (if_ethersubr.c:1303). The other four reinput
callers (vlan if_vlan.c:630, carp ip_carp.c:1231/1253, bridge
if_bridge.c:2880/2987) all pass non-NULL mbufs β lagg is the outlier.
DF-0367 covers a different bug in lagg_input (unlocked ifp->if_lagg deref,
lines 1442-1456); the NULL reinput at 1466-1470 is distinct and was unfiled.
Fix validation
fix.diff (two hunks: primary return in if_lagg.c after monitor-discard;
defensive NULL guard at the top of ether_reinput_oncpu):
- Baseline (stock #0 kernel): panic as above β reproduced before patching.
- Patched: applied in-guest to
/usr/src,make -j8 nativekernel KERNCONF=X86_64_GENERIC+make installkernel, rebooted into#1kernel. Same PoC: all 8 steps rc=0, frame discarded by the monitor path,NO PANIC β bug did not reproduce, interfaces cleanly destroyed, guest stays up (vm.sh status= up, ssh alive). See run.patched.log + build.log. - Guest then reset to the clean snapshot (
vm.sh reset with-src).
Notes
ether_reinput_oncpu's only other NULL hazard (m_freem(NULL)on a !IFF_UP interface) is harmless βm_freem()iswhile (m)-guarded (sys/kern/uipc_mbuf.c:1469). The deref at :1318-1320 is the crash.- Could an unprivileged user reach this without root setup? No β SIOCSLAGG and SIOCSIFFLAGS are privileged; the finding is privileged-config + remote/unprivileged-trigger, same profile as DF-0445 (Medium).
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
fixedfix.diff applied in-guest to /usr/src, make -j8 nativekernel KERNCONF=X86_64_GENERIC + make installkernel, rebooted into #1. Exact PoC re-run: frame harmlessly discarded by the monitor path, 'NO PANIC - bug did not reproduce' printed, lagg0/tap0 destroyed cleanly, guest remained up and ssh-able. Baseline panicked 2/2 on the same input.
run.patched.log (full PoC output on patched kernel), build.log (kernel build tail + uname)
Confirmed kernel references
Detail
Exploit chain
ifconfig tap0 create/up; ifconfig lagg0 create; ifconfig lagg0 laggproto failover; ifconfig lagg0 laggport tap0 (slave if_type becomes IFT_IEEE8023ADLAG -> RX diverted to lagg_input at if_ethersubr.c:1076 before L3); ifconfig lagg0 up monitor; write 34 bytes (bcast ether + minimal IPv4) to /dev/tap0 -> deterministic panic on the first received packet. Remote variant: once configured, any frame arriving on any slave (e.g. ambient broadcast) panics the kernel.
Evidence (decisive lines)
["panic.txt / panic.2.txt: 'Fatal trap 12 ... fault virtual address = 0xa0 ... Stopped at ether_reinput_oncpu+0x1a: movq %rdi,0xa0(%rsi)'", 'run.2.log: all 7 ifconfig steps rc=0, connection dropped immediately after (write() executed, output lost to the panic)', "panic.2.txt serial mirror: '[df3387] + ifconfig lagg0 up monitor -> rc=0' directly followed by the panic", 'fix.diff: two hunks (if_lagg.c return-after-monitor-discard; if_ethersubr.c defensive NULL guard)', "run.patched.log + build.log: patched kernel #1, same PoC -> 'NO PANIC - bug did not reproduce', guest stays up"]
PoC changes
Seed (none existed for this bug; written fresh). Frame content beyond the 14-byte header is irrelevant since the IFT_IEEE8023ADLAG branch in ether_demux_oncpu runs before any L3 processing; a 34-byte bcast/ETHERTYPE_IP frame is used for realism. Progress lines mirrored to /dev/console because the panic eats unflushed ssh output.
Verified recommended fix
if_lagg.c: return immediately after the IFF_MONITOR discard (do not reinput a consumed mbuf); if_ethersubr.c: defensive NULL check at top of ether_reinput_oncpu.
Verdict
Reproduced 2/2 on the stock INVARIANTS guest: lagg_input() (sys/net/lagg/if_lagg.c:1466-1470) frees the mbuf and NULLs it under IFF_MONITOR, then falls through to ether_reinput_oncpu(scifp, NULL, REINPUT_RUNBPF); with the lagg master IFF_UP, ether_reinput_oncpu (sys/net/if_ethersubr.c:1317-1320) writes ifp to m->m_pkthdr.rcvif of a NULL mbuf -> Fatal trap 12 at vaddr 0xa0, Stopped at ether_reinput_oncpu+0x1a: movq %rdi,0xa0(%rsi). Trigger is a single frame received on any slave (write(2) to /dev/tapN = RX injection via tapwrite -> if_input); setup is privileged ifconfig. Write-only fault at fixed near-NULL address: clean DoS panic, no controllable primitive, no escalation path.
No comments yet.