myxce/src/scheduler
Rongsong Shen bc3f76b42e
fix(scheduler): invalidate stale builtin_fcfs picker cursors across calls (bug-1686)
User-reported live crash: xce_scheduler SIGSEGV in fcfs_cursor_enter_queue
(builtin_fcfs.c), called from builtin_fcfs_next_job <- sched_job_picker
(picker thread).

priv->cursor/priv->job_cursor (fcfs_plugin_data_t) are raw ordq_item_t*
pointers that persist across SEPARATE next_job() calls, each with its own
independent priv->lock acquisition/release. The existing mutual-exclusion
fix (task 2.4) correctly protects the cursor walk from corruption DURING
one locked call, but does nothing to stop a LATER call from dereferencing
a node a properly-locked, INTERVENING call already freed.
on_queue_update()'s re-sort (remove old ordq_item_t node, insert a fresh
one via ordq_put) is exactly such a free -- with only one queue in the
deployment, the picker's cursor is essentially always parked on it
between calls (sched_queue_is_ready/sched_job_is_ready are unconditional
true stubs today, so the cursor never advances away while jobs remain),
making a queue config update land in that window trivially reachable.

Fixed by invalidating (NULLing) whichever cursor points at a node right
before that node is freed, at all three sites that can free a node the
cursors might reference: on_queue_update's reuse-and-resort path
(priv->cursor + priv->job_cursor in lockstep), builtin_fcfs_on_job_state_change's
!JOB_IS_PEND removal, and fcfs_remove_job_everywhere's cross-queue removal
loop (both job_cursor only, via an ordq_peek-then-invalidate-then-delete
pattern). Always safe: next_job() treats a NULL cursor as "no more
candidates this round", and begin_session() resets both cursors fresh
every round regardless -- worst case is a bounded one-round delay, never
a crash or a permanently lost job.

New test_builtin_fcfs_cursor_uaf.c reproduces the bug fully single-threaded
(next_job() releases the lock between calls, so no real concurrency is
needed to hit the window). RED-phase independently confirmed a real ASAN
heap-use-after-free with the exact same call chain as the live crash
(freed by on_queue_update, read by next_job()); GREEN confirms 0 failures
and no job permanently lost across the invalidation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-25 08:49:43 +00:00
..
include fix(scheduler): mirror plugin_begin_session/plugin_end_session into sim-mode driver (bug-1681) 2026-08-25 06:40:14 +00:00
src fix(scheduler): invalidate stale builtin_fcfs picker cursors across calls (bug-1686) 2026-08-25 08:49:43 +00:00
testsuite fix(scheduler): invalidate stale builtin_fcfs picker cursors across calls (bug-1686) 2026-08-25 08:49:43 +00:00
CLAUDE.md docs(scheduler): document driver-equivalence invariant, scope decision-parity fix spec 2026-08-22 02:52:00 +00:00
meson.build fix(scheduler): extend core-isolation nm-audit to core_events.c/core_session.c/core_clock.c (Req 5 AC 1/2) 2026-08-19 11:00:41 +00:00