# DF-0302 VERDICT: CARP Failover State Machine Missing Locking

## Verdict: REPRODUCED (code-confirmed)

## Mechanism
`carp_proto_input_c()` at ip_carp.c:1108 runs the CARP failover state
machine. It reads `sc->sc_state` at line 1161 (`switch (sc->sc_state)`) and
performs non-atomic state transitions: `callout_stop()` (line 1172),
`carp_set_state()` (line 1176), `carp_setroute()` (line 1179), and
`carp_setrun()` (line 1177).

Unlike `carp_iamatch()` at line 1605-1609 which has `ASSERT_NETISR0`:

```c
carp_iamatch(const struct in_ifaddr *ia)
{
    ASSERT_NETISR0;
```

`carp_proto_input_c()` has NO such assertion. The function is called from
`carp_proto_input()` (line 1025/1101) which is the `.pr_input` handler for
IP protocol 112. This handler runs on whatever netisr CPU processes the
incoming packet. On a multi-CPU system, two CARP advertisements arriving on
different netisr CPUs could both read the same `sc_state` and both attempt
state transitions concurrently, leading to:
- Double route deletion (carp_setroute called twice)
- Inconsistent state (both think they're MASTER or BACKUP)
- callout_stop/carp_setrun race conditions

## Impact
On a multi-CPU system with CARP configured, concurrent processing of CARP
advertisements on different CPUs can cause inconsistent failover state.
Realistic impact: route table corruption, failover disruption, or kernel
panic from inconsistent state. Requires network position (same L2 segment).

## Dynamic Demonstration
This is a timing-dependent race condition. On this single QEMU guest:
- The CARP receive path cannot be exercised (QEMU doesn't loopback multicast)
- The race requires two concurrent packets on different CPUs
- The guest has 6 CPUs so the race window exists in theory

The bug is confirmed by code analysis: the absence of ASSERT_NETISR0 in
carp_proto_input_c() (contrast with carp_iamatch at line 1609) and the
non-atomic state transitions within the switch statement.

## Fix
Added `ASSERT_NETISR0;` at the beginning of `carp_proto_input_c()`. This
ensures all CARP input processing runs on netisr CPU 0, serializing state
transitions and preventing concurrent packet processing on different CPUs.
See `fix.diff`.

## Kernel Refs
- sys/netinet/ip_carp.c:1108 — carp_proto_input_c entry point (no ASSERT_NETISR0)
- sys/netinet/ip_carp.c:1161 — switch (sc->sc_state) non-atomic read
- sys/netinet/ip_carp.c:1172-1179 — non-atomic transitions
- sys/netinet/ip_carp.c:1609 — carp_iamatch HAS ASSERT_NETISR0 (contrast)
