From 5db734aa44650039b5ba6bd412ff747ac6d208e8 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Tue, 30 Jun 2026 09:23:51 +0000 Subject: [PATCH] =?UTF-8?q?fix(controller):=20NULL-guard=20ep->owner=20in?= =?UTF-8?q?=20myreq=5Fhandler=20=E2=80=94=20stop=20round-4=20restart=20SIG?= =?UTF-8?q?SEGV=20(bug-1286)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root-caused via the non-ASAN debug build + gdb: on a chaotic restart an inbound endpoint dispatches a request (e.g. walCatchupEntry op 37) while ep->owner (cluster_t) is NULL — not yet wired at accept, or cleared during teardown. The handler did `c = ep->owner` then `ha_dispatch_gate(&c->ha, ...)`; with c==NULL, &c->ha = NULL+offsetof(cluster,ha) = 0x3ff0, a garbage pointer ha_dispatch_gate dereferenced (atomic_load &ha->role) -> SIGSEGV, dropping the node below quorum and stalling the election. Guard c==NULL after decode (so the decoded request is freed, no leak): log a loud WARN and drop the request — there is no cluster context to dispatch through and no c->myhost to address a reply. The controller stays alive (never self-terminate on an operational condition); the peer retries once the endpoint is owned. Backtrace: ha_dispatch_gate(ha=0x3ff0) <- myreq_handler cluster.c <- vtask_entry <- run_task. Second of the two round-4 blockers (bug-1282 deadlock already fixed). Re-running the 7.2 chaotic soak with both fixes. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_019dbTHR9CsEYK9RsqmQpQpC --- src/controller/cluster.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/controller/cluster.c b/src/controller/cluster.c index 8ca858d..54d3fd5 100644 --- a/src/controller/cluster.c +++ b/src/controller/cluster.c @@ -247,6 +247,23 @@ static int myreq_handler(vtask_t* task) init_reply_by_req(&reply, &request); + /* bug-1286: a chaotic-restart race can run this handler on an inbound endpoint + * whose owner (cluster_t) is NULL — not yet wired at accept time, or cleared + * during teardown. With c == NULL, `&c->ha` is a NULL+offset garbage pointer + * (0x3ff0) that SIGSEGV'd ha_dispatch_gate (ha.c) when it loaded ha->role. + * Drop the request LOUDLY instead of dereferencing it: we have no cluster + * context to dispatch through and cannot even address a reply (it needs + * c->myhost). The controller stays alive (never self-terminate on an + * operational condition); the peer retries once the endpoint is owned. The + * request is freed here so the decoded packet does not leak. */ + if (c == NULL) { + WARN("myreq_handler: endpoint %p has no owner (cluster) — dropping %s " + "(restart race, bug-1286)", (void*)ep, + xce_request_op_name(&request)); + xce_free_request(&request); + return -1; + } + /* Dispatch gate: reject ops not allowed in current FSM state (req 1.3, 1.4) */ rc = ha_dispatch_gate(&c->ha, request.opcode); if (rc != 0) {