myxce/src/include/xcectx.h
Rongsong Shen 8da8fd03dd fix(libxce): bug-1077 ctrlstatus crash from shared reply-union type confusion
ctx->reply.u is a single untagged union shared across opcodes (debugmsg /
get(dataset_t*) / jobid / clusterconf / ctrlstatus alias the same 8 bytes).
Commit 61e1391 added speculative free-guards
`if (ctx->reply.u.ctrlstatus) free_CtrlStatus_ptr(...)` (and the clusterconf
twin). A non-NULL slot does NOT imply a CtrlStatus*: a failover-aborted
reslist/joblist leaves ctx->reply.u.get == &getreply (a dead STACK pointer)
because xce_get_with_condition/xce_resval only cleared the slot on the success
path, not the xce_get_req<0 / cbuf_get<0 early returns. The next ctrlstatus
reply (worker thread) freed that foreign pointer as a CtrlStatus -> ASan
SEGV / stack-use-after-return in free_CtrlStatus (controller.capnp.c:11638).

Fix (root cause, both layers):
- Revert the unsafe free-guards in xce_handle_reply: MOVE_PTR overwrite only,
  never free/deref an untagged union slot by type.
- Clear ctx->reply.u.get = NULL on ALL exit paths of xce_get_with_condition
  and xce_resval via a single `goto done` cleanup (forget-proof; the bug was
  one forgotten failover path). Also drops the now-unused controller.capnp.h.
- Defense-in-depth: handle_get_reply drops a reply when the slot is NULL.

Regression: expose xce_handle_reply and add
test_handle_reply_ctrlstatus_does_not_free_foreign_slot, which poisons the
shared slot with a foreign heap value and proves the ctrlstatus reply handler
does not free it (RED reproduced the exact free_CtrlStatus_ptr double-free;
GREEN after fix). xcelib_test 244/0, controller_unit_test 166/0, all binaries
build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSxwb2Xt5F84ATgiAZnH8V
2026-06-24 09:44:43 +00:00

85 lines
2.9 KiB
C

#if !defined(_XCECTX_H_)
#define _XCECTX_H_ 1
#include <stdint.h>
#include "config.h" /* XCE_MAXHOSTNAME_LEN */
#include "netio.h"
#include "request.h"
#include "vtaskmgr.h"
#include "xce/types.h"
typedef enum {
XCE_CTX_INIT = 0,
XCE_CTX_RUN,
XCE_CTX_DOWN,
XCE_CTX_BUSY,
XCE_CTX_ERR
} xce_ctx_state_t;
typedef int (*xce_request_handler_t)(xce_request_t*, void*);
typedef struct {
xce_ctx_state_t state;
xce_peerinfo_t myinfo;
iomgr_t iomgr;
vtaskmgr_t vtaskmgr;
endpoint_t* ep;
xce_request_handler_t handler;
void* handler_args;
cbuf_t wbuf;
dataset_t* dataset;
/* Redirect-hint address captured from the last redirect reply by
* handle_reply() (which only sees xce_ctx_t) and read by the failover
* rotator on the next retry. Empty host ("") means no hint. Advisory only;
* correctness never depends on it. */
char redirect_hint_host[XCE_MAXHOSTNAME_LEN];
uint16_t redirect_hint_port;
struct {
union {
char* debugmsg;
dataset_t* get;
uint64_t jobid;
xce_clusterconf_t* clusterconf;
xce_ctrlstatus_t* ctrlstatus;
} u;
} reply;
} xce_ctx_t;
extern int xce_ctx_init(xce_ctx_t*, char*, char*);
extern void xce_ctx_fini(xce_ctx_t*);
extern int xce_ctx_set_handler(xce_ctx_t*, xce_request_handler_t, void*);
extern int xce_ctx_start(xce_ctx_t*);
extern int xce_ctx_stop(xce_ctx_t*);
extern int xce_wakeup(xce_ctx_t*, int);
extern int xce_try_wakeup(xce_ctx_t*, int);
extern int xce_ctx_set_endpoint(xce_ctx_t*, endpoint_t*);
extern int xce_ctx_reconnect(xce_ctx_t*, char*, int);
extern void xce_block_tasks(xce_ctx_t*);
extern void xce_unblock_tasks(xce_ctx_t*);
/* Reply dispatch (normally driven by the vtask reply path). Exposed for the
* libxce test suite so the reply union's type-safety can be verified without a
* live endpoint (bug-1077: the shared, untagged reply union must never be
* dereferenced/freed as the wrong type). */
extern int xce_handle_reply(xce_ctx_t*, xce_reply_t*);
/*
* xce_ctx_capture_redirect_hint: copy a redirect address carried on a reply
* into the ctx's redirect_hint_host/port fields as a best-effort failover hint
* (Req 2.2, 8.3). Called by handle_reply() BEFORE the capnp-owned reply (and
* its redirect_host string) is freed.
*
* When `host` is NULL/empty or `port` is 0 there is no usable address: the hint
* is left untouched (it stays empty). Otherwise the host is bounded-copied into
* the fixed-size field (truncated + NUL-terminated, never overflowing) and the
* port is stored. A NULL ctx is a safe no-op. Advisory only — correctness never
* depends on the hint.
*/
extern void xce_ctx_capture_redirect_hint(xce_ctx_t*, const char* host,
uint16_t port);
#endif