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
738 lines
13 KiB
C
738 lines
13 KiB
C
#include <errno.h>
|
|
|
|
#include "utils.h"
|
|
#include "xceapi.h"
|
|
|
|
int xce_hello(xce_ctx_t* ctx, char* msg)
|
|
{
|
|
int rc, status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_hello_req(ctx->ep, msg);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_nop(xce_ctx_t* ctx)
|
|
{
|
|
int rc;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_nop_req(ctx->ep);
|
|
|
|
/* no need to wait reply, just as heartbeat */
|
|
return rc;
|
|
}
|
|
|
|
int xce_heartbeat(xce_ctx_t* ctx, int hb_interval)
|
|
{
|
|
time_t now;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->ep == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
now = time(NULL);
|
|
if (now - ctx->ep->ts_last_out < hb_interval) {
|
|
return 0;
|
|
}
|
|
|
|
return xce_nop(ctx);
|
|
}
|
|
|
|
int xce_shutdown(xce_ctx_t* ctx)
|
|
{
|
|
int rc;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_shutdown_req(ctx->ep);
|
|
|
|
/* do not need wait controller back as it will be shutdown */
|
|
return rc;
|
|
}
|
|
|
|
int xce_get_with_condition(xce_ctx_t* ctx, xce_get_t* get, void*** data)
|
|
{
|
|
int rc;
|
|
int status;
|
|
dataset_t getreply;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((get == NULL) || (data == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(&getreply, 0x0, sizeof(getreply));
|
|
|
|
ctx->reply.u.get = &getreply;
|
|
rc = xce_get_req(ctx->ep, get);
|
|
if (rc < 0) {
|
|
goto done;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
goto done;
|
|
}
|
|
|
|
if (status >= 0) {
|
|
*data = getreply.rows;
|
|
rc = getreply.nrows;
|
|
} else {
|
|
rc = status;
|
|
}
|
|
|
|
done:
|
|
/* Always clear the destination — including on the xce_get_req/cbuf_get
|
|
* failover error paths — so the single, untagged reply union never retains
|
|
* the address of this now-returning stack `getreply`. A later ctrlstatus
|
|
* reply would otherwise free that dead-stack pointer as a CtrlStatus*
|
|
* (bug-1077, ASan stack-use-after-return). */
|
|
ctx->reply.u.get = NULL;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int xce_get(xce_ctx_t* ctx, int table, void*** data)
|
|
{
|
|
/* clang-format off */
|
|
xce_match_t condition = {
|
|
.type = MatchCondition_condition_matchall
|
|
};
|
|
xce_get_t get = {
|
|
.table = table,
|
|
.condition = &condition
|
|
};
|
|
/* clang-format on */
|
|
|
|
return xce_get_with_condition(ctx, &get, data);
|
|
}
|
|
|
|
int xce_reslist(xce_ctx_t* ctx, xce_resource_t*** res)
|
|
{
|
|
int table = Data_data_resources;
|
|
|
|
return xce_get(ctx, table, (void***)res);
|
|
}
|
|
|
|
int xce_resval(xce_ctx_t* ctx, char* host, char* res, xce_resval_t*** vals)
|
|
{
|
|
int rc;
|
|
int status;
|
|
/* clang-format off */
|
|
xce_reskey_t reskey = {
|
|
.resname = res,
|
|
.hostname = host
|
|
};
|
|
xce_match_t condition = {
|
|
.type = MatchCondition_condition_matchreskey,
|
|
.condition = {
|
|
.reskey = &reskey
|
|
}
|
|
};
|
|
xce_get_t get = {
|
|
.table = Data_data_resvals,
|
|
.condition = &condition
|
|
};
|
|
dataset_t getreply = {
|
|
.table = Data_data_resvals,
|
|
.nrows = 0,
|
|
.rows = NULL
|
|
};
|
|
/* clang-format on */
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx->reply.u.get = &getreply;
|
|
rc = xce_get_req(ctx->ep, &get);
|
|
if (rc < 0) {
|
|
goto done;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
goto done;
|
|
}
|
|
|
|
if (status >= 0) {
|
|
*vals = (xce_resval_t**)getreply.rows;
|
|
rc = getreply.nrows;
|
|
} else {
|
|
rc = status;
|
|
}
|
|
|
|
done:
|
|
/* Clear on every path (incl. the failover error returns) so the shared
|
|
* reply union never keeps this stack `getreply` address — bug-1077. */
|
|
ctx->reply.u.get = NULL;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int xce_join(xce_ctx_t* ctx, int role, hostattrs_t* attrs)
|
|
{
|
|
int rc, status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_join_req(ctx->ep, role, attrs);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_workerlist(xce_ctx_t* ctx, xce_worker_t*** workers)
|
|
{
|
|
int table = Data_data_workers;
|
|
|
|
return xce_get(ctx, table, (void***)workers);
|
|
}
|
|
|
|
int xce_free_workers(int nworkers, xce_worker_t* workers) { return 0; }
|
|
|
|
int xce_jobdispatch(xce_ctx_t* ctx, xce_job_dispatch_t* info)
|
|
{
|
|
int rc, status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_jobdispatch_req(ctx->ep, info);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_notify(xce_ctx_t* ctx, xce_notify_data_t* data)
|
|
{
|
|
int rc;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_notify_req(ctx->ep, data);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int xce_jobsubmit(xce_ctx_t* ctx, xce_job_submit_t* submit, uint64_t* jobid)
|
|
{
|
|
int rc, status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_jobsubmit_req(ctx->ep, submit);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
if (status >= 0) {
|
|
*jobid = ctx->reply.u.jobid;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
int xce_joblist_ex(xce_ctx_t* ctx, int sched, xce_job_t*** jobs)
|
|
{
|
|
int table;
|
|
|
|
if (sched) {
|
|
table = Data_data_schedjobs;
|
|
} else {
|
|
table = Data_data_jobs;
|
|
}
|
|
|
|
return xce_get(ctx, table, (void***)jobs);
|
|
}
|
|
|
|
int xce_sched_joblist(xce_ctx_t* ctx, xce_job_t*** jobs)
|
|
{
|
|
return xce_joblist_ex(ctx, 1, jobs);
|
|
}
|
|
|
|
int xce_joblist(xce_ctx_t* ctx, xce_job_t*** jobs)
|
|
{
|
|
return xce_joblist_ex(ctx, 0, jobs);
|
|
}
|
|
|
|
int xce_varlist(xce_ctx_t* ctx, xce_config_t*** vars)
|
|
{
|
|
#if 0
|
|
int table = XCETables_conf;
|
|
|
|
return xce_get(ctx, table, (void***)vars);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int xce_queuelist(xce_ctx_t* ctx, xce_queue_t*** queues)
|
|
{
|
|
#if 0
|
|
int table = XCETables_queues;
|
|
|
|
return xce_get(ctx, table, (void***)queues);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int xce_free_queues(int nqueues, xce_queue_t* queues)
|
|
{
|
|
int i;
|
|
|
|
if (queues == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < nqueues; i++) {
|
|
FREEUP(queues[i].resreq);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int xce_group_configlist(xce_ctx_t* ctx, int group_type,
|
|
xce_group_config_t*** groups)
|
|
{
|
|
#if 0
|
|
int table;
|
|
|
|
switch (group_type) {
|
|
case 0:
|
|
table = XCETables_ugroups;
|
|
break;
|
|
default:
|
|
table = XCETables_hgroups;
|
|
break;
|
|
}
|
|
|
|
return xce_get(ctx, table, (void***)groups);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int xce_free_groups(int ngroups, xce_group_config_t* groups)
|
|
{
|
|
/* TODO */
|
|
return 0;
|
|
}
|
|
|
|
int xce_clusterconf(xce_ctx_t* ctx, xce_clusterconf_t** conf)
|
|
{
|
|
int rc, status;
|
|
|
|
if ((ctx == NULL) || (conf == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_clusterconf_req(ctx->ep);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
if (status < 0) {
|
|
return status;
|
|
}
|
|
|
|
MOVE_PTR((*conf), ctx->reply.u.clusterconf);
|
|
return 0;
|
|
}
|
|
|
|
int xce_ctrlstatus(xce_ctx_t* ctx, xce_ctrlstatus_t** status)
|
|
{
|
|
int rc, st;
|
|
|
|
if ((ctx == NULL) || (status == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_ctrlstatus_req(ctx->ep);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &st);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
if (st < 0) {
|
|
return st;
|
|
}
|
|
|
|
MOVE_PTR((*status), ctx->reply.u.ctrlstatus);
|
|
return 0;
|
|
}
|
|
|
|
int xce_update_resvals(xce_ctx_t* ctx, int count, xce_resval_t** vals)
|
|
{
|
|
int rc;
|
|
int status;
|
|
|
|
/* clang-format off */
|
|
xce_resval_list_t valist = {
|
|
.count = count,
|
|
.vals = vals
|
|
};
|
|
xce_update_data_t data = {
|
|
.table = Update_data_resvals,
|
|
.data = {
|
|
.resvals = &valist
|
|
}
|
|
};
|
|
/* clang-format on */
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_update_req(ctx->ep, &data);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_update_jusage(xce_ctx_t* ctx, uint64_t jobid, int npids, int* pids,
|
|
int ngids, int* pgids, int nusage, xce_rusage_t** usages)
|
|
{
|
|
int rc;
|
|
int status;
|
|
|
|
/* clang-format off */
|
|
xce_job_rusages_t rusages = {
|
|
.jobid = jobid,
|
|
.npids = npids,
|
|
.pids = pids,
|
|
.pgids = pgids,
|
|
.count = nusage,
|
|
.usages = usages
|
|
};
|
|
xce_update_data_t data = {
|
|
.table = Update_data_jobrusages,
|
|
.data = {
|
|
.jobrusages = &rusages }
|
|
};
|
|
/* clang-format on */
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_update_req(ctx->ep, &data);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_jobctrl(xce_ctx_t* ctx, xce_jobctrl_t* ctrl)
|
|
{
|
|
int rc;
|
|
int status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_jobctrl_req(ctx->ep, ctrl);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_queuectrl(xce_ctx_t* ctx, xce_queuectrl_t* ctrl)
|
|
{
|
|
int rc;
|
|
int status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_queuectrl_req(ctx->ep, ctrl);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_hostctrl(xce_ctx_t* ctx, xce_hostctrl_t* ctrl)
|
|
{
|
|
int rc;
|
|
int status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_hostctrl_req(ctx->ep, ctrl);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_runtask(xce_ctx_t* ctx, xce_taskrun_t* runinfo)
|
|
{
|
|
int rc;
|
|
int status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_runtask_req(ctx->ep, runinfo);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_set_pend_reason(xce_ctx_t* ctx, xce_preason_set_t* reasons)
|
|
{
|
|
int rc;
|
|
int status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_set_pend_reason_req(ctx->ep, reasons);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_clear_reason(xce_ctx_t* ctx, xce_clear_reason_t* reasons)
|
|
{
|
|
int rc;
|
|
int status;
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->state != XCE_CTX_RUN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_clear_reason_req(ctx->ep, reasons);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = cbuf_get(&(ctx->wbuf), &status);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int xce_jobinfo(xce_ctx_t* ctx, uint64_t jobid, xce_job_t** job)
|
|
{
|
|
/* clang-format off */
|
|
xce_match_t condition = {
|
|
.type = MatchCondition_condition_matchjid,
|
|
.condition = {
|
|
.jobid = jobid
|
|
}
|
|
};
|
|
xce_get_t get = {
|
|
.table = Data_data_jobs,
|
|
.condition = &condition
|
|
};
|
|
/* clang-format on */
|
|
int rc;
|
|
xce_job_t** jobs;
|
|
|
|
if ((ctx == NULL) || (job == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = xce_get_with_condition(ctx, &get, (void***)&jobs);
|
|
|
|
if (rc <= 0) {
|
|
return rc;
|
|
}
|
|
|
|
*job = jobs[0];
|
|
FREEUP(jobs);
|
|
|
|
return 1;
|
|
}
|