fix(scheduler): activate virtual-clock logging for sim/REPL sessions (Req 5 AC 3)

sched_sim_driver_start() now registers the core's tlog format function and
marks its core active for logging right after the virtual clock is seeded
(xce_core_register_tlog_format()/xce_core_set_active_for_logging()) --
every log line for the rest of the run shows frozen/scaled replay time
instead of real wall-clock time, matching what an operator debugging
against a replay's own timeline needs. sched_sim_driver_run() deactivates
on all 7 of its return paths, since there's no sim-driver teardown
function and g_active_core_for_logging would otherwise dangle past the
caller's stack frame.

Also fixes a genuine pre-existing ASAN stack-use-after-return (bug-1598)
the new deactivation test exposed: sim_picker_thread_fn is spawned but, by
design, never joined/cancelled, so a stack-local `xce_sched_sim_driver_t
sim` in test_sim_event_loop.c was always a latent UAF once its owning test
function returned -- just not previously triggered by any prior test
ordering. Switched the three affected fixtures in that file to `static`
storage (safe: sched_sim_driver_start() memsets the whole struct on entry,
and none of the three call sites are re-entrant).
This commit is contained in:
Rongsong Shen 2026-08-19 11:00:33 +00:00
parent 25e962309f
commit b450a6806b
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
4 changed files with 164 additions and 2 deletions

View file

@ -509,7 +509,23 @@ test_run_processes_multiple_sessions_in_order_and_no_longer_returns_enosys(void)
char path[PATH_MAX];
FILE* f;
sched_sim_args_t args;
xce_sched_sim_driver_t sim;
/* static, not stack-local (2026-08-19 ASAN finding): sched_sim_driver_
* run() spawns sim_picker_thread_fn (sim_loop.c) on a real pthread that
* is, by this codebase's own documented design, "never joined or
* cancelled: no sim-driver teardown/fini function exists yet" (that
* function's own header comment) -- it loops forever on
* cbuf_get(&sim.core.wbuf, ...), reading `sim` for the lifetime of the
* process. A stack-local `sim` here would make that a genuine stack-
* use-after-return the instant this test function returns (a later
* test's own stack frame reusing the same address triggers ASAN's
* detector -- reproduced live when a new test was added between this
* one and its neighbor). `static` storage sidesteps it cheaply: the
* zombie thread keeps reading valid (if stale) memory instead of a
* freed stack frame. This does not fix the underlying thread-lifecycle
* gap (a real fix needs an actual sim-driver teardown function, out of
* scope here) -- it only makes this test suite safe to run under ASAN
* regardless of test ordering/stack layout. */
static xce_sched_sim_driver_t sim;
int rc;
f = write_header_and_snapshot(path, sizeof(path), "_run");
@ -553,6 +569,62 @@ test_run_processes_multiple_sessions_in_order_and_no_longer_returns_enosys(void)
"returns_enosys: PASS\n");
}
/* ---------------------------------------------------------------------- */
/* Test 2b (design review follow-up, Req 5 AC 3): sched_sim_driver_run() */
/* must deactivate tlog's virtual-clock logging on completion -- it has no */
/* fini()/teardown function, and sched_sim_driver_start() (called */
/* internally) activates logging via g_active_core_for_logging = &sim.core */
/* (core_clock.c's module-static, white-box #included above). Left active */
/* past this call, `sim` (this test's own local/stack struct) would be a */
/* dangling reference the moment this function returns -- and in a real */
/* process running MULTIPLE sim sessions back to back (or, in this test */
/* binary, any later test calling tlog after this one returns), every */
/* subsequent log line would read a stale/dangling core pointer. Seed a */
/* bogus non-NULL sentinel first (simulating "a prior run left this set") */
/* so the assertion genuinely proves deactivation happened, not just that */
/* nothing ever touched it. */
/* ---------------------------------------------------------------------- */
static void
test_run_deactivates_tlog_virtual_clock_logging_on_completion(void)
{
char path[PATH_MAX];
FILE* f;
sched_sim_args_t args;
/* static -- see test_run_processes_multiple_sessions_in_order_and_no_
* longer_returns_enosys()'s own comment on this same pattern above: the
* picker thread sched_sim_driver_run() spawns is never joined, so a
* stack-local `sim` here is a stack-use-after-return once this
* function returns (this is in fact the exact test whose addition
* first surfaced that ASAN finding live). */
static xce_sched_sim_driver_t sim;
int rc;
f = write_header_and_snapshot(path, sizeof(path), "_tlogdeactivate");
TEST_ASSERT_NOT_NULL(f);
append_end_of_stream_marker(f);
fclose(f);
memset(&args, 0, sizeof(args));
args.replay_path = path;
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
args.sim_speed = 0;
xce_core_set_active_for_logging((xce_sched_core_t*)0x1); /* bogus sentinel */
rc = sched_sim_driver_run(&sim, &args);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_NULL_MESSAGE(
g_active_core_for_logging,
"sched_sim_driver_run() must deactivate tlog virtual-clock logging "
"before returning -- otherwise later log calls dereference this "
"now-out-of-scope sim.core");
printf("test_run_deactivates_tlog_virtual_clock_logging_on_completion:"
" PASS\n");
}
/* ---------------------------------------------------------------------- */
/* Test 3 (bug-1567 regression): a job/queue already present in the */
/* INITIAL snapshot -- as opposed to arriving later via a ReplayEvent -- */
@ -631,7 +703,10 @@ static FILE* write_header_and_snapshot_with_pending_job(char* out_path,
static char* run_sim_and_capture_stdout(const char* path, int* out_rc)
{
sched_sim_args_t args;
xce_sched_sim_driver_t sim;
/* static -- same never-joined-picker-thread hazard documented on
* test_run_processes_multiple_sessions_in_order_and_no_longer_returns_
* enosys()'s own `sim` declaration above. */
static xce_sched_sim_driver_t sim;
char tmp[] = "/tmp/xce_simloop_run_XXXXXX";
int fd = mkstemp(tmp);
int saved_stdout;
@ -742,6 +817,7 @@ int main(void)
test_process_replay_event_advances_virtual_clock_to_exact_event_ts_in_file_order);
RUN_TEST(
test_run_processes_multiple_sessions_in_order_and_no_longer_returns_enosys);
RUN_TEST(test_run_deactivates_tlog_virtual_clock_logging_on_completion);
RUN_TEST(
test_job_present_only_in_initial_snapshot_still_reaches_the_picker);