fix(lib): detach the received packet before handing it to a vtask (bug-1916)

vtask_handle_msg published the VTASK_IO task while ep->fsm_in.pkt still
pointed at the packet; INVOKE_MSG_HANDLER clears the slot only after the
handler returns. A vtask could decode and free the packet (xce_decode)
and wake the client, whose failover reconnect then ran endpoint_fini and
freed the dangling slot again: ASAN heap-use-after-free in free_packet,
seen in the worker and scheduler during failovers under heavy CPU load.

vtask_handle_msg now clears ep->fsm_in.pkt before vtask_enq and marks
the task the packet's sole owner (owns_io_pkt), so never-run tasks free
it at vtaskmgr_fini and an enqueue failure frees it instead of leaking;
xcectx vtask_entry frees it on its early -EINVAL exits. Same take-and-
clear discipline the controller's peer-control path already used.

Tests: test_vtask_pkt_handoff.c (3), RED reproduces the soak's ASAN stack.
libxce 733/0; xce-build all green; loaded /xce-test-ha soak (8 rounds,
40 spinners) PASS with no ASAN reports (it crashed 2/2 before).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-03 23:18:22 +00:00
parent 2aefa939db
commit 0fb2e14cc7
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
6 changed files with 257 additions and 17 deletions

View file

@ -51,12 +51,14 @@ typedef struct {
/* raft-drive-async task 3.1: when non-zero AND data->vtype == VTASK_IO, the
* carried io_packet_t (data->u.iodata.pkt) is OWNED by the task and must be
* freed by taskq_item_killer if the task is destroyed while still queued
* (never executed) at vtaskmgr_fini. Default 0 preserves the legacy
* endpoint-owned-packet model used by the libxce client path (xcectx.c's
* vtask_handle_msg does NOT clear ep->fsm_in.pkt, so the packet lifetime is
* tied to the endpoint, not the task — freeing it here would double-free).
* The controller's peer-control dispatch sets this to 1 because it clears
* ep->fsm_in.pkt and the task becomes the sole owner. */
* (never executed) at vtaskmgr_fini. Both IO handoffs set it to 1 because
* both clear ep->fsm_in.pkt and the task becomes the sole owner: the
* controller's peer-control dispatch (ha.c) and, since bug-1916,
* vtask_handle_msg (libxce client xcectx.c + controller cluster.c), which
* used to leave ep->fsm_in.pkt naming a packet the task's xce_decode freed
* until INVOKE_MSG_HANDLER cleared it after the handler returned — a window
* in which endpoint_fini double-freed it. 0 only for tasks that do not
* own a packet. */
int owns_io_pkt;
} vtask_t;