β¬’ 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.8 KB ↓ raw
README.md readme Finding summary 721 B ↓ raw
build.sh build-script No binary (code analysis) 65 B view raw
run.sh run-script Verify missing ASSERT_NETISR0 89 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 status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

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.