fuzz: ensures the alternate stack for signal handling (#1864)
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
This commit is contained in:
1
Makefile
1
Makefile
@@ -258,6 +258,7 @@ clean: ## Ensure a clean build
|
||||
fuzz_timeout_seconds ?= 10
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
@cd internal/integration_test/fuzz && cargo test
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run no_diff --sanitizer=none -- -rss_limit_mb=8192 -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run memory_no_diff --sanitizer=none -- -rss_limit_mb=8192 -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run validation --sanitizer=none -- -rss_limit_mb=8192 -max_total_time=$(fuzz_timeout_seconds)
|
||||
|
||||
142
internal/integration_test/fuzz/Cargo.lock
generated
142
internal/integration_test/fuzz/Cargo.lock
generated
@@ -2,6 +2,18 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.58"
|
||||
@@ -23,6 +35,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
@@ -32,6 +50,22 @@ dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.1.3"
|
||||
@@ -40,7 +74,7 @@ checksum = "c9a577516173adb681466d517d39bd468293bc2c2a16439375ef0f35bba45f3d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -63,9 +97,12 @@ checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
@@ -84,7 +121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.0",
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -120,34 +157,56 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.40"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.20"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
@@ -160,6 +219,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.1"
|
||||
@@ -167,26 +237,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.34.1"
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f14a94e06a3e2ed1af4e80cac712fed883142019ebe33c3899fd1b5e8550df9d"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d135e8940b69dbee0f5b0a0be9c1cd6fa8b71d774904c13a3fcfc5dc265e43d"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-smith"
|
||||
version = "0.12.20"
|
||||
version = "0.12.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c368a04ec656f45cf36c860dca97654d94679b260f1f1f3ef6a5bb1e43ad4fd"
|
||||
checksum = "4d9a642a2aa8a998228a247036d0f34470a07afc146231bd5c22cc61b8b51e73"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"flagset",
|
||||
"indexmap 2.0.0",
|
||||
"leb128",
|
||||
"wasm-encoder",
|
||||
"wasmparser 0.114.0",
|
||||
"wasmparser 0.117.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -200,10 +276,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.114.0"
|
||||
version = "0.117.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ef211410dcb08b037eb6d197b2398f8ef9d635c5dc5598d0dfda32094315ea3"
|
||||
checksum = "9b206de0c992af9f0b51ef2fb9455623e0a19eb68f172cd8ba9cd0e46637f5ab"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.3",
|
||||
"indexmap 2.0.0",
|
||||
"semver",
|
||||
]
|
||||
@@ -222,7 +299,30 @@ dependencies = [
|
||||
name = "wazero-fuzz-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"libc",
|
||||
"libfuzzer-sys",
|
||||
"nix",
|
||||
"wasm-smith",
|
||||
"wasmprinter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
@@ -12,6 +12,9 @@ cargo-fuzz = true
|
||||
libfuzzer-sys = "0.4.7"
|
||||
wasm-smith = "0.12.20"
|
||||
wasmprinter = "0.2.39"
|
||||
libc = "0.2"
|
||||
nix = "0.23.0"
|
||||
ctor = "0.2.6"
|
||||
|
||||
[[bin]]
|
||||
name = "memory_no_diff"
|
||||
|
||||
@@ -15,3 +15,69 @@ extern "C" {
|
||||
#[allow(dead_code)]
|
||||
pub fn validate(binary_ptr: *const u8, binary_size: usize);
|
||||
}
|
||||
|
||||
use ctor::ctor;
|
||||
use libc::SIGSTKSZ;
|
||||
use nix::libc::{sigaltstack, stack_t};
|
||||
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[ctor]
|
||||
/// Sets up the separate stack for signal handlers, and sets the SA_ONSTACK flag for signals that are handled by libFuzzer
|
||||
/// https://github.com/llvm/llvm-project/blob/8eff5704829ba5edd28754fd9ec7665b34fde22a/compiler-rt/lib/fuzzer/FuzzerUtilPosix.cpp#L117-L141
|
||||
/// in order to ensure that Go's stacks won't get corrupted accidentally.
|
||||
///
|
||||
/// This is necessary due to the undocumented requirement/behavior of Go runtime, and for detail,
|
||||
/// see the detailed comments in `tests/sigstack.rs`.
|
||||
fn setup_sig_handlers() {
|
||||
set_signal_stack();
|
||||
set_sa_on_stack(Signal::SIGABRT);
|
||||
set_sa_on_stack(Signal::SIGALRM);
|
||||
set_sa_on_stack(Signal::SIGBUS);
|
||||
set_sa_on_stack(Signal::SIGFPE);
|
||||
set_sa_on_stack(Signal::SIGILL);
|
||||
set_sa_on_stack(Signal::SIGINT);
|
||||
set_sa_on_stack(Signal::SIGSEGV);
|
||||
set_sa_on_stack(Signal::SIGTERM);
|
||||
set_sa_on_stack(Signal::SIGXFSZ);
|
||||
set_sa_on_stack(Signal::SIGUSR1);
|
||||
set_sa_on_stack(Signal::SIGUSR2);
|
||||
}
|
||||
|
||||
/// Sets the SA_ONSTACK flag for the given signal.
|
||||
fn set_sa_on_stack(sig: Signal) {
|
||||
let old_action = unsafe {
|
||||
let tmp = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty());
|
||||
sigaction(sig, &tmp).unwrap()
|
||||
};
|
||||
// Create a new SigAction with the SA_ONSTACK flag added.
|
||||
let new_flags = old_action.flags() | SaFlags::SA_ONSTACK;
|
||||
let new_action = SigAction::new(old_action.handler(), new_flags, old_action.mask());
|
||||
unsafe {
|
||||
sigaction(sig, &new_action).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the separate stack for signal handlers.
|
||||
fn set_signal_stack() {
|
||||
// Allocate a new stack for signal handlers to run on.
|
||||
const STACK_SIZE: usize = SIGSTKSZ * 2;
|
||||
let mut stack = vec![0u8; STACK_SIZE];
|
||||
|
||||
let stack_ptr = stack.as_mut_ptr();
|
||||
|
||||
let signal_stack = stack_t {
|
||||
ss_sp: stack_ptr as *mut libc::c_void,
|
||||
ss_flags: 0,
|
||||
ss_size: STACK_SIZE,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
if sigaltstack(&signal_stack, null_mut()) != 0 {
|
||||
panic!("Failed to set alternate signal stack");
|
||||
}
|
||||
|
||||
// Leak the stack vector to prevent it from being dropped.
|
||||
std::mem::forget(stack);
|
||||
}
|
||||
}
|
||||
|
||||
69
internal/integration_test/fuzz/fuzz/tests/sigstack.rs
Normal file
69
internal/integration_test/fuzz/fuzz/tests/sigstack.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
extern crate libc;
|
||||
extern crate nix;
|
||||
|
||||
use libc::SIGSTKSZ;
|
||||
use libc::{pthread_kill, pthread_self, SIGUSR1};
|
||||
use nix::sys::signal;
|
||||
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
const STACK_SIZE: usize = SIGSTKSZ * 4;
|
||||
|
||||
#[test]
|
||||
fn main() {
|
||||
unsafe {
|
||||
let sa = SigAction::new(
|
||||
SigHandler::SigAction(handler),
|
||||
// Set SA_ONSTACK to ensure the signal handler runs on the alternate stack.
|
||||
// The alternate stack is prepared by the Go runtime if there's not the one by the host C program
|
||||
// via the sigaltstack syscall. However if this flag is not set, which happens when
|
||||
// the host C program not having intention to deal with signals gracefully (e.g. stack overflow),
|
||||
// the signal handler (installed by either C program or Go runtime) will run on
|
||||
// the "current stack". That is problematic when a signal handling happens during execution
|
||||
// of wazevo function because it uses "Go allocated" stack.
|
||||
//
|
||||
// On the other hand, this is more of a general problem for any C program that uses Go as a library,
|
||||
// not limited to wazevo, when it does not not install sig handlers with SA_ONSTACK. That means
|
||||
// any Gorountime stack could result in being used during signal handling, which can potentially
|
||||
// cause any memory corruption. I would say such C program is using Go library in a dangerous way.
|
||||
//
|
||||
// To reproduce the failure in wazevo, Use SaFlags::empty() and wazevoapi.StackGuardCheckEnabled=true.
|
||||
//
|
||||
// Note that this only happens a Go program is compiled as c-archive or c-shared. If it is
|
||||
// used normally, the signal handlers are installed on each signal by the Go runtime, which
|
||||
// sets SA_ONSTACK and proper alternate stack, hence there's no way the current stack is used
|
||||
// during singal handling.
|
||||
SaFlags::SA_ONSTACK,
|
||||
SigSet::empty(),
|
||||
);
|
||||
|
||||
if let Err(err) = sigaction(signal::SIGUSR1, &sa) {
|
||||
panic!("Failed to set signal handler: {}", err);
|
||||
}
|
||||
|
||||
let main_thread_id = pthread_self();
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
pthread_kill(main_thread_id, SIGUSR1);
|
||||
});
|
||||
test_signal_stack();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn handler(_: libc::c_int, _: *mut libc::siginfo_t, _: *mut libc::c_void) {
|
||||
// Declare a large local array to use the stack space.
|
||||
let mut large_array: [u8; 1024] = [0; 1024];
|
||||
|
||||
// Use the array to prevent compiler optimizations from removing it.
|
||||
for i in 0..large_array.len() {
|
||||
large_array[i] = i as u8;
|
||||
}
|
||||
if large_array[100] != 100 {
|
||||
panic!("large_array[0] != 0");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn test_signal_stack();
|
||||
}
|
||||
@@ -2,14 +2,22 @@ package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func main() {}
|
||||
@@ -81,3 +89,76 @@ func newCompilerConfig() wazero.RuntimeConfig {
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
//export test_signal_stack
|
||||
func test_signal_stack() {
|
||||
if runtime.GOARCH != "arm64" { // TODO: amd64.
|
||||
return
|
||||
}
|
||||
// (module
|
||||
// (func (export "long_loop")
|
||||
// (loop
|
||||
// global.get 0
|
||||
// i32.eqz
|
||||
// if ;; label = @1
|
||||
// unreachable
|
||||
// end
|
||||
// global.get 0
|
||||
// i32.const 1
|
||||
// i32.sub
|
||||
// global.set 0
|
||||
// br 0
|
||||
// )
|
||||
// )
|
||||
// (global (;0;) (mut i32) i32.const 2147483648)
|
||||
// )
|
||||
bin := binaryencoding.EncodeModule(&wasm.Module{
|
||||
TypeSection: []wasm.FunctionType{{}},
|
||||
FunctionSection: []wasm.Index{0},
|
||||
GlobalSection: []wasm.Global{{
|
||||
Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true},
|
||||
Init: wasm.ConstantExpression{
|
||||
Opcode: wasm.OpcodeI32Const,
|
||||
Data: leb128.EncodeInt32(math.MaxInt32),
|
||||
},
|
||||
}},
|
||||
ExportSection: []wasm.Export{{Type: wasm.ExternTypeFunc, Name: "long_loop", Index: 0}},
|
||||
CodeSection: []wasm.Code{
|
||||
{
|
||||
Body: []byte{
|
||||
wasm.OpcodeLoop, 0,
|
||||
wasm.OpcodeGlobalGet, 0,
|
||||
wasm.OpcodeI32Eqz,
|
||||
wasm.OpcodeIf, 0,
|
||||
wasm.OpcodeUnreachable,
|
||||
wasm.OpcodeEnd,
|
||||
wasm.OpcodeGlobalGet, 0,
|
||||
wasm.OpcodeI32Const, 1,
|
||||
wasm.OpcodeI32Sub,
|
||||
wasm.OpcodeGlobalSet, 0,
|
||||
wasm.OpcodeBr, 0,
|
||||
wasm.OpcodeEnd,
|
||||
wasm.OpcodeEnd,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
ctx := context.Background()
|
||||
config := wazero.NewRuntimeConfigInterpreter()
|
||||
wazevo.ConfigureWazevo(config)
|
||||
r := wazero.NewRuntimeWithConfig(ctx, config)
|
||||
module, err := r.Instantiate(ctx, bin)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer func() {
|
||||
if err = module.Close(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = module.ExportedFunction("long_loop").Call(ctx)
|
||||
if !strings.Contains(err.Error(), "unreachable") {
|
||||
panic("long_loop should be unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user