if_ic: every SIOCSIFFLAGS downβup cycle leaks the ic_obuf/ic_ifbuf pair (unbounded M_DEVBUF kernel heap leak)
Summary
The down branch never frees/NULLs the buffers and the up branch unconditionally re-kmallocs - each cycle orphans 2x(mtu+ICHDRLEN) bytes. REPRODUCED 300/300 on guest via a virtual-I2C-bridge KLD harness + stock ifconfig: devbuf +2 allocs/+3008B per cycle exactly (mtu 65535 = ~128KB/cycle); NULL-guard reuse fix validated byte-flat on rebuilt if_ic.ko. Race-free, distinct from DF-2000/2001.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4939 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 3.9 KB | β raw | |
| lpbbfake.c | β | 3.4 KB | view raw | |
| Makefile.harness | β | 102 B | β download | |
| build.sh | β | 108 B | view raw | |
| run.sh | β | 90 B | view raw | |
| build.log | β | 1.9 KB | view raw | |
| run.log | β | 2.8 KB | view raw | |
| env.txt | β | 596 B | view raw | |
| fix.diff | β | 780 B | view raw | |
| verdict.json | β | 3.2 KB | view raw |
DF-4939 β if_ic: unbounded kernel heap leak on every SIOCSIFFLAGS downβup cycle
File: sys/dev/netif/ic/if_ic.c (icioctl, lines 179β202)
Verdict: REPRODUCED (leak) on the QEMU guest, DragonFly 6.5-DEVELOPMENT
X86_64_GENERIC, using a virtual-I2C-bridge KLD harness (lpbbfake.c, included).
Fix VALIDATED: patched if_ic.ko shows a byte-flat M_DEVBUF budget across
the same 200-cycle workload that leaked ~600 KB unfixed.
Prerequisite (no I2C hardware needed)
The ic driver only attaches as a child of iicbus, which needs an I2C
master controller. The harness KLD registers a fake lpbb bit-bang bridge on
ISA with virtual lines (SDA always-ACK, SCL always-high), producing
lpbb0 β iicbb0 β iicbus0 β ic0 on any machine.
Build
# on the guest (root): mkdir -p /root/harness && cd /root/harness cp <pack>/lpbbfake.c <pack>/Makefile.harness ./Makefile # see Makefile.harness make # -> lpbbfake.ko
Run
./run.sh
which performs, in order:
kldload lpbbfake.ko; kldload iicbb; kldload iicbus; kldload if_icifconfig ic0 10.99.99.1 10.99.99.2 netmask 255.255.255.252 up- baseline
vmstat -m(M_DEVBUF row) - 200 Γ (
ifconfig ic0 down; ifconfig ic0 up) vmstat -magain β compare M_DEVBUF InUse count/KB
Expected (unfixed kernel)
devbuf 219 214K (before) devbuf 619 814K (after 200 cycles) <- +400 allocs, +600K (2 x 1504 B/cycle)
Second round of 100 cycles adds another +200 allocs / +300K (run.2.log) β
the leak is unbounded and linear in the number of cycles. With
ifr_mtu = 65535 (max allowed by if.c:2272), each cycle leaks 2 Γ 65539 B,
so a root shell looping ifconfig ic0 down; ifconfig ic0 up exhausts kernel
heap (local DoS / memory-pressure amplifier; SIOCSIFFLAGS requires
SYSCAP_RESTRICTEDROOT, if.c:2135).
Fix validation (fix.diff)
Apply fix.diff to a guest copy of /usr/src/sys/dev/netif/ic/if_ic.c,
rebuild if_ic.ko, reboot, repeat step 3β5: M_DEVBUF stays 219 / 214K β
219 / 214K (byte-flat). Observed and recorded in run.log (section
FIX-CYCLES-DONE).
Root cause
icioctl SIOCSIFFLAGS down-branch (if_ic.c:179β187) releases the iicbus and
clears IFF_RUNNING but never frees (nor NULLs) sc->ic_obuf/sc->ic_ifbuf.
The up-branch (if_ic.c:188β202) unconditionally kmallocs two fresh buffers
and overwrites the pointers, orphaning the previous pair. Repeat forever.
DF-4939 VERDICT β REPRODUCED (kernel heap leak, unbounded, privileged trigger)
What was claimed
sys/dev/netif/ic/if_ic.c icioctl() SIOCSIFFLAGS: the down branch
(if_ic.c:179-187) never frees sc->ic_obuf / sc->ic_ifbuf, and the up
branch (if_ic.c:193-197) unconditionally re-kmallocs both, overwriting the
pointers. Every downβup cycle leaks 2 Γ (if_mtu + ICHDRLEN) bytes of
M_DEVBUF. Unbounded: a privileged loop (while :; do ifconfig ic0 down;
ifconfig ic0 up; done) exhausts the kernel heap.
How it was reproduced (guest, 2026-09-25)
Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026,
X86_64_GENERIC, clean-source snapshot; sys/dev/netif/ic/if_ic.c sha256
3ff9e187... (identical to host audit tree).
No I2C controller exists in QEMU, so the ic driver (child of iicbus) cannot
attach on stock hardware. A test-harness KLD (lpbbfake.c in this pack)
registers a driver named "lpbb" on the ISA bus β the exact bus name iicbb
attaches to (iicbb.c:444 DRIVER_MODULE(iicbb, lpbb, ...)) β and implements
the iicbb_if bit-bang methods against virtual lines (SDA reads 0 = ACK,
SCL reads 1). This yields lpbb0 β iicbb0 β iicbus0 β ic0 with no
hardware. All subsequent exercise of the bug is via stock ifconfig(8)
only; the harness never touches if_ic internals.
Observed (full transcript in run.log):
kldload lpbbfake.ko, iicbb, iicbus, if_ic -> ic0 attached ifconfig ic0 10.99.99.1 10.99.99.2 netmask 255.255.255.252 up vmstat -m : devbuf 219 214K (baseline) 200 x (ifconfig ic0 down && ifconfig ic0 up) vmstat -m : devbuf 619 814K (+400 allocs, +600 KB)
+400 allocations = exactly 2 per cycle (ic_obuf + ic_ifbuf), +600 KB β 200 Γ 2 Γ 1504 B. run.2.log: a further 100 cycles β 819 allocs / 1.09M (+200, +300 KB): linear, unbounded, deterministic (not a race β 300/300 cycles leaked).
Impact
Kernel memory exhaustion DoS (VM pressure, allocation failures system-wide) driven by an otherwise-inert config loop. Trigger is privileged (SIOCSIFFLAGS is gated by caps_priv_check SYSCAP_RESTRICTEDROOT, sys/net/if.c:2135) and requires an if_ic instance (I2C-attached system, or the same virtual-bridge technique if an attacker can load modules). With if_mtu at the generic max 65535 (if.c:2272), the leak rate is ~128 KB/cycle. Severity Low (privileged trigger, leak class).
Non-overlap: DF-2000 (SIOCSIFMTU free-vs-use UAF race) and DF-2001 (NULL deref pre-alloc) both concern the same buffers but different defects; this finding is the steady-state lifecycle leak, no race involved.
Fix validation
fix.diff (authored after reproduction): up-branch allocates only when the
respective pointer is NULL (buffers retained across down are reused).
Applied to the guest's /usr/src copy, if_ic.ko rebuilt (build.log.fix),
installed to /boot/kernel, guest rebooted, identical 200-cycle workload:
vmstat -m : devbuf 219 214K (before) vmstat -m : devbuf 219 214K (after 200 cycles) <- byte-flat
Bad behavior GONE. Guest then reset to clean-source snapshot.
Kernel references
- sys/dev/netif/ic/if_ic.c:179-187 (down: no free, no NULL)
- sys/dev/netif/ic/if_ic.c:193-197 (up: unconditional re-kmalloc)
- sys/net/if.c:2134-2175 (generic SIOCSIFFLAGS wrapper, privilege + flag pre-set)
- sys/kern/kern_slaballoc.c (M_DEVBUF zone stats backing vmstat -m)
Observations, limitations
- The one-off shutdown hang noted during teardown (vm.sh down after heavy cycling, console log overwritten by the next boot) was NOT characterized and is NOT counted as part of this finding.
- Leak is M_DEVBUF-typed;
vmstat -mInUse count/KB used as the observable.
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
fixedPatched if_ic.ko (fix.diff) rebuilt and installed on the guest; the exact baseline workload (200 down/up cycles) no longer leaks: M_DEVBUF InUse stays 219 allocs / 214K before and after (baseline unfixed: +400 allocs / +600K).
run.log FIX VALIDATION section (FIX-CYCLES-DONE); build.log.fix note in build.log
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log: baseline devbuf 219/214K -> 619/814K after 200 cycles -> 819/1.09M after +100 (exactly 2 allocs per cycle); FIX section of run.log: patched module byte-flat 219/214K -> 219/214K across the same 200 cycles. Harness lpbbfake.c + Makefile.harness attach ic0 with no i2c hardware.
PoC changes
Pass-1-style PoC sketch did not exist for this finding (new pass-2 finding). Had to invent the reproduction environment: guest has no I2C controller, so a virtual 'lpbb' bit-bang bridge KLD (lpbbfake.c) was written to instantiate lpbb->iicbb->iicbus->ic0. Also: ifconfig requires a destination address for the point-to-point ic0 ('ifconfig ic0 A B netmask ... up').
Verified recommended fix
In icioctl SIOCSIFFLAGS up-branch, allocate ic_obuf/ic_ifbuf only if the pointer is NULL (reuse the pair retained across down), per fix.diff.
Verdict
REPRODUCED: every SIOCSIFFLAGS down->up cycle on if_ic leaks 2 x (if_mtu+4) bytes of M_DEVBUF because the down branch (if_ic.c:179-187) never frees ic_obuf/ic_ifbuf and the up branch (if_ic.c:193-197) unconditionally re-kmallocs both. Demonstrated on the QEMU guest with a virtual-I2C-bridge harness KLD: 300/300 cycles leaked, M_DEVBUF grew 219->619->819 allocs (214K->814K->1.09M) for 200+100 cycles, exactly +2 allocs/+3008B per cycle. Deterministic, unbounded, privileged trigger (root loop can exhaust kernel heap). Fix (allocate-only-if-NULL in the up branch) validated by rebuilding if_ic.ko on the guest: same 200-cycle workload is byte-flat (219/214K -> 219/214K).
No comments yet.