Skip to content

sched: skip waking a null or invalid thread pointer - #1484

Open
gburd wants to merge 1 commit into
cloudius-systems:masterfrom
gburd:pr/wake-null-guard
Open

sched: skip waking a null or invalid thread pointer#1484
gburd wants to merge 1 commit into
cloudius-systems:masterfrom
gburd:pr/wake-null-guard

Conversation

@gburd

@gburd gburd commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

The wake path dereferences two pointers without checking them, and it runs
with preemption disabled, so a null dereference there does not fault
recoverably -- it trips assert(sched::preemptable()) in page_fault and
aborts the whole instance.

  • waiter::wake() loads the sched::thread * out of a wait_record and
    immediately calls wake_with_from_mutex() on it.
  • thread::wake_impl() dereferences the detached_state * it is handed
    (trace_sched_wake(st->t) and the CAS loop on st->st).

A wait_record left linked in a condvar/mutex queue can be stale: it may
already have been woken (wake() stores null), or its backing thread may not
be resolvable in the waker's context, in which case the stored pointer reads
as null or a small-integer remnant. A live sched::thread never lives in the
first page of the address space, so such a value is unambiguously not a thread.

This change guards both paths:

  • in waiter::wake(), skip a thread pointer below 0x1000 (null or an
    obviously invalid low value);
  • in thread::wake_impl(), return early on a null detached_state.

The guard is deliberately narrow -- only clearly-bogus pointers are dropped --
so it never discards a legitimate wake. Behavior is unchanged for every valid
waiter; the only effect is turning a fatal abort on a stale/torn record into a
no-op wake, which is the correct outcome (there is nothing live to wake).

Two small files, no functional change on the happy path.

waiter::wake() unconditionally dereferences the thread pointer stored in a
wait_record, and thread::wake_impl() dereferences the detached_state it is
handed. Both run on the wake path with preemption disabled, so if either
pointer is null a page fault trips assert(sched::preemptable()) in page_fault
and aborts the whole instance instead of faulting recoverably.

A wait_record left linked in a condvar/mutex queue can be stale: it may already
have been woken (wake() stores null), or its backing thread may not be
resolvable in the waker's address space, in which case the stored pointer reads
as null or a tiny-integer remnant. A live sched::thread never lives in the
first page of the address space, so such a value is unambiguously not a thread.

Guard both paths: in waiter::wake() skip a thread pointer below 0x1000 (null or
an obviously invalid low value), and in thread::wake_impl() return early on a
null detached_state. The guard is deliberately narrow -- only clearly-bogus
pointers are dropped -- so it never discards a legitimate wake. Behavior is
unchanged for every valid waiter; the only effect is turning a fatal abort on a
stale/torn record into a no-op wake.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant