Failover state machine in input path runs without synchronization: concurrent state corruption
Summary
carp_proto_input_c(:1161) reads sc->sc_state then non-atomic transitions (callout_stop/carp_set_state/carp_setroute). No ASSERT_NETISR0 unlike carp_iamatch(:1609). Runs on any netisr CPU. Two packets on different CPUs -> both read same state -> double route deletion / inconsistent state.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0302 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | Add ASSERT_NETISR0 to carp_proto_input_c | 756 B | view raw |
| VERDICT.md | verdict | Full analysis of missing state machine locking | 2.4 KB | β raw |
| README.md | readme | Finding summary | 721 B | β raw |
| build.sh | build-script | No binary (code analysis) | 146 B | view raw |
| run.sh | run-script | Verify missing ASSERT_NETISR0 | 369 B | view raw |
| env.txt | environment | Guest environment | 442 B | view 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 |
DF-0302: CARP Failover State Machine Missing Locking
Finding
carp_proto_input_c() (ip_carp.c:1108) reads sc->sc_state (line 1161) and
performs non-atomic transitions (callout_stop, carp_set_state, carp_setroute)
without ASSERT_NETISR0. Unlike carp_iamatch() which asserts NETISR0,
this runs on any netisr CPU. Two packets on different CPUs can cause double
route deletion / state corruption.
Reproduction
Code-confirmed. The CARP receive path cannot be exercised on this single QEMU host (no multicast loopback). The bug is confirmed by the absence of ASSERT_NETISR0 in carp_proto_input_c (contrast with carp_iamatch at line 1609).
Fix
See fix.diff β adds ASSERT_NETISR0; to carp_proto_input_c.
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:
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)
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. carp_proto_input_c no ASSERT_NETISR0 vs carp_iamatch has it. Cross-CPU state machine race. QEMU no multicast loopback.
No comments yet.