From 1789585ab540e9486af30a7fff2eb4b73859b60d Mon Sep 17 00:00:00 2001 From: Nia Waldvogel Date: Fri, 5 Dec 2025 17:19:25 -0500 Subject: [PATCH] runtime (wasm): scan the system stack This saves the system stack pointer into a global and uses it to scan. It should fix some use-after-free issues? --- src/internal/task/task_asyncify.go | 7 +++++++ src/runtime/arch_tinygowasm.go | 8 ++++++++ src/runtime/gc_stack_portable.go | 21 ++++++++++----------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go index 637a6b2237..546b6841b2 100644 --- a/src/internal/task/task_asyncify.go +++ b/src/internal/task/task_asyncify.go @@ -108,6 +108,10 @@ func (*stackState) unwind() func (t *Task) Resume() { // The current task must be saved and restored because this can nest on WASM with JS. prevTask := currentTask + if prevTask == nil { + // Save the system stack pointer. + saveStackPointer() + } t.gcData.swap() currentTask = t if !t.state.launched { @@ -123,6 +127,9 @@ func (t *Task) Resume() { } } +//go:linkname saveStackPointer runtime.saveStackPointer +func saveStackPointer() + //export tinygo_rewind func (*state) rewind() diff --git a/src/runtime/arch_tinygowasm.go b/src/runtime/arch_tinygowasm.go index c6e15b2771..0f61fe0082 100644 --- a/src/runtime/arch_tinygowasm.go +++ b/src/runtime/arch_tinygowasm.go @@ -73,6 +73,14 @@ func align(ptr uintptr) uintptr { //export tinygo_getCurrentStackPointer func getCurrentStackPointer() uintptr +// savedStackPointer is used to save the system stack pointer while in a goroutine. +var savedStackPointer uintptr + +// saveStackPointer is called by internal/task.state.Resume() to save the stack pointer before entering a goroutine from the system stack. +func saveStackPointer() { + savedStackPointer = getCurrentStackPointer() +} + // growHeap tries to grow the heap size. It returns true if it succeeds, false // otherwise. func growHeap() bool { diff --git a/src/runtime/gc_stack_portable.go b/src/runtime/gc_stack_portable.go index dbe6367c93..04162bb07a 100644 --- a/src/runtime/gc_stack_portable.go +++ b/src/runtime/gc_stack_portable.go @@ -29,16 +29,6 @@ type stackChainObject struct { // - The system stack (aka startup stack) is not heap allocated, so even // though it may be referenced it will not be scanned by default. // -// Therefore, we only need to scan the system stack. -// It is relatively easy to scan the system stack while we're on it: we can -// simply read __stack_pointer and __global_base and scan the area in between. -// Unfortunately, it's hard to get the system stack pointer while we're on a -// goroutine stack. But when we're on a goroutine stack, the system stack is in -// the scheduler which means there shouldn't be anything on the system stack -// anyway. -// ...I hope this assumption holds, otherwise we will need to store the system -// stack in a global or something. -// // The compiler also inserts code to store all globals in a chain via // stackChainStart. Luckily we don't need to scan these, as these globals are // stored on the goroutine stack and are therefore already getting scanned. @@ -50,9 +40,18 @@ func markStack() { // live. volatile.LoadUint32((*uint32)(unsafe.Pointer(&stackChainStart))) + // Scan the system stack. + var sysSP uintptr if task.OnSystemStack() { - markRoots(getCurrentStackPointer(), stackTop) + // We are on the system stack. + // Use the current stack pointer. + sysSP = getCurrentStackPointer() + } else { + // We are in a goroutine. + // Use the saved stack pointer. + sysSP = savedStackPointer } + markRoots(sysSP, stackTop) } // trackPointer is a stub function call inserted by the compiler during IR