fix(lib): keep a refused inbound packet endpoint-owned instead of leaking it (bug-1918)

INVOKE_MSG_HANDLER stored NULL into ep->fsm_in.pkt after every msg
handler. Handlers that take the packet clear the slot themselves
(vtask_handle_msg, peer_ctrl_msg_handler); one that refuses it
(-EINVAL / -ENOMEM) leaves it there, and the NULL store dropped the
only reference. The slot is now left as the handler leaves it: a
refused packet is reused for the next message or freed by endpoint_fini.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-04 23:59:29 +00:00
parent e27bc2ed3a
commit a74b684c48
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
2 changed files with 66 additions and 4 deletions

View file

@ -141,8 +141,8 @@ typedef struct endpoint_t {
* via the vtask dispatcher, so the vtask_enq/-1 ref pair does not cover this
* call). A handler whose own logic drops ep's last reference (e.g. a stale
* vote reply driving raft_cb_become_follower -> endpoint_unref on the very ep
* currently being read) frees ep out from under the trailing pkt=NULL write
* below, a heap-use-after-free. Hold a ref for the duration of the call, same
* currently being read) frees ep out from under the macro's own trailing use
* of it, a heap-use-after-free. Hold a ref for the duration of the call, same
* pattern as the vtask dispatch path, so the handler's own unref cannot drop
* the count to zero until this macro is done touching ep. */
#define INVOKE_MSG_HANDLER(ep) \
@ -150,9 +150,12 @@ typedef struct endpoint_t {
TRACE("%s msg handler %p", __func__, (ep)->msg_handler); \
if ((ep)->msg_handler != NULL) { \
endpoint_ref(ep); \
/* A handler that takes the packet clears ep->fsm_in.pkt itself \
* (vtask_handle_msg, peer_ctrl_msg_handler). One that refuses it \
* (early -EINVAL/-ENOMEM) leaves it endpoint-owned: the next \
* message reuses it and endpoint_fini frees it. Overwriting the \
* slot here leaked every refused packet (bug-1918). */ \
(ep)->msg_handler(ep); \
/* passing ownership of packet to upper layer */ \
(ep)->fsm_in.pkt = NULL; \
endpoint_unref(ep); \
} \
} while (0)