diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 9e74699..5bde586 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -4507,3 +4507,122 @@ fn send_with_overlapping_dest_and_payload() { let msg = inbox.try_recv().expect("should receive overlapping send"); assert_eq!(msg.0.len(), 4, "payload should be 4 bytes"); } + +// ── Memory defined but not exported as "memory" ───────────────────────────── + +#[test] +fn memory_not_exported_returns_missing_export() { + // Module defines memory internally but doesn't export it with the name "memory". + let wat = r#" + (module + (memory 1) + (func (export "alloc") (param i32) (result i32) i32.const 0) + (func (export "handle") (param i32 i32)) + ) + "#; + let wasm = wat::parse_str(wat).unwrap(); + let engine = SharedEngine::new().unwrap(); + let result = WasmActorBuilder::new(engine, wasm).build(); + // instantiation itself may fail because link_send requires memory export, + // OR build may succeed but get_memory returns None → MissingExport + assert!(result.is_err(), "missing memory export should be rejected"); + match result.err().unwrap() { + WasmActorError::MissingExport("memory") => {} // expected + WasmActorError::Wasmtime(_) => {} // also acceptable — linker can't resolve memory + other => panic!("unexpected error: {other}"), + } +} + +// ── Multi-value module rejected by sandboxed engine ───────────────────────── + +#[test] +fn multi_value_module_rejected_by_engine() { + // Module uses multi-value returns (disabled in SharedEngine config). + 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 0) + (func (export "handle") (param i32 i32)) + (func $multi (result i32 i32) i32.const 1 i32.const 2) + ) + "#; + let wasm = wat::parse_str(wat).unwrap(); + let engine = SharedEngine::new().unwrap(); + let result = WasmActorBuilder::new(engine, wasm).build(); + // Engine has multi_value disabled, so compilation should fail + assert!(result.is_err(), "multi-value module should be rejected"); +} + +// ── Send import reads dest at exact end of linear memory ──────────────────── + +#[test] +fn send_dest_at_exact_memory_boundary() { + // Guest calls swactor.send with dest_ptr such that dest_ptr + 32 == memory size. + // This should succeed because it's exactly in bounds. + 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 4096) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Copy 32-byte address from message to end of memory - 32 + ;; 65536 - 32 = 65504 + (memory.copy (i32.const 65504) (local.get $ptr) (i32.const 32)) + ;; Send with dest at very end of memory, payload at 4096 + (call $send (i32.const 65504) (i32.const 4096) (i32.const 1)) + ) + ) + "#; + 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::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"X")).unwrap(); + rt.tick(); + + let msg = inbox.try_recv().expect("send at exact boundary should succeed"); + assert_eq!(msg.0.len(), 1); +} + +// ── Send dest one byte past memory boundary (OOB) ────────────────────────── + +#[test] +fn send_dest_one_past_memory_boundary_traps() { + // Guest calls swactor.send with dest_ptr = memory_size - 31, so dest_ptr + 32 + // exceeds memory. The send import should return an error (which becomes a trap). + 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 4096) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; dest_ptr = 65505, so dest_end = 65505 + 32 = 65537 > 65536 + (call $send (i32.const 65505) (i32.const 4096) (i32.const 1)) + ) + ) + "#; + 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::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"Y")).unwrap(); + rt.tick(); + + // Send traps → outbox cleared → handle returns Err → no message delivered + assert!(inbox.try_recv().is_none(), "OOB send should trap, no delivery"); + + // Actor should survive (trap caught by handle) + rt.send_to(addr, framed_msg(inbox.addr(), b"Z")).unwrap(); + rt.tick(); + // This time no OOB send, but the module always tries the OOB send, so still trapped + assert!(inbox.try_recv().is_none(), "same module always traps"); +}