From 1387bce56273ca3a72f4e52681fe8803a5e97d85 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 08:59:12 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Cycle=209=20=E2=80=94=20send=20overflow?= =?UTF-8?q?/boundary,=20cross-thread=20relay,=20property=20fuzz=20all=20se?= =?UTF-8?q?nd=20args?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 153 ++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index a172b58..01393e3 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -1736,3 +1736,156 @@ fn ten_wasm_actors_chain_relay() { let received = inbox.try_recv().expect("10-actor chain should deliver"); assert_eq!(received.0, final_payload); } + +// ── send payload overflow: payload_ptr + payload_len wraps ────────────────── + +#[test] +fn send_with_payload_range_overflow_traps_actor_survives() { + // Guest calls send with payload_ptr=1, payload_len=i32::MAX. + // checked_add(payload_len) overflows → trap. Actor must survive. + 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 i32 i32) + i32.const 0 ;; dest_ptr (valid) + i32.const 1 ;; payload_ptr + i32.const 2147483647 ;; payload_len = i32::MAX → overflow + 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![1])).unwrap(); + rt.tick(); + + // Actor survives — send another + rt.send_to(addr, ByteMessage(vec![2])).unwrap(); + rt.tick(); +} + +// ── send with payload at exact memory end ─────────────────────────────────── + +#[test] +fn send_payload_at_exact_memory_end_works() { + // Guest writes a byte at offset 65535 (last byte of 1-page memory) and + // sends it as a 1-byte payload. payload_end = 65535 + 1 = 65536 == mem_len. + // This should succeed (not exceed 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 256) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Write marker at last byte + (i32.store8 (i32.const 65535) (i32.const 88)) + ;; Send: dest from message, payload = last byte of memory + local.get $ptr ;; dest_ptr + i32.const 65535 ;; payload_ptr (last byte) + 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 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 received = inbox.try_recv().expect("exact-end payload should succeed"); + assert_eq!(received.0, vec![88]); +} + +// ── Multi-worker: two WASM actors cross-thread messaging ──────────────────── + +#[test] +fn wasm_actors_communicate_across_threads() { + // Two WASM echo actors on a 2-worker runtime. Actor A echoes to Actor B, + // Actor B echoes to inbox. Verifies cross-thread WASM messaging. + let engine = SharedEngine::new().unwrap(); + let wasm_bytes = guest_wasm("echo"); + + let actor_a = WasmActorBuilder::new(engine.clone(), wasm_bytes.clone()).build().unwrap(); + let actor_b = WasmActorBuilder::new(engine, wasm_bytes).build().unwrap(); + + let config = RuntimeConfig { + num_threads: 2, + ..RuntimeConfig::default() + }; + let rt = Runtime::new(config); + let inbox = rt.new_inbox::().unwrap(); + + let addr_a = rt.spawn(actor_a).unwrap(); + let addr_b = rt.spawn(actor_b).unwrap(); + + // A receives [addr_b | [inbox_addr | "cross-thread"]] + // A echoes [inbox_addr | "cross-thread"] to B + // B echoes "cross-thread" to inbox + let final_payload = b"cross-thread"; + let msg_for_b = framed_msg(inbox.addr(), final_payload); + let msg_for_a = framed_msg(&addr_b, &msg_for_b.0); + + rt.send_to(addr_a, msg_for_a).unwrap(); + + let handle = rt.run().unwrap(); + std::thread::sleep(std::time::Duration::from_millis(100)); + + let received = inbox.try_recv().expect("cross-thread relay should deliver"); + assert_eq!(received.0, final_payload); + + handle.shutdown(); +} + +// ── Property: any send arguments never crash the host ──────────────────────── + +proptest! { + #[test] + fn prop_any_send_args_never_crash_host( + dest_ptr in -100i32..70000, + payload_ptr in -100i32..70000, + payload_len in -100i32..70000, + ) { + // Regardless of what arguments the guest passes to swactor.send, + // the host import should either succeed or trap — never panic. + let wat = format!(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 i32 i32) + i32.const {dest_ptr} + i32.const {payload_ptr} + i32.const {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(); + + // Should never panic regardless of send args + rt.send_to(addr, ByteMessage(vec![0u8; 64])).unwrap(); + rt.tick(); + + // Actor should still accept messages (not poisoned) + let result = rt.send_to(addr, ByteMessage(vec![1])); + prop_assert!(result.is_ok(), "actor must survive any send args: dest={dest_ptr} payload_ptr={payload_ptr} len={payload_len}"); + } +}