fix(apiserver): don't free client config C-strings that the C library retains (bug-1501)

tryConnect built the xce_client_config_t with C.CString(host/from/key), then a
defer freed all three. But xcecli_ctx_init stores the config POINTER
(ctx->conf = config), not a copy — client_failover.c dereferences
config->masters/from/key for the client's ENTIRE lifetime (e.g.
xce_failover_next_target strncpy's a master host on every write-fail/redirect).
Freeing the strings left config->masters[0] dangling → ASAN heap-use-after-free
(strncpy in the background C thread) after a few submits.

Fix: keep the three C strings for the process lifetime (tryConnect runs once at
startup, gCtx is a process-global); free them only on the init-failure path.

Verified: 6 consecutive job submits, apiserver stays up, 0 ASAN errors
(previously crashed after ~2-3 submits).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
lei.chen 2026-08-04 08:59:06 +00:00
parent 5b47ee603f
commit ff2d784ca9
2 changed files with 33 additions and 5 deletions

View file

@ -80,11 +80,6 @@ func tryConnect(host string, port int, from, key string) error {
cAddr := C.CString(host)
cFrom := C.CString(from)
cKey := C.CString(key)
defer func() {
C.free(unsafe.Pointer(cAddr))
C.free(unsafe.Pointer(cFrom))
C.free(unsafe.Pointer(cKey))
}()
var cConfig C.xce_client_config_t
cConfig.nmasters = 1
@ -95,8 +90,23 @@ func tryConnect(host string, port int, from, key string) error {
rc := C.xce_backend_init(&cConfig, &gCtx)
if rc < 0 {
// Init failed — the client did not retain the config, so the strings
// are safe to free here (avoids a leak on the error path).
C.free(unsafe.Pointer(cAddr))
C.free(unsafe.Pointer(cFrom))
C.free(unsafe.Pointer(cKey))
return fmt.Errorf("xce_backend_init failed on %s:%d: %d", host, port, int(rc))
}
// bug-1501: xcecli_ctx_init stores the config POINTER (ctx->conf = config),
// not a copy — client_failover.c dereferences config->masters/from/key for
// the client's ENTIRE lifetime (e.g. xce_failover_next_target strncpy's a
// master host on every write-fail/redirect). The three C strings above must
// therefore outlive this call: freeing them here (as the old defer did)
// left config->masters[0] dangling, and a later failover read the freed
// host → ASAN heap-use-after-free (strncpy, background C thread). They are
// deliberately leaked for the process lifetime — tryConnect runs once at
// startup and gCtx is a process-global.
return nil
}