bin-runner #36
1 changed files with 203 additions and 0 deletions
|
|
@ -2401,3 +2401,206 @@ fn double_stop_is_idempotent() {
|
|||
let result = rt.stop_actor(addr);
|
||||
assert!(result.is_err(), "stopping already-stopped actor should error");
|
||||
}
|
||||
|
||||
// ── Disabled features: SIMD module rejected by sandboxed engine ─────────────
|
||||
|
||||
#[test]
|
||||
fn module_using_disabled_simd_is_rejected() {
|
||||
// The engine disables SIMD. A module using v128 SIMD types should
|
||||
// fail to compile or instantiate.
|
||||
let wat = r#"
|
||||
(module
|
||||
(memory (export "memory") 1)
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||
(func (export "handle") (param i32 i32)
|
||||
;; v128.const is a SIMD instruction
|
||||
v128.const i32x4 0 0 0 0
|
||||
drop
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let result = wat::parse_str(wat);
|
||||
// If wat parses it, try to compile with the sandboxed engine
|
||||
match result {
|
||||
Ok(wasm) => {
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let build_result = WasmActorBuilder::new(engine, wasm).build();
|
||||
assert!(build_result.is_err(), "SIMD module should be rejected by sandboxed engine");
|
||||
}
|
||||
Err(_) => {
|
||||
// wat parser itself rejects SIMD — that's also fine
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Garbage address in send: any 32 bytes accepted ──────────────────────────
|
||||
|
||||
#[test]
|
||||
fn send_with_garbage_address_bytes_silently_fails() {
|
||||
// Guest sends to an address that's 32 random/garbage bytes.
|
||||
// The runtime can't route to it — ctx.send() returns Err, which is
|
||||
// silently dropped. Actor survives.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
;; Pre-fill offset 0-31 with garbage (0xDE repeated)
|
||||
(data (i32.const 0) "\de\ad\be\ef\de\ad\be\ef\de\ad\be\ef\de\ad\be\ef\de\ad\be\ef\de\ad\be\ef\de\ad\be\ef\de\ad\be\ef")
|
||||
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||
(func (export "handle") (param i32 i32)
|
||||
;; Send to garbage address at offset 0
|
||||
i32.const 0 ;; dest_ptr (garbage address from data segment)
|
||||
i32.const 32 ;; payload_ptr
|
||||
i32.const 1 ;; payload_len
|
||||
call $send
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
rt.send_to(addr, ByteMessage(vec![42])).unwrap();
|
||||
rt.tick(); // send to garbage addr fails silently
|
||||
|
||||
// Actor survives
|
||||
rt.send_to(addr, ByteMessage(vec![99])).unwrap();
|
||||
rt.tick();
|
||||
}
|
||||
|
||||
// ── Indirect call: guest uses call_indirect for handle logic ────────────────
|
||||
|
||||
#[test]
|
||||
fn guest_using_call_indirect_works() {
|
||||
// Guest uses a function table and call_indirect to invoke a function
|
||||
// that calls send. Verifies table-based dispatch works in the sandbox.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(type $send_sig (func (param i32)))
|
||||
|
||||
;; A function that sends 1 byte from offset 200 using the dest at the param
|
||||
(func $do_send (param $dest_ptr i32)
|
||||
(i32.store8 (i32.const 200) (i32.const 55))
|
||||
local.get $dest_ptr
|
||||
i32.const 200
|
||||
i32.const 1
|
||||
call $send
|
||||
)
|
||||
|
||||
(table 1 funcref)
|
||||
(elem (i32.const 0) $do_send)
|
||||
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; Call the function at table index 0 via call_indirect
|
||||
local.get $ptr
|
||||
(call_indirect (type $send_sig) (i32.const 0))
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), b"indirect")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let received = inbox.try_recv().expect("indirect call should deliver message");
|
||||
assert_eq!(received.0, vec![55]);
|
||||
}
|
||||
|
||||
// ── alloc returns 0 with len=0: subtle Ok(0) guard behavior ─────────────────
|
||||
|
||||
#[test]
|
||||
fn alloc_returns_zero_for_zero_length_message_succeeds() {
|
||||
// The guard `Ok(0) if len > 0 => return` only triggers when len > 0.
|
||||
// For a zero-length message, alloc returning 0 should fall through and
|
||||
// handle(0, 0) should be called. This tests the subtle conditional.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(global $called (mut i32) (i32.const 0))
|
||||
|
||||
(func (export "alloc") (param i32) (result i32)
|
||||
i32.const 0 ;; Always return 0
|
||||
)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; Mark that handle was called
|
||||
(global.set $called (i32.const 1))
|
||||
;; Write marker and send it
|
||||
(i32.store8 (i32.const 200) (i32.const 77))
|
||||
;; Use offset 100 as dest (will be zeroes = invalid addr, but that's fine)
|
||||
i32.const 100 ;; dest_ptr (zeroes)
|
||||
i32.const 200 ;; payload_ptr
|
||||
i32.const 1 ;; payload_len
|
||||
call $send
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
// Send empty message — alloc returns 0, len == 0, so guard doesn't trigger.
|
||||
// handle(0, 0) should be called.
|
||||
rt.send_to(addr, ByteMessage(vec![])).unwrap();
|
||||
rt.tick();
|
||||
|
||||
// Then send a non-empty message — alloc returns 0, len > 0, guard triggers.
|
||||
// handle should NOT be called. Actor survives.
|
||||
rt.send_to(addr, ByteMessage(vec![1, 2, 3])).unwrap();
|
||||
rt.tick();
|
||||
|
||||
// Actor still alive
|
||||
rt.send_to(addr, ByteMessage(vec![])).unwrap();
|
||||
rt.tick();
|
||||
}
|
||||
|
||||
// ── Module with custom sections: should build successfully ──────────────────
|
||||
|
||||
#[test]
|
||||
fn module_with_custom_section_builds_and_works() {
|
||||
// WASM modules can have custom sections. The builder should ignore them.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
(i32.store8 (i32.const 200) (i32.const 99))
|
||||
local.get $ptr
|
||||
i32.const 200
|
||||
i32.const 1
|
||||
call $send
|
||||
)
|
||||
(@custom "my_section" "hello")
|
||||
)
|
||||
"#;
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), b"custom")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let received = inbox.try_recv().expect("module with custom section should work");
|
||||
assert_eq!(received.0, vec![99]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue