myxce/src/lib/xlist.c
Rongsong Shen 63de947305
fix(lib): zarray_fini/xlist_fini must reset caps/count, not just free data
A user-reported core dump showed xce_scheduler crashing with SIGSEGV in
its allocator thread: zarray_get_ex dereferencing a NULL array->data
inside match_job_host_limit_() (plugins/limit.c), reached via schedobj.c's
reset_sched_obj().

Root cause: zarray_fini() freed array->data and set it to NULL, but left
array->caps/count/pos at their stale pre-fini values instead of resetting
to the safe (0,0,0) state zarray_init() establishes. xlist_fini() had the
identical gap. Both structs' own bounds checks (`idx >= caps`) treat a
nonzero caps as a guarantee that data is a valid allocation -- a
guarantee broken for any caller that reuses the same struct after
_fini() without re-init.

schedobj.c's reset_sched_obj() does exactly that: it tears down a job's
own PERSISTENT schedobj (reused for the job's whole lifetime, not a
disposable scratch object) whenever the cluster's config version has
advanced, with no matching re-init. Once a host-limit match had grown
hlimits' capacity, and the config version bumped again while that job
sat PEND, the next allocation attempt for the same job dereferenced the
torn array -- exactly matching the reported backtrace. This same crash
class had already caused at least one earlier, never-successfully-root-
caused crash near the same call site (2026-08-17).

Fixed at the library-primitive level rather than reset_sched_obj()'s
call site: zarray_fini()/xlist_fini() now reset caps/count/pos to 0,
matching what _init() establishes. This makes every existing
_fini()-then-reuse caller correct by construction, with zero changes
needed at any call site. Audited every non-test zarray_fini()/
xlist_fini() call site first -- none relies on caps/count surviving
_fini(), so this is a pure safety improvement, not a behavior change.

RED confirmed by temporarily disabling both resets: new tests failed
cleanly ("Expected 0 Was 4" / "Expected 0 Was 2", the stale pre-fini
caps). GREEN confirmed restored: full lib suite 314/314, scheduler
suite 77/77, zero regressions.

bug-1817.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
2026-09-16 02:31:53 +00:00

181 lines
3.1 KiB
C

#include <errno.h>
#include <stdlib.h>
#include "utils.h"
#include "xlist.h"
int xlist_init(xlist_t* l, int delta)
{
if ((l == NULL) || (delta <= 0)) {
return -EINVAL;
}
l->delta = delta;
l->data = NULL;
l->count = 0;
l->caps = 0;
return 0;
}
static int xlist_ensure(xlist_t* l)
{
void* p;
int nc, i;
if (l->count < l->caps) {
return 1;
}
nc = l->caps + l->delta;
p = (void*)realloc(l->data, nc * sizeof(void*));
if (p == NULL) {
return -ENOMEM;
}
l->data = p;
for (i = l->caps; i < nc; i++) {
l->data[i] = NULL;
}
l->caps = nc;
return 1;
}
int xlist_store(xlist_t* l, void* data)
{
int rc;
int idx;
if (l == NULL) {
return -EINVAL;
}
rc = xlist_ensure(l);
if (rc < 0) {
return rc;
}
idx = l->count;
l->data[idx] = data;
l->count++;
return idx;
}
void* xlist_get(xlist_t* l, int idx)
{
if ((l == NULL) || (idx < 0)) {
errno = EINVAL;
return NULL;
}
if (idx >= l->caps) {
errno = 0;
return NULL;
}
return (void*)l->data[idx];
}
void xlist_clear(xlist_t* l)
{
if (l == NULL) {
return;
}
l->count = 0;
}
int xlist_iter(xlist_t* l, xlist_iter_fn_t fn, void* data)
{
int i;
if ((l == NULL) || (fn == NULL)) {
return -EINVAL;
}
for (i = 0; i < l->count; i++) {
(*fn)((void*)l->data[i], data);
}
return 0;
}
int xlist_del(xlist_t* l, void* data)
{
int i, j;
if (l == NULL) {
return -EINVAL;
}
j = -1;
i = 0;
while (i < l->count) {
if (l->data[i] == data) {
for (j = i + 1; j < l->count; j++) {
l->data[j - 1] = l->data[j];
}
l->count--;
} else {
i++;
}
}
return 1;
}
void* xlist_find(xlist_t* l, xlist_find_fn_t fn, ...)
{
int i;
va_list ap;
void* it = NULL;
if ((l == NULL) || (fn == NULL)) {
errno = EINVAL;
return NULL;
}
va_start(ap, fn);
for (i = 0; i < l->count; i++) {
va_list ap1;
int rc;
va_copy(ap1, ap);
rc = fn((void*)l->data[i], ap1);
va_end(ap1);
if (rc) {
it = (void*)l->data[i];
break;
}
}
return it;
}
/*
* xlist_fini - tears down `l`'s backing storage, leaving it in the SAME
* safe, empty state xlist_init() establishes (data=NULL, caps=0, count=0)
* -- NOT just data=NULL with caps/count still holding their pre-fini
* values. See zarray_fini()'s own doc comment (zarray.c) for the exact
* live crash this class of bug caused (schedobj.c's reset_sched_obj(),
* the analogous xlist_t field obj->glimits) -- xlist_get()'s bounds check
* has the identical shape (`idx >= l->caps`), so a stale-nonzero caps with
* data==NULL is just as exploitable here. No existing caller relies on
* caps/count surviving an xlist_fini() call.
*/
void xlist_fini(xlist_t* l)
{
if (l == NULL) {
return;
}
FREEUP(l->data);
l->count = 0;
l->caps = 0;
}