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

if_ic: SIOCSIFMTU frees the working buffers before checking the replacement allocations β€” on kmalloc failure installs NULL ic_obuf/ic_ifbuf while still updating if_mtu

Summary

MTU change kmallocs replacements, kfrees the working pair, installs unchecked (M_WAITOK CAN return NULL for 64KB-class under pressure on DFly), and updates if_mtu - a working interface becomes a DF-2001 NULL-deref panic machine deterministically. Source-proven; PoC declined (forcing kmem exhaustion risks the shared guest). Allocate-first+rollback fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4941 Β· 7 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 3.6 KB ↓ raw
fix.diff β€” 1.4 KB view raw
verdict.json β€” 2.9 KB view raw
lpbbfake.c β€” 3.4 KB view raw
Makefile.harness β€” 102 B ↓ download
build.sh β€” 76 B view raw

DF-4941 β€” if_ic: SIOCSIFMTU frees the working buffers even when the replacement allocation fails (installs NULL ic_obuf/ic_ifbuf)

File: sys/dev/netif/ic/if_ic.c (icioctl SIOCSIFMTU, lines 205–223) Verdict: source-proven, PoC not executed (triggering the allocation failure deterministically requires kernel-memory exhaustion; see VERDICT.md). Fix (fix.diff) authored from the line-accurate root cause, compile-checked against the guest tree, not runtime-validated.

The bug

sc->ic_ifbuf = kmalloc(ifr->ifr_mtu+ICHDRLEN, M_DEVBUF, M_WAITOK);   /* 211 */
sc->ic_obuf  = kmalloc(ifr->ifr_mtu+ICHDRLEN, M_DEVBUF, M_WAITOK);   /* 214 */
if (iptr) kfree(iptr, M_DEVBUF);                                     /* 217 */
if (optr) kfree(optr, M_DEVBUF);                                     /* 220 */
sc->ic_if.if_mtu = ifr->ifr_mtu;                                     /* 222 */

There is no NULL check and no rollback. M_WAITOK is widely assumed to never fail, but on DragonFly that is false for these sizes:

  • the generic SIOCSIFMTU wrapper allows 72 <= ifr_mtu <= 65535 (sys/net/if.c:2272, IF_MINMTU/IF_MAXMTU, sys/net/if_var.h:558-559), so the allocation is up to 65539 bytes;
  • allocations at/above ZoneLimit go to kmem_slab_alloc() which returns NULL on failure even with M_WAITOK (sys/kern/kern_slaballoc.c:942-950);
  • for zone-sized allocations, zone creation failure also unwinds to NULL (kern_slaballoc.c:1070-1072 if (z == NULL) goto fail;).

Under kernel-map pressure, ifconfig ic0 mtu 65535 therefore: 1. kfree()s the working buffer pair (lines 217-220), 2. installs two NULL pointers, 3. still updates if_mtu (line 222) and returns success.

From that moment the interface is armed with the DF-2001 sinks: icintr INTR_RECEIVE *sc->ic_cp++ (line 309) and icoutput's bcopy(..., sc->ic_obuf, ICHDRLEN) (line 358) dereference NULL β€” a deterministic kernel panic (local DoS) on the next traffic or received byte, caused by an ordinary error path rather than the DF-2001 pre-alloc condition.

Fix

Allocate the replacements first with M_NULLOK; on failure free only the failed new allocations and return ENOBUFS, leaving the working pair and the old MTU untouched (fix.diff).

Why not runtime-verified

The failure precondition is kmem exhaustion for a 64 KB-class allocation. Deliberately exhausting kernel memory on the shared audit guest risks wedging unrelated subsystems (and the VM), and the mechanism (source trace above) is deterministic. Recorded as untested rather than risking a low-value DoS of the test environment; the root cause is fully cited.

VERDICT.md
↓ download raw

DF-4941 VERDICT β€” source-proven error-path lifecycle defect; PoC not executed (untested)

Root cause (path:line)

sys/dev/netif/ic/if_ic.c icioctl SIOCSIFMTU (205-223): both replacement buffers are kmalloc'd, the OLD working pair is kfree'd, and if_mtu is updated β€” with no NULL check on either kmalloc result and no rollback.

M_WAITOK does not guarantee success on DragonFly for the sizes reachable here:

  • if.c:2272 admits 72 <= ifr_mtu <= 65535 (IF_MINMTU/IF_MAXMTU, if_var.h:558-559) β†’ allocation size up to 65539 bytes;
  • size >= ZoneLimit β†’ kmem_slab_alloc(size, PAGE_SIZE, flags); on failure the allocator returns NULL even with M_WAITOK (sys/kern/kern_slaballoc.c:942-950);
  • below ZoneLimit, zone creation failure unwinds to NULL as well (kern_slaballoc.c:1070-1072).

Under kernel-map pressure ifconfig ic0 mtu 65535 therefore kfree()s the working pair, installs ic_ifbuf == NULL and ic_obuf == NULL, sets if_mtu = 65535 and reports success. The interface is then armed with the (pass-1, DF-2001) NULL-deref sinks:

  • TX: icoutput if_ic.c:358 bcopy((char *)&hdr, sc->ic_obuf, ICHDRLEN) β†’ NULL write, panic, on the next routed packet;
  • RX: icintr INTR_START caches sc->ic_cp = sc->ic_ifbuf (NULL) and INTR_RECEIVE executes *sc->ic_cp++ = *ptr (if_ic.c:267,309) β†’ NULL write, panic, on the next byte from the i2c master.

Note the RX check sc->ic_xfercnt >= sc->ic_if.if_mtu+ICHDRLEN (305) does NOT protect against this: with the NULL buffers there is nothing to overflow into β€” the write happens at address 0+xfercnt.

Classification and honest status

  • status: untested β€” the failure precondition is kernel-memory exhaustion for a 64 KB-class allocation; forcing it on the shared audit guest risks wedging unrelated subsystems. No PoC was executed.
  • The defect itself is deterministic and fully source-traced; the sinks' behavior (NULL write panic) is established by DF-2001's analysis (same sink lines). Confidence: certain for the code defect, likely for end-to-end impact.
  • Severity: Low β€” privileged trigger (SIOCSIFMTU is SYSCAP_RESTRICTEDROOT-gated, if.c:2265), requires kmem pressure, impact ceiling = local kernel panic (DoS).

Non-overlap

  • DF-2000: SIOCSIFMTU race (serializer vs output/intr on the freed old buffers) β€” different mechanism (no race here; this is the sequential error path).
  • DF-2001: the NULL-deref sinks pre-allocation β€” same sink lines, but this finding is the distinct deterministic path that installs NULLs on a previously-working interface while claiming success.

Fix

fix.diff (authored after source verification, compile-checked style, not runtime-validated β€” fix_status not_tested): allocate replacements with M_WAITOK|M_NULLOK first; on any failure free only the new allocations and return ENOBUFS, leaving the working pair and old MTU intact.

Kernel references

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

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored from line-accurate root cause (allocate-first, ENOBUFS rollback, M_NULLOK); not runtime-validated because the baseline failure path itself could not be triggered without risking the shared guest.

fix.diff
↓ fix.diffper-fix-DF-4941

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md root-cause trace: if_ic.c:211-222 (unchecked kmallocs, free of working pair, mtu update); kern_slaballoc.c:942-950 and 1070-1072 (M_WAITOK NULL paths); if.c:2272 (bounds admitting 65535).

PoC changes

No PoC executed (deliberate): deterministic trigger requires kernel-memory exhaustion; documented as untested with full source trace instead.

Verified recommended fix

Allocate replacement buffers with M_WAITOK|M_NULLOK before freeing the working pair; on failure free only the new allocations and return ENOBUFS without changing if_mtu (fix.diff).

Verdict

Source-proven error-path defect, not executed: SIOCSIFMTU (if_ic.c:205-223) kfree()s the working ic_obuf/ic_ifbuf pair and installs the new kmalloc results without any NULL check or rollback, and still updates if_mtu. DF kmalloc CAN return NULL here despite M_WAITOK (sizes up to 65539 B admitted by if.c:2272; >=ZoneLimit path returns NULL on kmem_slab_alloc failure, kern_slaballoc.c:942-950; zone-create failure likewise, :1070-1072), so under kernel-memory pressure ifconfig ic0 mtu 65535 deterministically leaves the interface with NULL buffers β€” arming the DF-2001 NULL-deref sinks (icoutput:358 bcopy, icintr:309 write) for a panic on the next packet/byte. PoC not executed because the trigger requires kmem exhaustion (risk of wedging the shared guest); root cause fully cited path:line. Fix authored (allocate-first with M_NULLOK, ENOBUFS rollback) but not runtime-validated.