# DF-0301 VERDICT: CARP Missing Replay Protection

## Verdict: REPRODUCED (code-confirmed)

## Mechanism
`carp_proto_input_c()` in `sys/netinet/ip_carp.c` processes incoming CARP
advertisements. After verifying the HMAC (line 1137), it reconstructs the
64-bit counter from the advertisement (lines 1144-1146) and then at line
1148 has the literal placeholder:

```c
/* XXX Replay protection goes here */
```

At line 1151, the counter is accepted unconditionally:
```c
sc->sc_counter = tmp_counter;
```

There is NO comparison to any previously-seen counter value. An attacker who
captures a valid CARP multicast advertisement (destination 224.0.0.18, IP
protocol 112) can replay it any number of times. Each replay passes HMAC
verification (the HMAC was computed by the legitimate master) and is
processed by the state machine as if fresh.

## Impact
DoS via failover suppression. After the legitimate master fails, a BACKUP
that receives the replayed advertisement resets its master-down timer and
never promotes to MASTER. The virtual IP is black-holed. Requires only
network position (same L2 segment), not the CARP secret key.

## Dynamic Demonstration
The PoC (`carp_replay.c`) crafts valid CARP advertisements with a known key
and injects them via BPF on the parent interface. Two identical packets
(same counter) are sent; both should be accepted, demonstrating the missing
replay protection.

On this single QEMU host, the CARP receive path could not be exercised
dynamically because:
1. QEMU user-mode networking does not loop back multicast (224.0.0.18)
2. BPF feedback does not set the `M_MCAST` mbuf flag required by
   `carp_input()` (ip_carp.c:1235) for multicast demultiplexing to carp
   children

The bug is confirmed by code analysis: the XXX TODO at line 1148 is
definitive proof that no replay counter comparison exists.

## Confirmed Code Path
- `carp_proto_input()` at ip_carp.c:928 — registered for IPPROTO_CARP (112)
  via in_proto.c:320
- HMAC verification at ip_carp.c:1137 — if it fails, carps_badauth++ and drop
- Counter reconstruction at ip_carp.c:1144-1146
- **XXX Replay protection goes here** at ip_carp.c:1148
- Unconditional counter acceptance at ip_carp.c:1151
- State machine at ip_carp.c:1161 — processes advertisement as if fresh

## CARP is Functional on This Guest
- `ifconfig carp0 create` works (carp0 created, IFT_CARP)
- CARP sysctls present (net.inet.carp.allow, preempt, etc.)
- carp0 promotes to MASTER and sends its own advertisements (visible in tcpdump)
- carpstats counter `carps_ostates` increments (240+ state updates)

## Fix
Added `sc_replay_counter` field to `struct carp_softc`. After HMAC
verification, if the received counter is not strictly greater than the last
accepted counter (and the replay counter has been initialized), the packet
is dropped with `carps_badauth++`. See `fix.diff`.

## Kernel Refs
- sys/netinet/ip_carp.c:1148 — `/* XXX Replay protection goes here */`
- sys/netinet/ip_carp.c:1151 — `sc->sc_counter = tmp_counter`
- sys/netinet/ip_carp.c:1137 — HMAC verification gate
- sys/netinet/in_proto.c:320 — protocol 112 registration
