fix(controller): NULL-guard ep->owner in myreq_handler — stop round-4 restart SIGSEGV (bug-1286)

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dbTHR9CsEYK9RsqmQpQpC
This commit is contained in:
Rongsong Shen 2026-06-30 09:23:51 +00:00
parent 02e9275706
commit 5db734aa44

View file

@ -247,6 +247,23 @@ static int myreq_handler(vtask_t* task)
init_reply_by_req(&reply, &request); 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) */ /* Dispatch gate: reject ops not allowed in current FSM state (req 1.3, 1.4) */
rc = ha_dispatch_gate(&c->ha, request.opcode); rc = ha_dispatch_gate(&c->ha, request.opcode);
if (rc != 0) { if (rc != 0) {