test: Cycle 13 — SIMD rejection, garbage address, call_indirect, alloc-zero-len, custom sections
Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
84911869c8
commit
186af06ec2
1 changed files with 203 additions and 0 deletions
|
|
@ -2401,3 +2401,206 @@ fn double_stop_is_idempotent() {
|
||||||
let result = rt.stop_actor(addr);
|
let result = rt.stop_actor(addr);
|
||||||
assert!(result.is_err(), "stopping already-stopped actor should error");
|
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