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

Unlocked/wrong-lock readers of the port list vs lagg_port_destroy: unlocked lagg_sysctl_active walk, and port if_ioctl/if_output/if_lagg torn down without the port serializer

Summary

Three readers race lagg_port_destroy: (a) lagg_sysctl_active walks sc_ports with NO lock whatsoever - any concurrent port add/destroy yields a walk over a freed lagg_port, and the sysctl is readable by any unprivileged user (0444). (b) lagg_port_destroy restores if_type/if_ioctl/if_output/if_lagg holding only the lagg WLock + global ifnet_lock, NOT the port ifnet's serializer, while lagg_port_ioctl asserts its caller holds that serializer: an ioctl that loaded if_ioctl before the swap can read if_lagg after it is NULLed -> lp stays NULL -> fallback executes 'if (lp->lp_ioctl != NULL)' -> guaranteed NULL-deref panic (unpriv SIOCGIFMEDIA suffices); or reads a stale lp after kfree -> call through freed memory. (c) lagg_port_output reads ifp->if_lagg with no NULL check and calls lp->lp_output - same window -> NULL function-pointer call. Beyond DF-0369 (discarded return only), the fallback is reachable with lp == NULL. Unverified (speculative timing; mechanism certain). Fix: RLOCK the sysctl walk; restore if_lagg BEFORE if_ioctl/if_output/if_type; NULL-check lp in the fallback/output. [VERIFIED pass2: unlocked sysctl walk vs port teardown - 4000x300 race iterations no crash; mechanism line-confirmed, window needs in-flight traffic; RLOCK fix compile-validated]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3348 Β· 5 files
FileTypeDescriptionSize
README.md β€” 854 B ↓ raw
VERDICT.md β€” 2.0 KB ↓ raw
lagg_race.sh β€” 647 B view raw
manifest.json β€” 722 B view raw
verdict.json β€” 1.5 KB view raw

DF-3348 -- verification pack

Method: Code-line analysis + attempted live reproduction Status: NOT REPRODUCED | impact: none | confidence: likely

What this is

lagg_sysctl_active walks sc_ports with no LAGG lock (if_lagg.c:1620-1638) and lagg_port_destroy swaps if_ioctl/if_output/if_lagg without the port serializer. Race attempted live: 4000 sysctl walks against 300 port add/remove cycles - no crash (the freed lp is slab-contained and the window needs packet traffic in flight during teardown). Mechanism line-confirmed.

Build

cc -O -o <prog> <prog>.c   (on the guest)

Run

(driver script)

(success criterion: see VERDICT.md)

Evidence

run log in VERDICT.md (lagg_race.sh: sysctl=net.link.lagg.0.active, 4000 walks, no crash)

Kernel references: sys/net/lagg/if_lagg.c:1620-1638, sys/net/lagg/if_lagg.c:755-775

VERDICT.md
↓ download raw

DF-3348 -- VERDICT

NOT REPRODUCED (impact=none, confidence=likely) Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Summary

lagg_sysctl_active walks sc_ports with no LAGG lock (if_lagg.c:1620-1638) and lagg_port_destroy swaps if_ioctl/if_output/if_lagg without the port serializer. Race attempted live: 4000 sysctl walks against 300 port add/remove cycles - no crash (the freed lp is slab-contained and the window needs packet traffic in flight during teardown). Mechanism line-confirmed.

How it was verified

  • Method: Code-line analysis + attempted live reproduction
  • Kernel references: sys/net/lagg/if_lagg.c:1620-1638, sys/net/lagg/if_lagg.c:755-775
  • Evidence: run log in VERDICT.md (lagg_race.sh: sysctl=net.link.lagg.0.active, 4000 walks, no crash)
  • Reachability: local-unpriv where noted in the summary; root-config for tun/lagg/carp/ipfw3/gre items; remote-facing for the packet parsers (wlan harnesses model the RX/TX path verbatim because the guest has no 802.11 hardware; precedent DF-0393/DF-0594/DF-0621/DF-0350).
  • Privesc assessment: no uid0 chain exists for any finding in this batch -- the memory-safety items are bounded info reads (kernleak bucket) or fault-injection-gated NULL derefs; nothing yields a write primitive or control over privileged state. The one panic-class live item (DF-3414) is root-only (SIOCIFDESTROY).

Fix

fix_status: not_testable -- Fix: LAGG_RLOCK around the sysctl walk (and hold the port serializer across the if_* swap); not A/B-able without a crash oracle.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable

Fix: LAGG_RLOCK around the sysctl walk (and hold the port serializer across the if_* swap); not A/B-able without a crash oracle.

per-fix-DF-3348

Confirmed kernel references

Detail

Evidence (decisive lines)

run log in VERDICT.md (lagg_race.sh: sysctl=net.link.lagg.0.active, 4000 walks, no crash)

Verdict

lagg_sysctl_active walks sc_ports with no LAGG lock (if_lagg.c:1620-1638) and lagg_port_destroy swaps if_ioctl/if_output/if_lagg without the port serializer. Race attempted live: 4000 sysctl walks against 300 port add/remove cycles - no crash (the freed lp is slab-contained and the window needs packet traffic in flight during teardown). Mechanism line-confirmed.