test: Cycle 29 — unexported memory, multi-value rejection, send boundary tests
- memory_not_exported_returns_missing_export: memory defined but not exported as "memory" - multi_value_module_rejected_by_engine: engine config rejects multi-value returns - send_dest_at_exact_memory_boundary: dest_ptr + 32 == mem_len succeeds - send_dest_one_past_memory_boundary_traps: dest_ptr + 32 > mem_len traps safely All 127 tests pass. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
9a890770d9
commit
13dfe82af8
1 changed files with 119 additions and 0 deletions
|
|
@ -4507,3 +4507,122 @@ fn send_with_overlapping_dest_and_payload() {
|
||||||
let msg = inbox.try_recv().expect("should receive overlapping send");
|
let msg = inbox.try_recv().expect("should receive overlapping send");
|
||||||
assert_eq!(msg.0.len(), 4, "payload should be 4 bytes");
|
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::<ByteMessage>().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::<ByteMessage>().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");
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue