check_uid/check_gid dereference an unreferenced, unlocked inpcb on the output path β use-after-free read race against in_pcbdetach()->kfree(inp)
Summary
ip_fw3_match_guid() takes a raw pcb from in_pcblookup_hash() with no reference and no held token (the exact-match walk takes none), then dereferences pcb->inp_socket->so_cred. in_pcbdetach() does kfree(inp) from the owning netisr thread. Output-direction ipfw3 filtering runs in the SENDER'S USER THREAD (ether_ipfw_chk from ether_output_frame), so when the sender's CPU matches the pcbinfo's, a concurrently closing socket can free the pcb between the lookup and the deref - UAF read, panic under churn. The NULL-check of pcb->inp_socket also races the detach clearing it. Local unpriv user with an ipfw3 'uid/gid out' rule installed matching their own traffic can race close(2) storms against filtered packets to crash the kernel (narrow window, read-only UAF; DoS ceiling). Not runtime-verified (narrow window). Fix: hold the pcbinfo token across lookup+deref (the wildcard-path discipline) or re-check under so->so_refs. [VERIFIED pass2: not reproduced at 3.6M uid-rule evaluations under 6-thread close storms - window not hit; code-certain trace stands]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3467 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 760 B | β raw | |
| VERDICT.md | β | 1.8 KB | β raw | |
| env.txt | β | 309 B | view raw | |
| fix.diff | β | 788 B | view raw | |
| manifest.json | β | 778 B | view raw | |
| raceuid.c | β | 1.7 KB | view raw | |
| run.log | β | 629 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-3467 - ipfw3 check_uid unreferenced inpcb UAF (output path)
Build: gcc -O -pthread -o raceuid raceuid.c Setup (root, from a nohup script - ipfw3 loads default-deny and will cut ssh): kldload ipfw3 ipfw3_basic ipfw3_layer4 ipfw3 add 100 allow udp from any to any uid 1001 ipfw3 add 200 allow tcp from any to any # keep ssh alive Run (unprivileged uid 1001): ./raceuid 60 (x3 rounds) Expected (observed): rule 100 counters climb to ~1.2M/round (check_uid evaluated per packet on the output path), 3.6M matched packets total across concurrent close(2) storms, NO panic, guest healthy -> not_reproduced (narrow window).
Kernel: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
DF-3467 - VERDICT
status: not_reproduced | reproduced: 0 | impact: none | confidence: likely
Attempted and not reproduced. The vulnerable code path was exercised at full rate: ipfw3+ipfw3_basic+ipfw3_layer4 loaded on the stock guest, rule '100 allow udp from any to any uid 1001' active, and six unprivileged (uid 1001) threads raced 3,599,984 UDP socket()/connect()/send()/close() cycles against 10.0.2.2 so the output-direction check_uid ran in the sender threads (ipfw3 counter 00100 = 3,599,984 packets / 331,198,528 bytes matched by the uid rule) while concurrent close(2) storms tore the pcbs down. Zero panics, zero anomalies across all runs; guest healthy after. The UAF window between in_pcblookup_hash() (ip_fw3_layer4.c:120/125, no pcbinfo token held) and the inp_socket deref (:130/:136/:139) is a handful of instructions against the detaching CPUs hash-removal - the finding remains code-plausible (lookup takes no reference and no token; the wildcard path in the same file shows the intended token discipline) but did not manifest in 3.6M matched packets under a close storm. Read-only UAF ceiling (panic at most) unchanged.
Kernel references
- sys/net/ipfw3_layer4/ip_fw3_layer4.c:120
- sys/net/ipfw3_layer4/ip_fw3_layer4.c:130
- sys/net/ipfw3_layer4/ip_fw3_layer4.c:136
Evidence
run.log (full ipfw3 show counters + RACEUID-DONE lines), raceuid.c
Recommended fix
hold the per-CPU pcbinfo token across lookup+dereference in ip_fw3_match_guid (as the wildcard path does), or take a pcb reference.
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_testableConfirmed kernel references
Detail
Evidence (decisive lines)
run.log (full ipfw3 show counters + RACEUID-DONE lines), raceuid.c
PoC changes
raceuid.c written fresh (cpuset_setaffinity does not exist on DFly; pinning replaced by 6 busy threads spread by the LWKT scheduler)
Verified recommended fix
hold the per-CPU pcbinfo token across lookup+dereference in ip_fw3_match_guid (as the wildcard path does), or take a pcb reference.
Verdict
Attempted and not reproduced. The vulnerable code path was exercised at full rate: ipfw3+ipfw3_basic+ipfw3_layer4 loaded on the stock guest, rule '100 allow udp from any to any uid 1001' active, and six unprivileged (uid 1001) threads raced 3,599,984 UDP socket()/connect()/send()/close() cycles against 10.0.2.2 so the output-direction check_uid ran in the sender threads (ipfw3 counter 00100 = 3,599,984 packets / 331,198,528 bytes matched by the uid rule) while concurrent close(2) storms tore the pcbs down. Zero panics, zero anomalies across all runs; guest healthy after. The UAF window between in_pcblookup_hash() (ip_fw3_layer4.c:120/125, no pcbinfo token held) and the inp_socket deref (:130/:136/:139) is a handful of instructions against the detaching CPUs hash-removal - the finding remains code-plausible (lookup takes no reference and no token; the wildcard path in the same file shows the intended token discipline) but did not manifest in 3.6M matched packets under a close storm. Read-only UAF ceiling (panic at most) unchanged.
No comments yet.