Stream: git-wasmtime

Topic: wasmtime / Issue #900 Improve wasm handling for stack ove...


view this post on Zulip Wasmtime GitHub notifications bot (Mar 11 2020 at 23:17):

iximeow commented on Issue #900:

This is very similar to an issue we've been looking at addressing in lucet, where the "almost stack overflowed" case has similar fatal end results for us. The approach we've been most seriously considering looks much like this, though we're planning on keeping a guard page and only checking against some reserved stack space limit at the point we call into native code. This avoids needing instrumentation on each internal call, instead trading for a memory access, to get the stack limit from VMCtx.

In the lucet case, we'd like to avoid reserving an extra native thread(ish) of stack unconditionally, so we might tweak this approach a bit more. I've recently suggested that we reframe the approach as having an amount of stack space reserved for native calls. Then, in the case that the wasm stack doesn't have this much space available, we allocate a NATIVE_RESERVED_STACK_SIZE chunk of memory and do a native call there instead. In the happy path, we can do a native call on the same stack with the overhead of a memory access and compare. In the less happy path, we can try to proceed instead of aborting the program, assuming the system itself has memory available.

One concern we've run into is the case of mutual recursion between wasm code and native code. If this is a concern for wasmtime too? If it is, it sounds like you'd handle this by using a new stack limit when calling back into wasm code, so that makes sense.

I assume a stack overflow in native code would then be considered a native code error by virtue of not fitting within the reserved stack space? It looks like those would be program-ending faults still. One of the reasons we want lucet-runtime to continue handing segfaults in the stack guard page is to try isolating these errors, but if we end up needing to unwind native call frames to run destructors we might use more stack leading to an overflow when trying to unwind from an overflow...

view this post on Zulip Wasmtime GitHub notifications bot (Mar 12 2020 at 15:48):

alexcrichton commented on Issue #900:

@iximeow that sounds like a strategy that would work for this as well. I'm always iffy myself on tracking stack sizes, but the general idea is the same where whenever native code is running we try to provide a guarantee there's a "reasonable amount of stack space". I figured though that if this limit was ever blown we'd just return a wasm stack overflow trap rather than trying to allocate more native stack at the last second to keep running code.

I think we'd definitely want to handle mutually recursive wasm/native code, and my thinking there at lesat is that you might get some weird errors like shallow wasm invocations triggering stack overflow b/c a previous wasm module recursed so much, but as long as everything has pretty reasonable limits I think it'll probably turn out roughly ok.

I do agree yeah native stack overflow is still going to be a program-ending fault, and I'm mostly uncomfortable running sigsegv signals on the sigaltstack which is frequently too small, and if we didn't do that there'd be no way to have libstd's nice "you overflowed the stack" message as well.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2020 at 16:19):

alexcrichton commented on Issue #900:

For posterity here's a test which shows the bug we have today, and how we're "catching" segfaults in host code.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 21 2020 at 18:03):

sunfishcode closed Issue #900:

Currently wasmtime handles stack overflow in wasm code through the use of the segfault signal handler, relying on wasm code to hit the guard page to trigger a segfault which we the longjmp to recover from. Unfortunately, though, this has a consequence where it doesn't work well for "almost stack overflowed" wasm code which calls native imports.

For example let's say that a wasm module has an extremely deep call stack, but then it calls an imported function where then the imported function hits the guard page. This will trigger a segfault, and we still want this to be a somewhat recoverable situation! In this scenario though it's not actually save to longjmp over a bunch of Rust frames since it can skip some critical destructors.

I think in general we'll want to update wasmtime's handling of stack overflow in WebAssembly code to not rely on segfaults. In talking with some Spidermonkey folks, I think we'll want to switch to a scheme that looks something like:

This should help retain all our current speed (with sufficient tweaks/optimization) but also allow us to guarantee that native code imported by wasm always has a reasonable amount of stack space. This means that stack overflow in native code would never be recovered from (it'd abort the process as typical Rust programs do).


Last updated: Dec 23 2024 at 12:05 UTC