From 81d8ed23e8e7cd7041d9973ebaf05f7435141a66 Mon Sep 17 00:00:00 2001 From: Peter M Date: Wed, 25 Feb 2026 13:31:39 +0100 Subject: [PATCH] Fix use-after-free in process scheduling lists When `do_spawn` fails after `context_new` has been called, `context_destroy` is invoked directly. `context_new` calls `globalcontext_init_process` which adds the context to both `processes_table` and `waiting_processes`, but `context_destroy` only removed from `processes_table`. The freed context's node remained linked in `waiting_processes`, causing a use-after-free corruption of the scheduling list. Fix by adding a spinlock-protected `list_remove` + `list_init` of `processes_list_head` in `context_destroy`. The `list_init` makes the node self-referential so that a second `list_remove` (from callers like `scheduler_terminate` that already do the removal before calling `context_destroy`) is a safe no-op. Also remove the now-redundant explicit removal in the native handler kill path of `scheduler_run`, since `context_destroy` handles it. Signed-off-by: Peter M --- src/libAtomVM/context.c | 7 +++++++ src/libAtomVM/scheduler.c | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index 0e35082ad1..9019227a31 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -140,6 +140,13 @@ void context_destroy(Context *ctx) struct ListHead *processes_table_list = synclist_wrlock(&ctx->global->processes_table); UNUSED(processes_table_list); + // Remove from scheduling lists (waiting/ready/running). + // list_remove on a self-referential (already removed + re-initialized) node is a safe no-op. + SMP_SPINLOCK_LOCK(&ctx->global->processes_spinlock); + list_remove(&ctx->processes_list_head); + list_init(&ctx->processes_list_head); + SMP_SPINLOCK_UNLOCK(&ctx->global->processes_spinlock); + list_remove(&ctx->processes_table_head); // Ensure process is not registered diff --git a/src/libAtomVM/scheduler.c b/src/libAtomVM/scheduler.c index a856d60012..f78574a388 100644 --- a/src/libAtomVM/scheduler.c +++ b/src/libAtomVM/scheduler.c @@ -287,9 +287,6 @@ Context *scheduler_run(GlobalContext *global) // process signal messages and also empty outer list to inner list. scheduler_process_native_signal_messages(result); if (UNLIKELY(result->flags & Killed)) { - SMP_SPINLOCK_LOCK(&global->processes_spinlock); - list_remove(&result->processes_list_head); - SMP_SPINLOCK_UNLOCK(&global->processes_spinlock); context_destroy(result); } else { if (mailbox_has_next(&result->mailbox)) { @@ -429,6 +426,7 @@ void scheduler_terminate(Context *ctx) SMP_SPINLOCK_LOCK(&ctx->global->processes_spinlock); context_update_flags(ctx, ~NoFlags, Killed); list_remove(&ctx->processes_list_head); + list_init(&ctx->processes_list_head); SMP_SPINLOCK_UNLOCK(&ctx->global->processes_spinlock); if (!ctx->leader) { context_destroy(ctx);