Unauthenticated PFSYNC_ACT_CLR/DEL/DEL_C let attacker mass-destroy arbitrary pf state across all CPUs
Summary
PFSYNC_ACT_CLR(:542-608): reads attacker creatorid, ifname[0]==0 walks tree_id[] on every CPU pf_unlink_state matching creatorid(:561-574); non-empty ifname walks pf_statetbl[](:584-601). CLR creatorid==0 accepted unconditionally. PFSYNC_ACT_DEL(:749) and DEL_C(:873) unlink individual states by attacker id/creatorid. No source auth (DF-0428). Remote attacker mass-deletes firewall state -> tear down all tracked connections (availability), selectively kill specific flows to manipulate ruleset re-eval (integrity). HA pair desync.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0430 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| inject_clr.c | trigger-source | crafts PFSYNC_ACT_CLR(ifname="",creatorid) mass-delete trigger packet | 3.8 KB | view raw |
| build.sh | build-script | cc -o inject_clr inject_clr.c | 197 B | view raw |
| run.sh | run-script | inject + show zero pfsync activity | 1.1 KB | view raw |
| build.log | build-log | full build output, BUILD_EXIT=0 | 68 B | view raw |
| run.log | run-log | decisive run: CLR injected -> no state deletion, no pfsyncstats | 2.1 KB | view raw |
| env.txt | environment | uname, pf/pfsync state, nm checks (pfsync_input ABSENT from static kernel) | 365 B | view raw |
| VERDICT.md | verdict | full narrative: dead-code false-positive (same root cause as DF-0428) | 5.3 KB | β raw |
| README.md | readme | evidence-pack index + reproduce instructions | 1.8 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
| live_injection_test.txt | reachability-test | Live pfsync packet injection test - pfsync_input not called | 1.4 KB | view raw |
DF-0430 β PoC evidence pack
Finding: Unauthenticated PFSYNC_ACT_CLR/DEL/DEL_C let attacker
mass-destroy arbitrary pf state across every CPU.
Severity (filed): High
Verdict: NOT REPRODUCED β FALSE POSITIVE (dead code)
Impact: none
TL;DR
DF-0430 cites case PFSYNC_ACT_CLR: (if_pfsync.c:542-608), DEL (:749)
and DEL_C (:873) β all arms of the switch (action) inside
pfsync_input() (a single function spanning if_pfsync.c:462-1006).
pfsync_input is dead code: the only registration site
(sys/netinet/in_proto.c:281-296) is wrapped in #ifdef NPFSYNC, and
NPFSYNC is never defined anywhere in sys/, so the handler is never
registered and no proto-240 packet can reach it. This is the identical
root cause proven exhaustively for the sibling finding DF-0428.
Files
| file | purpose |
|---|---|
VERDICT.md |
full narrative + evidence |
inject_clr.c |
crafts the exact CLR(ifname="",creatorid) mass-delete trigger packet |
build.sh |
cc -o inject_clr inject_clr.c |
run.sh |
inject + show zero pfsync activity |
build.log |
full build output |
run.log |
decisive run: injection β no state deletion, no pfsyncstats |
env.txt |
guest uname, pf/pfsync state, nm checks |
manifest.json |
machine-readable catalog |
Reproduce
./build.sh # (root on guest): kldload pf.ko; pfctl -e ./run.sh # run as root (raw socket); expect NO state deletion, NO pfsyncstats
Expected on master DEV: the CLR packet is sent, but pfsync_input is never
invoked (symbol absent from kernel.debug; no proto-240 handler registered),
so no pf state is deleted. pfsyncstats stays empty.
Fix
No code change needed β false-positive. See VERDICT.md Β§ Recommended fix.
DF-0430 β Verdict: NOT REPRODUCED (FALSE POSITIVE β dead code)
One-line verdict
DF-0430 claims unauthenticated PFSYNC_ACT_CLR/DEL/DEL_C packets let an
attacker mass-destroy arbitrary pf state across every CPU
(sys/net/pf/if_pfsync.c:542-608, :749, :873). This code path is
unreachable: every cited line lives inside pfsync_input(), which is
dead code on the default kernel. The same root cause already proven for
the sibling finding DF-0428 applies verbatim here.
What the finding cited (and where it lives)
sys/net/pf/if_pfsync.c:542-608βcase PFSYNC_ACT_CLR:β reads an attacker-controlledcreatoridand, whenifname[0]==0, walkstree_id[]on every CPU callingpf_unlink_state()for matching states (:561-574); non-emptyifnamewalkspf_statetbl[](:584-601).sys/net/pf/if_pfsync.c:749βcase PFSYNC_ACT_DEL:β unlinks individual states by attackerid/creatorid.sys/net/pf/if_pfsync.c:873βcase PFSYNC_ACT_DEL_C:β same, with a client-id match (:881-893).
Critical structural fact: pfsync_input() is ONE function spanning
sys/net/pf/if_pfsync.c:462-1006 (the done: label is at :1003, closing
brace at :1006). The switch (action) begins at :541; case
PFSYNC_ACT_CLR: at :542 is the 1st arm, DEL and DEL_C later arms of the
very same switch. So all three arms DF-0430 cites are the same dead
function as DF-0428 β not separate sinks.
Why it is a false positive (dead code β inherited from DF-0428)
pfsync_input is registered as the IPPROTO_PFSYNC (240) input handler in
exactly one place, and that place is compiled out:
sys/netinet/in_proto.c:281:#ifdef NPFSYNC
sys/netinet/in_proto.c:288: .pr_input = pfsync_input,
sys/netinet/in_proto.c:296:#endif /* NPFSYNC */
NPFSYNC is never defined anywhere in sys/ β exhaustive grep returns
only the two gate lines (in_proto.c:281 and :296). No #define NPFSYNC,
no options NPFSYNC in sys/conf/options, no option in any kernel config.
β The .pr_input = pfsync_input protosw entry is compiled out of the static
kernel. With no protosw carrying pr_protocol=240, ip_init
(sys/netinet/ip_input.c:343-351) leaves ip_protox[240] pointing at the
IPPROTO_RAW wildcard, so any proto-240 packet is dispatched to rip_input
(ip_input.c:409-415) β never to pfsync_input. pf.ko does not
dynamically register a handler either (it exports pfsync_input/pfsyncattach
as symbols but references neither inetsw nor protosw).
The honest comment at if_pfsync.c:486 β "This function is not yet called
from anywhere" β is accurate.
Definitive evidence (re-confirmed on this run, #0 with-src unpatched)
- Static kernel symbol:
nm /boot/kernel/kernel.debug | grep -w pfsync_inputβ ABSENT. The function is not even linked into the running kernel. - Module symbol, unreferenced:
nm /boot/kernel/pf.ko | grep pfsync_inputβ0000000000003040 T pfsync_input;grep inetsw|protoswβ none. The module carries the code but never registers it. - No pfsync protocol registered:
netstat -sp pfsyncβ empty (no pfsyncstats block at all) even withpf.koloaded +pfctl -e+pfsync0present. - Live injection, zero effect: injecting the exact
PFSYNC_ACT_CLR(ifname="",creatorid=0xdeadbeef) packet DF-0430 describes (./inject_clr 10.0.2.99 224.0.0.240 0xdeadbeef) deleted no pf state, produced nopfsyncs_ipacketsincrement, and no observable pfsync activity. Seerun.log.
Impact of this false positive
None. The "mass-delete arbitrary pf state across every CPU" primitive DF-0430
describes is not reachable. pfsync_input is dormant; the CLR/DEL/DEL_C arms
are unreachable code inside a dead function. (DragonFly's in-kernel pfsync
receiver was never finished/wired up β only the sender side
pfsync_sendout is functional.)
Build / run (reproduce the negative result)
./build.sh # cc -o inject_clr inject_clr.c (unprivileged build OK) # (root on guest): kldload pf.ko; pfctl -e ./run.sh # injects the CLR mass-delete trigger; expects NO effect
PoC changes
inject_clr.cβ NEW. Crafts a rawip_p=240,ip_ttl=255packet carryingpfsync_header{action=PFSYNC_ACT_CLR}+pfsync_state_clr{ifname="", creatorid=0xdeadbeef}β the exact trigger cited atif_pfsync.c:542-608(the every-CPUtree_id[]walk). The same dead-code conclusion covers the DEL (:749) and DEL_C (:873) arms since they are siblings in the same switch.- No
exploit.cβ there is no memory-corruption primitive; this is an auth/availability claim whose entire surface is dead code.
Recommended fix
No code change needed: false-positive. The cited PFSYNC_ACT_CLR/DEL/DEL_C
arms (if_pfsync.c:542-895) are inside pfsync_input(), which is unreachable
on the default kernel and on pf.ko-loaded kernels because the
#ifdef NPFSYNC registration block in in_proto.c:281-296 is compiled out
(NPFSYNC is never defined) and pf.ko does not register a handler.
If the project ever wires up the pfsync receiver (defines NPFSYNC,
finishes the integration), then the missing-source-authentication concern
becomes real; but that is gated on first making the code reachable, which is
not the case today. (Matches the conclusion of sibling DF-0428.)
Fix verification
not_testablenot_applicable -- false positive.
n/a
Confirmed kernel references
Detail
Exploit chain
none β dead code.
Evidence (decisive lines)
Same live injection test as DF-0428: CLR packets sent, zero state deletion.
PoC changes
Added live_injection_test.txt referencing the DF-0428 live test.
Verified recommended fix
No code change needed: false-positive (same root cause as DF-0428).
Verdict
NOT REPRODUCED (dead code). Same root cause as DF-0428. PFSYNC_ACT_CLR/DEL/DEL_C mass-deletion at if_pfsync.c:542-608,749,873 is inside pfsync_input which is never registered. Live injection test sent CLR packets β zero pf state deletion, no pfsyncstats.
No comments yet.