fix(controller): string resvals own their bytes -- rejoin use-after-free killed new primary (bug-1851)

update_val_res() memcpy'd the xce_val_t into w->host.resvals, so the
"type"/"model" string resources aliased the interned xstrings owned by
w->host.attrs. rejoin_worker() drops the old attrs strings, then
apply_join_state() -> resync_arm_round() resolves the previous join's
still-outstanding round, firing the worker hooks before
add_hostattrs_resvals() re-seeds resvals; the apiserver hook's
collect_resval() strdup()ed the freed hostmodel. Live: a newly promoted
primary died ~8s after election during an HA soak.

A string resval now stores a private strdup (taken before the old copy is
freed), and the owning arrays free their entries: host.resvals gets
kill_resval (replacing the FIXME NULL killer) and c->sharedinst's
resval_obj_killer (previously a no-op) frees the string and the value --
both also leaked every xce_val_t before. Also closes the same alias for
worker-reported string resvals and config values.

Regression test: test_rejoin_worker_string_resvals_survive_outstanding_round
(RED reproduced the live ASAN stack; GREEN after). meson test 109/109.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-24 08:41:55 +00:00
parent ac033f4783
commit ca4db13a79
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
5 changed files with 199 additions and 4 deletions

View file

@ -1561,11 +1561,19 @@ static void queue_obj_killer(void* arg)
}
}
/* bug-1851: c->sharedinst owns each xce_val_t (update_val_res, worker.c)
* and, for a string value, a private strdup()ed copy. */
static void resval_obj_killer(void* arg)
{
if (arg == NULL) {
xce_val_t* v = (xce_val_t*)arg;
if (v == NULL) {
return;
}
if (v->which == Value_u_svalue) {
FREEUP(v->u.svalue);
}
free(v);
}
static void config_obj_killer(void* arg)