From 5ef942e0488e0c504e8f1adb813e14111589938d Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 4 Aug 2026 15:07:11 -0300 Subject: [PATCH] fix(guest): take the zero-copy input via the safe get_private_input_slice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The zero-copy read is the right call, but it hand-rolls what `syscalls::get_private_input_slice` already does: borrow the mapped private-input region in place and hand back `&'static [u8]`, no copy and no allocation. `get_private_input` is that same call plus a `to_vec()`, so dropping to the slice is the whole win without the pointer plumbing. Three things that buys: - No raw pointers in guest code. `syscalls.rs` deliberately keeps the region layout and its one `unsafe` block in a single place — that is why `get_private_input_slice` exists. Re-reading the length prefix in the guest duplicates layout knowledge that has to stay in step with the executor. - Restores the length-prefix clamp. `get_private_input_slice` bounds the prefix by `MAX_PRIVATE_INPUT_SIZE`; `ef_io::read_input` returns it raw. The executor rejects oversized inputs, so honest runs are identical — but a forged prefix built a slice reaching past the region instead of a bounded one. - Drops a dependency on unspecified behavior. `ef_io::read_input` documents `buf_ptr` as unspecified when `buf_size == 0`, and the previous code fed it to `from_raw_parts` regardless. Harmless in practice (the implementation always writes it, and ethrex input is never empty), but not a contract to lean on. `bench_vs/lambda/recursion` already reads its blob this way. --- executor/programs/rust/ethrex/src/main.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/executor/programs/rust/ethrex/src/main.rs b/executor/programs/rust/ethrex/src/main.rs index 4d42a804e..8154978cf 100644 --- a/executor/programs/rust/ethrex/src/main.rs +++ b/executor/programs/rust/ethrex/src/main.rs @@ -5,15 +5,12 @@ use lambda_vm_ethrex_crypto::LambdaVmEcsmCrypto; use rkyv::rancor::Error; pub fn main() { - // Zero-copy private input: `ef_io::read_input` returns a pointer+len into - // the memory-mapped input region (host pre-loads it before execution), so - // rkyv deserializes straight from the input. `get_private_input()` would - // `to_vec()` the whole input first — a full extra copy plus one large - // allocation (~52k cycles on a 20-tx block). - let mut input_ptr: *const u8 = core::ptr::null(); - let mut input_len: usize = 0; - unsafe { lambda_vm_syscalls::ef_io::read_input(&mut input_ptr, &mut input_len) }; - let input = unsafe { core::slice::from_raw_parts(input_ptr, input_len) }; + // Zero-copy private input: borrow the memory-mapped input region in place + // (the host pre-loads it before execution) so rkyv deserializes straight + // out of it. `get_private_input()` is this same slice plus a `to_vec()` — + // a full extra copy and one large allocation (~50k cycles on a 20-tx + // block). + let input = lambda_vm_syscalls::syscalls::get_private_input_slice(); let input = rkyv::from_bytes::(input).unwrap(); // LambdaVM crypto provider, defined in the lambda_vm repo and injected here // (so crypto changes don't require an ethrex PR — see `crypto/ethrex-crypto`).