# DF-0428 — pfsync_input reachability PoC

**Verdict: NOT REPRODUCED (FALSE POSITIVE).** See `VERDICT.md` for the
full analysis. The claimed `IPPROTO_PFSYNC(240)` injection surface does
not exist: `pfsync_input` is dead code on the default kernel and on
`pf.ko`-loaded kernels.

## What this PoC does
- `setup_pfsync.c` — issues `SIOCSETPFSYNC` to configure `pfsync0`
  (DragonFly's `ifconfig` does not expose the pfsync ioctls). Built as
  the unprivileged user but **must be run as root** (the ioctl is
  `caps_priv_check`'d).
- `inject_pfsync.c` — crafts a raw IP packet (`ip_p=240`, `ip_ttl=255`)
  carrying a `PFSYNC_ACT_CLR` payload from an arbitrary spoofed source
  to the configured peer / multicast group. This is the "on-link
  attacker" the finding describes. Must be run as root (raw socket).

## Build
```
./build.sh
```
Equivalently:
```
cc -o setup_pfsync setup_pfsync.c
cc -o inject_pfsync inject_pfsync.c
```

## Run
The run demonstrates the **negative result**: inject the packet and
observe the kernel does nothing.

```
# (root, on the guest)
kldload pf.ko
pfctl -e
ifconfig pfsync0 create          # usually already exists once pf.ko loaded
./setup_pfsync vtnet0 224.0.0.240 128   # configure syncdev (triggers bulkfail panic ~5s later — see VERDICT.md)

# inject from a spoofed on-link source
./inject_pfsync 10.0.2.99 224.0.0.240 0xdeadbeef
```

To avoid the unrelated `pfsync_bulkfail` panic, skip the
`./setup_pfsync` step — the demonstration of unreachability holds
without configuring a syncdev (the handler is never even dispatched).

## Expected (master DEV, current code)
- `inject_pfsync` prints `sent 64 bytes: ... proto=240 ttl=255 act=CLR ...`
- **No** kernel message in `dmesg`.
- **No** `pfsync` protocol in `netstat -s` (only `tcp udp ip icmp igmp
  carp ip6 icmp6`).
- **No** change in `pfctl -s states`.

→ `pfsync_input` is unreachable; the injected packet hits the RAW
wildcard handler (`rip_input`) and is dropped. The "any on-link host can
inject pfsync packets" primitive does not exist.

## Confirming the root cause yourself
```
# 1. The gate (compiled out because NPFSYNC is never defined)
grep -n NPFSYNC sys/netinet/in_proto.c
grep -rn NPFSYNC sys/    # only the two gate lines; no #define, no option

# 2. Static kernel has no pfsync_input
nm /boot/kernel/kernel.debug | grep pfsync_input    # empty

# 3. inetsw[] has no protocol-240 entry
gdb -q -batch \
  -ex 'printf "entries=%lu\n", sizeof(inetsw)/sizeof(struct protosw)' \
  -ex 'set $i=0' \
  -ex 'while $i<sizeof(inetsw)/sizeof(struct protosw)' \
  -ex 'printf "inetsw[%d] proto=%d\n",$i,inetsw[$i].pr_protocol' \
  -ex 'set $i=$i+1' -ex 'end' \
  /boot/kernel/kernel.debug
```
