fix(guest): take the zero-copy input via the safe get_private_input_slice - #898
Merged
Merged
Conversation
…lice 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.
diegokingston
approved these changes
Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Review fix on top of #886, targeting that branch so it lands inside it.
The zero-copy read is the right call —
get_private_input()really does copy thewhole input for nothing. But
syscalls::get_private_input_slicealready borrowsthe mapped region in place and returns
&'static [u8], andget_private_inputis literally that call plus a
to_vec(). So dropping to the slice is the entirewin, without the pointer plumbing:
Three things that buys beyond the line count:
syscalls.rskeeps the region layout and itsone
unsafeblock in a single place — the stated reasonget_private_input_sliceexists. Re-reading the length prefix in the guestduplicates layout knowledge that has to stay in step with the executor.
get_private_input_slicebounds the prefixby
MAX_PRIVATE_INPUT_SIZE;ef_io::read_inputreturns it raw. The executorrejects oversized inputs, so honest runs are byte-identical either way — but a
forged prefix built a slice reaching past the region instead of a bounded one.
Robustness, not soundness: private input is prover-chosen by definition.
ef_io::read_inputdocumentsbuf_ptras unspecified whenbuf_size == 0, and the previous code fed it tofrom_raw_partsregardless. Harmless in practice (the implementation alwayswrites it, and ethrex input is never empty), but not a contract to lean on.
bench_vs/lambda/recursionalready reads its blob exactly this way.The measured win should be unchanged — both forms skip the same
to_vec()— butit is worth re-running the cycle counts from #886 to confirm, since that number
is the whole point of the PR.
Verified locally
make executor/program_artifacts/rust/ethrex.elf— builds clean.cd tooling/ethrex-tests && cargo test --release -- --include-ignored --skip test_ethrex_real_block(CI's exact command) — 4 passed, 0 failed.
test_ethrexexecutes the rebuiltguest ELF and compares its public output against a native
execution_programrun, so the changed read path is covered.
rustfmt --checkclean.Not re-measured: the cycle deltas, and proving-level coverage
(
test_prove_ethrex_empty_blockis#[ignore]and no CI job selects it).