Stream: git-wasmtime

Topic: wasmtime / issue #6926 Support for not relying on signals...


view this post on Zulip Wasmtime GitHub notifications bot (Aug 29 2023 at 18:07):

rockwotj opened issue #6926:

Currently, on unix platforms, Wasmtime relies on a few signals to operate, here's a list of the ones I'm aware of:

SIGSEGV: Used for bounds checking when static memory is used. AFAIK using dynamic memory disables the need to handle this signal
SIGFPE: Only used on x86_64 for division by zero.
SIGILL: The implementation of unreachable wasm instructions or other explicit traps are implemented by using ud2 on x86_64 and udf on aarch64, this triggers a SIGILL.

Link to Zulip Conversation about this topic.

Benefit

Some environments/frameworks also rely on signals (or block them) which can cause issues with Wasmtime. Wasmtime currently registers signals lazily for the whole process when the first engine is created and other library code etc, could also be registering/unregistering signal handlers and these don't always cleanup properly or yield signal control to wasmtime correctly. This happens for me with the seastar.io framework on Linux, but I'm told Macos has also had it's own set of woes with signal handling.

Being able to disable signal handling would allow wasmtime to be used in these sorts of environments.

An additional positive of this note would be that it allows more testing of Cranelift, as it's my understanding some of the trap handling code is not run in tests/fuzzing.

Implementation

I believe nothing would need to change for removing the need to handle SIGSEGV, as for SIGFPE, we'd need to generate code for division by zero checks and explicitly trap.

That leaves SIGILL/ud2/udf, which seems to be the lion's share of the work here.

This is mostly just copying the notes from @cfallin :big_smile:

Three things to consider with the code to replace ud2:

Essentially we will replace our current usages of ud2 with a jmp to an offset of vmctx which holds the address of a small assembly function that would read the return address then call into the runtime with the return address as an argument. This return address would then be used to lookup the underlying trap code for that location. Using a pointer in vmctx allows for portability when code is loaded directly from disk.

One thing to look out for when doing this work is to determine if the size blowup from ud2 (2 bytes) to jmp offset+reg (8 bytes) is acceptable or not. Some small analysis of generated assembly should be able to easily determine that however before embarking on this task.

view this post on Zulip Wasmtime GitHub notifications bot (Aug 29 2023 at 18:12):

fitzgen commented on issue #6926:

I think even with dynamic memories, if we have spectre guards enabled, then we rely on loading a null pointer to raise a signal that is caught by the runtime. This is because we "dedupe" the spectre guard bounds check and the dynamic memory's bounds check. We could stop doing that when in this no-signals mode, but do note that that would make the slowdown of dynamic bounds checks go from ~1.5x to closer to ~2x compared to using virtual memory guard pages.

An additional positive of this note would be that it allows more testing of Cranelift, as it's my understanding some of the trap handling code is not run in tests/fuzzing.

Note that this is just for the cranelift-specific fuzzing and tests.

Wasmtime fuzzing does exercise trapping.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 13 2024 at 17:09):

alexcrichton closed issue #6926:

Currently, on unix platforms, Wasmtime relies on a few signals to operate, here's a list of the ones I'm aware of:

SIGSEGV: Used for bounds checking when static memory is used. AFAIK using dynamic memory disables the need to handle this signal
SIGFPE: Only used on x86_64 for division by zero.
SIGILL: The implementation of unreachable wasm instructions or other explicit traps are implemented by using ud2 on x86_64 and udf on aarch64, this triggers a SIGILL.

Link to Zulip Conversation about this topic.

Benefit

Some environments/frameworks also rely on signals (or block them) which can cause issues with Wasmtime. Wasmtime currently registers signals lazily for the whole process when the first engine is created and other library code etc, could also be registering/unregistering signal handlers and these don't always cleanup properly or yield signal control to wasmtime correctly. This happens for me with the seastar.io framework on Linux, but I'm told Macos has also had it's own set of woes with signal handling.

Being able to disable signal handling would allow wasmtime to be used in these sorts of environments.

An additional positive of this note would be that it allows more testing of Cranelift, as it's my understanding some of the trap handling code is not run in tests/fuzzing.

Implementation

I believe nothing would need to change for removing the need to handle SIGSEGV, as for SIGFPE, we'd need to generate code for division by zero checks and explicitly trap.

That leaves SIGILL/ud2/udf, which seems to be the lion's share of the work here.

This is mostly just copying the notes from @cfallin :big_smile:

Three things to consider with the code to replace ud2:

Essentially we will replace our current usages of ud2 with a jmp to an offset of vmctx which holds the address of a small assembly function that would read the return address then call into the runtime with the return address as an argument. This return address would then be used to lookup the underlying trap code for that location. Using a pointer in vmctx allows for portability when code is loaded directly from disk.

One thing to look out for when doing this work is to determine if the size blowup from ud2 (2 bytes) to jmp offset+reg (8 bytes) is acceptable or not. Some small analysis of generated assembly should be able to easily determine that however before embarking on this task.


Last updated: Dec 23 2024 at 12:05 UTC