diff --git a/core/sched.cc b/core/sched.cc index 07801a291..af6719517 100644 --- a/core/sched.cc +++ b/core/sched.cc @@ -1309,6 +1309,14 @@ void thread::destroy() // waking state. void thread::wake_impl(detached_state* st, unsigned allowed_initial_states_mask) { + // Defend against a null detached_state: a wait_record whose backing thread + // is not resolvable in the waker's address space can yield a null st here. + // Dereferencing it would fault, and on a preemption-disabled wake path that + // fault aborts the instance (assert(preemptable()) in page_fault). Skip + // the wake. + if (!st) { + return; + } status old_status = status::waiting; trace_sched_wake(st->t); while (!st->st.compare_exchange_weak(old_status, status::waking)) { diff --git a/include/osv/wait_record.hh b/include/osv/wait_record.hh index d75c507b2..3ed291f77 100644 --- a/include/osv/wait_record.hh +++ b/include/osv/wait_record.hh @@ -35,7 +35,21 @@ public: explicit waiter(sched::thread *t) : t(t) { }; inline void wake() { - t.load(std::memory_order_relaxed)->wake_with_from_mutex([&] { t.store(nullptr, std::memory_order_release); }); + // A wait_record left linked in a condvar/mutex queue can be stale: it + // may already be woken (wake() stores null below), or its backing + // thread pointer may not be resolvable in the waker's address space, + // reading as null or a tiny-integer remnant. Dereferencing it faults, + // and on the preemption-disabled wake path that fault aborts the whole + // instance. Skip a thread pointer that is null or an obviously invalid + // low value (no sched::thread lives in the first page); a real thread + // lives high in the address space. This is deliberately narrow -- only + // clearly-bogus pointers are dropped -- so it never discards a + // legitimate wake. + sched::thread *w = t.load(std::memory_order_relaxed); + if (reinterpret_cast(w) < 0x1000UL) { + return; + } + w->wake_with_from_mutex([&] { t.store(nullptr, std::memory_order_release); }); } inline void wait() const {