From fc87a662cda3ecfe2a43bc5fbc4728fb453bff17 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Sun, 21 Jun 2026 15:42:37 +0000 Subject: [PATCH] fix(raft): reject n_peers > RAFT_MAX_PEERS in raft_init next_index[]/match_index[]/pre_vote_replies[] are fixed-size RAFT_MAX_PEERS arrays indexed by peer; without an upper-bound check a caller passing n_peers > 8 would OOB-write in the leader-progress and pre-vote loops. Add the guard (returns -EINVAL, no mutation) + canary-checked test. Latent since task 1.1; surfaced by task 2.2 review (first loop writes into the new arrays). No current caller exceeds the bound (sim caps at 8, HA <= 4). --- src/lib/raft/raft.c | 5 +++++ src/lib/testsuite/test_raft.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/lib/raft/raft.c b/src/lib/raft/raft.c index 0382ec1e..3e254504 100644 --- a/src/lib/raft/raft.c +++ b/src/lib/raft/raft.c @@ -15,6 +15,11 @@ int raft_init(raft_state_t *s, const raft_config_t *cfg) /* Validate required callbacks and n_peers before touching *s */ if (cfg->n_peers < 1) return -EINVAL; + /* Upper bound: next_index[]/match_index[]/pre_vote_replies[] are fixed-size + * RAFT_MAX_PEERS arrays indexed by peer in [0, n_peers). Reject oversize + * clusters so the leader-progress and pre-vote loops can never OOB-write. */ + if (cfg->n_peers > RAFT_MAX_PEERS) + return -EINVAL; /* Even cluster sizes (>1) have no fault-tolerance advantage over the next * smaller odd size and can mislead operators into thinking split-brain is * impossible. Reject to prevent misconfiguration. */ diff --git a/src/lib/testsuite/test_raft.c b/src/lib/testsuite/test_raft.c index c6678013..2cda479a 100644 --- a/src/lib/testsuite/test_raft.c +++ b/src/lib/testsuite/test_raft.c @@ -518,6 +518,37 @@ static void test_raft_init_even_peers_returns_einval(void) TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role); } +/* n_peers > RAFT_MAX_PEERS → -EINVAL: the fixed-size next_index[]/match_index[]/ + * pre_vote_replies[] arrays are indexed by peer, so an oversize cluster must be + * rejected before any leader-progress/pre-vote loop can OOB-write. */ +static void test_raft_init_oversize_peers_returns_einval(void) +{ + raft_state_t s; + raft_callbacks_t cbs = make_full_cbs(); + raft_config_t cfg; + + memset(&s, 0xAB, sizeof(s)); + + /* n_peers = RAFT_MAX_PEERS + 1 → must fail (would OOB next_index[]) */ + memset(&cfg, 0, sizeof(cfg)); + cfg.n_peers = RAFT_MAX_PEERS + 1; + cfg.self_idx = 0; + cfg.cbs = &cbs; + TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg)); + + /* s must be untouched on rejection (canary) */ + { + unsigned char buf[sizeof(s)]; + memset(buf, 0xAB, sizeof(buf)); + TEST_ASSERT_EQUAL_MEMORY(buf, &s, sizeof(s)); + } + + /* n_peers = RAFT_MAX_PEERS (odd? RAFT_MAX_PEERS=8 is even → also rejected by + * the even-peers rule, so use the largest valid odd size at the bound) */ + cfg.n_peers = RAFT_MAX_PEERS - 1; /* 7: odd, within bound → must succeed */ + TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg)); +} + /* n_peers == 0 → -EINVAL */ static void test_raft_init_zero_peers_returns_einval(void) { @@ -4063,6 +4094,7 @@ void test_raft(void) RUN_TEST(test_raft_init_null_truncate_from_returns_einval); RUN_TEST(test_raft_init_log_callbacks_present_succeeds); RUN_TEST(test_raft_init_even_peers_returns_einval); + RUN_TEST(test_raft_init_oversize_peers_returns_einval); RUN_TEST(test_raft_init_zero_peers_returns_einval); RUN_TEST(test_raft_init_success_fields); RUN_TEST(test_raft_init_default_election_timeout);