Unauthenticated PFSYNC_ACT_UREQ forces victim to multicast entire pf state table: info disclosure + amplification DoS
Summary
PFSYNC_ACT_UREQ with id==0&&creatorid==0(:916): sets sc_ureq_received, seeds sc_bulk_send_next, sends PFSYNC_BUS_START. pfsync_bulk_update(:1545-1613) iterates up to sc_maxcount*PFSYNC_BULKPACKETS states across ALL cpus, packs into PFSYNC_ACT_UPD, emits via pfsync_sendout to multicast group(:1734-1736). Single ~40-byte packet -> full state table dump. Every tracked conn src/dst addr/ports/proto/seq/timeouts exposed to passive sniffer + CPU/bandwidth exhaustion. No priv/source check (DF-0428).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0429 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| inject_ureq.c | trigger-source | crafts PFSYNC_ACT_UREQ(id=0,creatorid=0) bulk-dump trigger packet | 3.7 KB | view raw |
| build.sh | build-script | cc -o inject_ureq inject_ureq.c | 195 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 | 69 B | view raw |
| run.log | run-log | decisive run: UREQ injected -> no bulk dump, 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.2 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-0429 β PoC evidence pack
Finding: Unauthenticated PFSYNC_ACT_UREQ (id==0 && creatorid==0)
forces victim to multicast entire pf state table (info disclosure + DoS).
Severity (filed): High
Verdict: NOT REPRODUCED β FALSE POSITIVE (dead code)
Impact: none
TL;DR
DF-0429 cites case PFSYNC_ACT_UREQ: at sys/net/pf/if_pfsync.c:900-949,
which is an arm 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_ureq.c |
crafts the exact UREQ(id=0,creatorid=0) trigger packet |
build.sh |
cc -o inject_ureq inject_ureq.c |
run.sh |
inject + show zero pfsync activity |
build.log |
full build output |
run.log |
decisive run: injection β no bulk dump, 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 bulk dump, NO pfsyncstats
Expected on master DEV: the UREQ packet is sent, but pfsync_input is never
invoked (symbol absent from kernel.debug; no proto-240 handler registered),
so no bulk state-table dump occurs. pfsyncstats stays empty.
Fix
No code change needed β false-positive. See VERDICT.md Β§ Recommended fix.
DF-0429 β Verdict: NOT REPRODUCED (FALSE POSITIVE β dead code)
One-line verdict
DF-0429 claims an unauthenticated PFSYNC_ACT_UREQ packet with
id==0 && creatorid==0 (sys/net/pf/if_pfsync.c:916) forces the victim to
multicast its entire pf state table. This code path is unreachable: it
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:900-949β thecase PFSYNC_ACT_UREQ:arm of the giantswitch (action)block insidepfsync_input().- The exact trigger line:
if_pfsync.c:916 if (id_key.id == 0 && id_key.creatorid == 0)β seedssc->sc_bulk_send_next, sendsPFSYNC_BUS_START, andpfsync_bulk_update()(:1545-1613) later streams the whole table asPFSYNC_ACT_UPDmulticast packets.
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. DF-0428's cited
range 462-541 is just the entry-validation prefix of that same function;
the case PFSYNC_ACT_UREQ: at :900 is the 7th arm of the very same switch.
So DF-0429 is the same dead function as DF-0428 β not a separate sink.
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_UREQ(id=0, creatorid=0) packet DF-0429 describes (./inject_ureq 10.0.2.99 224.0.0.240) produced noPFSYNC_BUS_START, no bulk state-table dump, nopfsyncs_ipacketsincrement, and no pf-state change. Seerun.log.
Impact of this false positive
None. The "single ~40-byte packet β full state-table disclosure" primitive
DF-0429 describes is not reachable. pfsync_input is dormant; the UREQ arm
is 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_ureq inject_ureq.c (unprivileged build OK) # (root on guest): kldload pf.ko; pfctl -e ./run.sh # injects the UREQ bulk-dump trigger; expects NO effect
PoC changes
inject_ureq.cβ NEW. Crafts a rawip_p=240,ip_ttl=255packet carryingpfsync_header{action=PFSYNC_ACT_UREQ}+pfsync_state_upd_req{id=0, creatorid=0}β the exact trigger cited atif_pfsync.c:916. (Note:struct pfsync_state_upd_req.idisu_int32_t id[2], so id is left as the memset zero rather than a scalar assignment.)- No
exploit.cβ there is no memory-corruption primitive; this is an auth/DoS claim whose entire surface is dead code.
Recommended fix
No code change needed: false-positive. The cited case PFSYNC_ACT_UREQ:
arm (if_pfsync.c:900-949) is 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. pfsync_input never registered.
Evidence (decisive lines)
Same live injection test as DF-0428: packets sent, zero pfsync activity. NPFSYNC never defined.
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, confirmed by the SAME live injection test. PFSYNC_ACT_UREQ handling at if_pfsync.c:900-949 is inside pfsync_input which is never registered (NPFSYNC gate). My live test sent crafted UREQ packets β zero response, no bulk state dump, no pfsyncstats.
No comments yet.