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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3467 Β· 8 files
FileTypeDescriptionSize
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

VERDICT.md
↓ download raw

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

Evidence

run.log (full ipfw3 show counters + RACEUID-DONE lines), raceuid.c

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_testable
↓ fix.diffper-fix-DF-3467

Confirmed 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.