--- id: "bug-1532" status: "fixed" status_updated: "2026-09-24" status_note: "" component: "apiserver" severity: "crash" timestamp: "2026-08-12T00:20:00.000Z" last_seen: "2026-09-19T09:00:00Z" occurrences: 1 files: ["src/frontend/apiserver/backend/clib.go"] tags: ["use-after-free", "cgo", "apiserver", "failover", "component-failover-adoption", "task-4.2"] related: [] legacy: true --- # bug-1532: apiserver clib.go tryConnect(): use-after-free on ctx->conf->masters[i] (host address string) on… ## Description apiserver clib.go tryConnect(): use-after-free on ctx->conf->masters[i] (host address string) on every post-initial-connect failover rotation attempt ## Environment _Not recorded (migrated from buglog.json)._ ## Reproduction _Not recorded (migrated from buglog.json)._ ## Root cause tryConnect() allocated cAddr/cFrom/cKey via C.CString(), built a Go-function-local xce_client_config_t pointing at them, called xce_backend_init() once, then deferred C.free() on all three strings immediately after that single call returned. xcecli_ctx_init() (src/lib/client.c) stores ctx->conf = cfg as a RAW pointer (never copies the struct), and client_failover.c dereferences ctx->conf->masters[cur_master] on EVERY subsequent failover rotation attempt (client_failover.c:120,386), not just the initial connect -- so any reconnect/rotation after the first tryConnect() call returned read freed memory. A second, independent hazard: cConfig itself was a Go-local var; ctx->conf retained a raw pointer to it invisible to Go GC once tryConnect() returned. (Note: from/key were NOT actually part of this UAF -- xce_ctx_init() strdup()s them immediately at ctx-init time, so freeing cFrom/cKey was safe in isolation; only the masters[] host string and the client_config_t struct address itself were genuinely unsafe.) ## Fix component-failover-adoption task 4.2: replaced tryConnect() and the dual Go-level connect loops entirely with buildClusterConfig()+RegisterToBackend(), backed by two new package-level (process-lifetime, GC-rooted) vars gClusterCfg (C.xce_cluster_config_t) and gClientCfg (C.xce_client_config_t). Host strings now live in gClusterCfg.candidates[i].hostname (fixed char[256] arrays, no CString/free at all -- xce_cluster_config_to_client_config() aliases pointers directly into this storage). from/key point at gClusterCfg.identity[0]/key[0] (also fixed arrays) rather than separate CStrings. No C.CString/C.free is used anywhere in the connect path except the transient peers-string argument to xce_parse_peers(), which is safely freed right after that one call since xce_parse_peers() copies into gClusterCfg internally. ## Verification _Not recorded (migrated from buglog.json)._ ## References _Not recorded (migrated from buglog.json)._