myxce/src/controller/myconf.c
Rongsong Shen b08e392eeb fix(config): serialize load_primary_config against concurrent invocations (bug-1476)
myconf->lock (mylock_init/fini wiring) had landed but load_primary_config()
itself was left in its RED-phase reproduction state (unguarded, decoding
directly into the shared myconf->cluster_config field), so the fix's own
regression test failed on resume: thread B raced through while thread A was
parked mid-call.

Wraps the whole free-decode-publish sequence in myconf->lock (acquired first,
released on every return path) and decodes into a local cluster_config_t*
first, publishing into myconf->cluster_config only after full decode and
validation succeed -- closing the UAF/double-free window a live 3-node ASan
soak caught (two concurrent handle_repl_config_push deliveries for the same
standby racing two load_primary_config calls).

Verified 4/4 clean full-suite runs (1036 tests, 0 failures) including the
specific race test.
2026-07-28 08:13:54 +00:00

167 lines
4.1 KiB
C

#define _GNU_SOURCE
#include <errno.h>
#include <inttypes.h>
#include <openssl/sha.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlog.h>
#include "byteorder.h"
#include "config_repl.h"
#include "configobj.h"
#include "controller.capnp.h"
#include "myconf.h"
#include "utils.h"
#include "xce/cluster_def.h"
#include "xce/types.h"
int add_config(void* c, char* name, char* value)
{
cluster_t* cls = (cluster_t*)c;
xce_config_t cvar = { .name = name, .value = value };
xce_config_t* cv = NULL;
cv = makeConfig(cls->configmgr, &cvar);
if (cv == NULL) {
return -errno;
}
return 0;
}
struct cluster_configuration {
cluster_config_t* cluster;
};
static int config_fini(int type, int situation, void* data, va_list ap)
{
struct cluster_configuration* cf = (struct cluster_configuration*)data;
switch (situation) {
case HOOK_AFTER_EVENT:
if (cf->cluster != NULL) {
free_ClusterConfig(cf->cluster);
FREEUP(cf->cluster);
}
break;
default:
break;
}
return 0;
}
static int cluster_hook_fun(int type, int situation, int detail, void* data,
va_list ap)
{
if (detail == HOOK_DETAIL_CLUSTER_SHUTDOWN) {
return config_fini(type, situation, data, ap);
}
return 0;
}
#define SET_DEFAULT_IF_UNSET(var, unset, val) \
do { \
if ((var) == (unset)) { \
(var) = (val); \
} \
} while (0)
static void fill_default_cluster_config(cluster_config_t* c)
{
SET_DEFAULT_IF_UNSET(c->heartbeat_interval, 0, 5);
SET_DEFAULT_IF_UNSET(c->fetch_job_interval, 0, 10);
}
static int load_cluster_config_from_file(char* file, cluster_config_t** config)
{
struct capn c;
ClusterConfig_ptr p;
FILE* fp;
int rc = 0;
fp = fopen(file, "rb");
if (fp == NULL) {
rc = -errno;
perror("fopen");
return rc;
}
capn_init_fp(&c, fp, 0);
p.p = capn_getp(capn_root(&c), 0, 1);
decode_ClusterConfig_ptr(config, p);
capn_free(&c);
fclose(fp);
return 0;
}
int load_cluster_config(cluster_t* c,
struct cluster_configuration** cluster_config)
{
cluster_config_t* cfg;
int rc;
*cluster_config
= (struct cluster_configuration*)calloc(1, sizeof(*cluster_config));
if ((*cluster_config) == NULL) {
return -ENOMEM;
}
rc = load_cluster_config_from_file(c->myconf->conffile,
&((*cluster_config)->cluster));
if (rc < 0) {
ERROR("failed to load configuration file `%s'", c->myconf->conffile);
return rc;
}
if ((*cluster_config)->cluster == NULL) {
return -errno;
}
cfg = (*cluster_config)->cluster;
fill_default_cluster_config(cfg);
xce_add_hook(&(c->hooksys), XCE_CONTROLLER_HOOK_CLUSTER,
(hook_fn_t)cluster_hook_fun, (*cluster_config));
return 0;
}
cluster_config_t* get_default_conf(void* cl)
{
cluster_t* cls = (cluster_t*)cl;
cluster_config_t* cfg = NULL;
int rc = load_cluster_config_from_file(cls->myconf->conffile, &cfg);
if (rc < 0 || cfg == NULL) {
PERROR("load_cluster_config_from_file");
return NULL;
}
fill_default_cluster_config(cfg);
return cfg;
}
/* ── myconf_fini ────────────────────────────────── */
void myconf_fini(myconf_t *myconf)
{
if (!myconf)
return;
free(myconf->conffile);
myconf->conffile = NULL;
if (myconf->cluster_config) {
free_ClusterConfig(myconf->cluster_config);
free(myconf->cluster_config);
myconf->cluster_config = NULL;
}
mylock_fini(&myconf->lock); /* bug-1476 */
}