fuzz: ensures the alternate stack for signal handling (#1864)

Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
This commit is contained in:
Takeshi Yoneda
2023-12-13 07:07:34 -08:00
committed by GitHub
parent f46d5b943d
commit cf426e7aa9
6 changed files with 341 additions and 21 deletions

View File

@@ -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)

View File

@@ -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",
]

View File

@@ -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"

View File

@@ -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);
}
}

View 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();
}

View File

@@ -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")
}
}