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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0430 Β· 11 files
FileTypeDescriptionSize
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
README.md readme evidence-pack index + reproduce instructions
↓ download 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.

VERDICT.md verdict full narrative: dead-code false-positive (same root cause as DF-0428)
↓ download raw

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-controlled creatorid and, when ifname[0]==0, walks tree_id[] on every CPU calling pf_unlink_state() for matching states (:561-574); non-empty ifname walks pf_statetbl[] (:584-601).
  • sys/net/pf/if_pfsync.c:749 β€” case PFSYNC_ACT_DEL: β€” unlinks individual states by attacker id/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)

  1. Static kernel symbol: nm /boot/kernel/kernel.debug | grep -w pfsync_input β†’ ABSENT. The function is not even linked into the running kernel.
  2. 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.
  3. No pfsync protocol registered: netstat -sp pfsync β†’ empty (no pfsyncstats block at all) even with pf.ko loaded + pfctl -e + pfsync0 present.
  4. 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 no pfsyncs_ipackets increment, and no observable pfsync activity. See run.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 raw ip_p=240, ip_ttl=255 packet carrying pfsync_header{action=PFSYNC_ACT_CLR} + pfsync_state_clr{ifname="", creatorid=0xdeadbeef} β€” the exact trigger cited at if_pfsync.c:542-608 (the every-CPU tree_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.

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_testable
baseline no→ patch + rebuild →patched clean

not_applicable -- false positive.

n/a
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.