# DF-0301: CARP Missing Replay Protection

## Finding
`carp_proto_input_c()` (sys/netinet/ip_carp.c:1148) has a literal TODO:
`/* XXX Replay protection goes here */`. After HMAC verification, the 64-bit
counter from the advertisement is accepted unconditionally at line 1151:
`sc->sc_counter = tmp_counter`. There is NO comparison to a previously-seen
counter value.

An attacker on the same L2 segment who captures a valid CARP multicast
advertisement (224.0.0.18) can replay it indefinitely. After the legitimate
master fails, the BACKUP keeps accepting the replayed advertisement and
resetting its master-down timer, so it never promotes -- the virtual IP is
black-holed (DoS). No crypto key is needed; pure replay.

## Reproduction

### Build
```
cc -o carp_replay carp_replay.c
```

### Run (as root, threat model is network attacker)
```
# Configure CARP as BACKUP
ifconfig carp0 create
ifconfig carp0 vhid 1 advskew 200 advbase 1 pass testkey12345678901 10.0.2.200/24
ifconfig carp0 up

# Inject two identical CARP advertisements (same counter = replay)
./carp_replay /dev/bpf1 vtnet0 1 testkey12345678901 10.0.2.200
```

### Expected (bug present)
Both the original and the replayed advertisement are accepted (carps_ipackets
increments, carps_badauth stays flat). With replay protection, the second
identical packet would be rejected.

### Expected (fixed)
The replayed packet is rejected with carps_badauth incremented.

## Caveats
On this single QEMU host, the CARP receive path could not be exercised
dynamically because QEMU user-mode networking does not loop back multicast
packets, and BPF feedback does not set the M_MCAST mbuf flag that
`carp_input()` requires for multicast demultiplexing. The bug is confirmed
by rigorous code-path analysis: the `XXX Replay protection goes here` TODO
at line 1148 is definitive proof that no counter comparison exists.
