--- id: "bug-343" status: "fixed" status_updated: "2026-09-24" status_note: "" component: "controller" severity: "crash" timestamp: "2026-06-14T04:34:32+00:00" last_seen: "2026-09-19T09:00:00Z" occurrences: 1 files: ["src/controller/testsuite/test_demotion_convergence.c"] tags: ["asan", "use-after-free", "ha", "rejoin", "delaymgr", "test-harness", "ha-convergence-recovery", "task-3.2"] related: [] legacy: true --- # bug-343: AddressSanitizer: heap-use-after-free in ha_rejoin_reconcile_fn (ha_convergence.c:236) during… ## Description AddressSanitizer: heap-use-after-free in ha_rejoin_reconcile_fn (ha_convergence.c:236) during test_rejoin_second_call_no_second_restart ## Environment _Not recorded (migrated from buglog.json)._ ## Reproduction _Not recorded (migrated from buglog.json)._ ## Root cause dc_run_pending_task peeked tasks.head and ran entry(args) WITHOUT dequeuing. The once-guard correctly makes the 2nd ha_rejoin_reconcile schedule no new task, so head still pointed at the first (already-run) task whose body had freed its heap ctx (args). The 2nd dc_run_pending_task re-ran that task on the freed ctx. The convergence task bodies free their own args (production delaymgr killer frees only the delay_task_t node, never args; production execv never re-runs a one-shot). ## Fix Made dc_run_pending_task DEQUEUE via ordq_get(&mgr->tasks, NULL), run the body, then free(t)/free(it) — mirroring the real delaymgr worker (ordq_get -> runner_fn -> FREEUP for runcount==1). A 2nd call now returns DC_NO_TASK as production would. Production ha_rejoin_reconcile/ha_rejoin_reconcile_fn unchanged (the body-frees-ctx pattern is correct). ## Verification _Not recorded (migrated from buglog.json)._ ## References _Not recorded (migrated from buglog.json)._