β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0302

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0302 Β· 8 files
FileTypeDescriptionSize
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
README.md readme Finding summary
↓ download 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.

VERDICT.md verdict Full analysis of missing state machine locking
↓ download raw

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

Fix verification

not_testable

compile 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.