if_ic: icoutput unconditionally forces IFF_RUNNING, bypassing the up state machine (permanently skipped buffer allocation/bus acquisition, TX over un-owned iicbus, spurious bus release)
Summary
The TX path sets IFF_RUNNING while ip_output has no UP gate and routes survive down: one packet to a DOWN ic0 makes it RUNNING-while-DOWN, every later 'up' skips allocation AND bus acquisition, TX runs over a bus it does not own (corrupting the real owner's transfer), and a later 'down' spuriously releases the bus - also sustaining the DF-2001 NULL-deref sinks. REPRODUCED live (unpriv ping -> phantom RUNNING + un-owned TX + skipped alloc); gate-on-UP+RUNNING fix validated (ENETDOWN, proper transitions).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4940 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.7 KB | β raw | |
| VERDICT.md | β | 5.5 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 | β | 348 B | view raw | |
| run.log | β | 2.7 KB | view raw | |
| env.txt | β | 427 B | view raw | |
| fix.diff | β | 662 B | view raw | |
| verdict.json | β | 3.8 KB | view raw |
DF-4940 β if_ic: icoutput forces IFF_RUNNING and transmits without the up-path state machine
File: sys/dev/netif/ic/if_ic.c (icoutput, line 347)
Verdict: REPRODUCED on the QEMU guest (state-machine corruption with
two security-relevant consequences, both demonstrated), fix VALIDATED.
The bug
icoutput() begins with an unconditional ifp->if_flags |= IFF_RUNNING;
(if_ic.c:347). ip_output() calls ifp->if_output() with no IFF_UP gate
(sys/netinet/ip_output.c:698,742), and if_down() deletes no routes
(sys/net/if.c:1818βif_down_dispatch, which only clears IFF_UP, purges the
ifq and purges cloned routes β sys/netinet/in.c:1450 in_if_down). So any
routed packet on a down ic0 executes the transmit path on an interface
that never ran the up branch. Consequences:
ic0is flagged RUNNING while DOWN. The next SIOCSIFFLAGS "up" seesIFF_UP && !IFF_RUNNINGfalse (if_ic.c:188) and therefore skips both the buffer allocation and iicbus_request_bus() forever β on a never-allocated interface this sustains the DF-2001 NULL-deref state;- the packet is transmitted over the iicbus without
if_icowning the bus (ownership is only taken in icioctl; iicbus data transfers do not checksc->ownerβ sys/bus/iicbus/iiconf.c:298-311), corrupting any transfer in flight by the actual bus owner; - a subsequent SIOCSIFFLAGS "down" calls
iicbus_release_bus()for a busif_icno longer owns; the parent-level release callback fires before the ownership check (iiconf.c:131-140) β a spurious controller-level release (benign with the stock null callback, but wrong).
Prerequisite
Same virtual-I2C-bridge harness as DF-4939 (lpbbfake.c, included) β no
I2C hardware needed.
Build / Run
sh build.sh # builds lpbbfake.ko on the guest sh run.sh
Expected (unfixed kernel)
after ifconfig ic0 down: flags=808810<POINTOPOINT,...> (no RUNNING) ping -c1 10.99.99.2 (TX while DOWN β netstat shows Opkts=1) after ping: flags=808850<POINTOPOINT,RUNNING,...> (RUNNING forced while DOWN!) ifconfig ic0 up -> allocates NOTHING (devbuf 819 -> 819) [control: normal down/up] -> allocates 2 buffers (devbuf 819 -> 821)
With fix.diff (patched if_ic.ko)
ping -c1 10.99.99.2 -> "sendto: Network is down" (ENETDOWN) flags after ping: 808810 (no phantom RUNNING) ifconfig ic0 up -> flags 808851 (proper UP+RUNNING); buffers reused (no leak, by DF-4939's fix)
Fix
Delete the forced ifp->if_flags |= IFF_RUNNING; and refuse to transmit
unless the interface completed the up branch (UP+RUNNING and non-NULL
buffers) β see fix.diff.
DF-4940 VERDICT β REPRODUCED (state-machine bypass in icoutput; contributes to DoS conditions)
Claim
sys/dev/netif/ic/if_ic.c:347: icoutput() unconditionally executes
ifp->if_flags |= IFF_RUNNING;. Because (a) ip_output invokes
ifp->if_output() with no IFF_UP gate (sys/netinet/ip_output.c:698,742),
(b) if_down leaves routes installed (if.c:1818 β if_down_dispatch if.c:1730
only clears IFF_UP/purges ifq; in_if_down (in.c:1450) only purges cloned
routes), and (c) the generic SIOCSIFFLAGS wrapper discards the driver's
error return (if.c:2167-2171 ifp->if_ioctl(ifp, cmd, data, cred); β value
dropped) β a single transmitted packet on a down (or never-properly-up)
ic0 corrupts the interface state machine:
- RUNNING is set without
iicbus_request_bus()and without buffer allocation (the up branch at if_ic.c:188-202 is then skipped forever because it requires!IFF_RUNNING); - the transmit itself runs on an iicbus the driver does not own (data
transfers never check
sc->owner, iiconf.c:298-311 β only intr dispatch does, iiconf.c:50-51); - a later SIOCSIFFLAGS down issues a spurious
iicbus_release_bus()whose parent callback fires before the ownership check (iiconf.c:131-140).
On a system whose first "up" failed (bus busy: the wrapper still left
IFF_UP set, if.c:2159-2160, and if_ic's own SIOCSIFADDR pre-sets IFF_UP at
if_ic.c:176 before the fall-through), the forced RUNNING locks in the
NULL ic_obuf/ic_ifbuf state β i.e. it permanently sustains the
DF-2001 NULL-deref sinks (icoutput:358 bcopy, icintr:309 write). The NULL
dereference itself is DF-2001 (pass 1, not re-reported here); this finding
is the state-machine defect that creates and preserves the precondition
through an ordinary data path.
Reproduction (guest, 2026-09-25, stock kernel)
Same harness as DF-4939 (lpbbfake.c β lpbb0 β iicbb0 β iicbus0 β ic0); transcript in run.log:
ifconfig ic0 10.99.99.1 10.99.99.2 netmask ... up # proper up, buffers allocated ifconfig ic0 down ifconfig ic0 | head -1 ic0: flags=808810<POINTOPOINT,SIMPLEX,MULTICAST,PROXY> ... # no RUNNING ping -c 1 -t 3 10.99.99.2 1 packets transmitted, 0 received (100% loss) # TX executed netstat -I ic0 -> Opkts 1 # icoutput ran while DOWN ifconfig ic0 | head -1 ic0: flags=808850<POINTOPOINT,RUNNING,...> # RUNNING FORCED while DOWN vmstat -m; ifconfig ic0 up; vmstat -m devbuf 819 -> 819 # allocation SKIPPED (up branch bypassed) # control (no intervening TX): ifconfig ic0 down; up devbuf 819 -> 821 # normal up allocates 2 buffers
Every element of the claim observed: phantom RUNNING while DOWN (flags 808810 β 808850), skipped allocation on the following up (819β819 vs control 819β821), and the transmit itself counted (Opkts=1) β i.e. it went out over an un-owned bus.
Impact
The demonstrated impact is kernel state corruption reachable from an unprivileged packet send once an administrator has configured the interface (or left it half-configured): phantom RUNNING, permanently skipped allocation/bus-acquisition, transmission without bus ownership (frame corruption against the real bus owner), spurious controller-level bus release. Combined with the (known, DF-2001) missing NULL guards it sustains a kernel NULL-deref panic state; combined with DF-4939's lifecycle it also interacts with the leak. Rated Low overall (needs if_ic hardware presence and admin configuration; direct memory-safety sinks are pass-1 findings), classified impact=dos for its ceiling.
Fix validation
fix.diff removes the forced flag and gates icoutput on
UP|RUNNING + non-NULL buffers. Patched if_ic.ko (built together with the
DF-4939 fix in one module; the two observables are independent) installed
and rebooted:
ifconfig ic0 down; ping -c1 10.99.99.2 ping: sendto: Network is down # ENETDOWN, packet dropped ifconfig ic0 | head -1 ic0: flags=808810<...> # NO phantom RUNNING ifconfig ic0 up ic0: flags=808851<UP,POINTOPOINT,RUNNING,...> # proper transition restored
Bad behavior GONE (transcript: run.log FIX section).
Kernel references
- sys/dev/netif/ic/if_ic.c:347 (forced IFF_RUNNING)
- sys/dev/netif/ic/if_ic.c:188-202 (up branch skipped once RUNNING is set)
- sys/dev/netif/ic/if_ic.c:176 (SIOCSIFADDR pre-sets IFF_UP before fall-through)
- sys/netinet/ip_output.c:698,742 (if_output called with no IFF_UP gate)
- sys/net/if.c:2134-2175 (wrapper pre-sets flags and discards driver error)
- sys/net/if.c:1730-1768 (if_down_dispatch deletes no routes)
- sys/netinet/in.c:1449-1453 (in_if_down purges cloned routes only)
- sys/bus/iicbus/iiconf.c:125-148 (release: parent callback before ownership check), :44-54 (intr dispatch to owner only)
Non-overlap statement
DF-2000 = SIOCSIFMTU free-vs-use race; DF-2001 = NULL derefs at icoutput:358 / icintr:309 pre-allocation (sink). This finding is the distinct icoutput state-machine defect (source line 347) that manufactures and preserves the conditions those sinks need; the sinks are cited, not re-claimed.
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: ping-while-down returns ENETDOWN, no phantom RUNNING flag (808810 stays), subsequent 'up' performs the proper transition (808851) with buffers reused (no leak). Baseline bad behaviors all absent.
run.log FIX VALIDATION section (fixB.step1-4)
Confirmed kernel references
Detail
Exploit chain
admin brings ic0 up with address (or half-configures it: SIOCSIFADDR/SIOCSIFFLAGS wrapper leaves IFF_UP set even when the driver's bus request fails, if.c:2159-2171, if_ic.c:176) -> any unprivileged user sends a packet routed via ic0 while it is DOWN -> icoutput sets IFF_RUNNING -> every future 'ifconfig ic0 up' skips allocation+bus acquisition -> on a never-allocated interface ic_obuf/ic_ifbuf stay NULL and every subsequent packet hits the bcopy NULL deref at if_ic.c:358 (sink owned by DF-2001) -> kernel panic (local DoS); additionally each such TX corrupts any in-flight transfer of the actual iicbus owner.
Evidence (decisive lines)
run.log B.step3: flags 808850 (RUNNING) on a down interface after ping (Opkts=1); B.step5: up allocates nothing (devbuf 819->819) vs control 819->821 (+2); FIX section: 'sendto: Network is down', flags stay 808810, proper 808851 after up.
PoC changes
New pass-2 finding, no seed PoC. Reproduction required the virtual-I2C-bridge harness (lpbbfake.ko, shared with DF-4939) and a destination address for the point-to-point interface. TX-while-down is reached via the surviving connected route (in_if_down purges cloned routes only).
Verified recommended fix
Remove the unconditional ifp->if_flags |= IFF_RUNNING in icoutput; refuse to transmit unless (if_flags & (IFF_UP|IFF_RUNNING)) == both and buffers are non-NULL (fix.diff).
Verdict
REPRODUCED: icoutput() unconditionally sets IFF_RUNNING (if_ic.c:347), so a single packet routed to a DOWN (or never-properly-up) ic0 β ip_output has no IFF_UP gate (ip_output.c:698,742) and if_down deletes no routes β flips the interface to RUNNING without iicbus_request_bus() and without buffer allocation. Demonstrated on the guest: flags 808810 -> 808850 (RUNNING while DOWN) after one ping, Opkts=1 (transmit over un-owned bus), and the following SIOCSIFFLAGS 'up' allocated nothing (devbuf 819->819) while a control cycle allocated 2 (819->821). This permanently skips the up branch (if_ic.c:188) and thereby sustains the DF-2001 NULL-deref state; also causes un-owned bus transmissions and spurious iicbus release (parent callback fires before ownership check, iiconf.c:131-140). Fix (gate icoutput on UP|RUNNING + non-NULL buffers, drop the forced flag) validated: patched module returns ENETDOWN, no phantom RUNNING, proper up transition restored.
No comments yet.