security: P0/P1 audit fixes (C-2, H-2)#1
Open
LucienSong wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
Addresses two high-severity security audit findings by (1) preventing free CPU DoS in the ML-DSA-65 batch verification precompile via upfront gas charging + batch-size limiting, and (2) removing overflow panics from attacker-controlled stack operands in the interpreter and hard-failing unknown PQVERIFY algorithm IDs.
Changes:
- Precompile: parse batch header up front, cap
n_sigs(MAX_BATCH_SIGNATURES), compute/charge total gas before doing any batch verification work, and addBatchTooLarge/GasOverflowerrors + regression tests. - Interpreter: replace panicking
U256::to::<T>()conversions inBLOCKHASHandPQVERIFYwithsaturating_to::<T>(), addInvalidPqAlgo(u8)error, and add regression tests for both cases.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| crates/pqvm-precompiles/src/lib.rs | Enforces batch limits and charges batch-verify gas before verification to prevent free CPU work; adds new error variants and regression tests. |
| crates/pqvm-interpreter/src/lib.rs | Prevents overflow panics in BLOCKHASH/PQVERIFY operand decoding and rejects unknown PQ algorithm IDs; adds targeted regression tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+614
to
+617
| // saturating_to: a U256 > u64::MAX saturates to u64::MAX which | ||
| // exceeds any plausible block number; the DB returns B256::ZERO | ||
| // for unknown heights, matching EVM BLOCKHASH semantics. | ||
| let n = self.pop()?.saturating_to::<u64>(); |
C-2: charge batch precompile gas before verification loop; cap n_sigs - Add MAX_BATCH_SIGNATURES = 256; reject larger headers before any work - Compute total_cost via checked_mul; charge BEFORE the verification loop - Add PrecompileError::BatchTooLarge and ::GasOverflow variants - Tests: OOG-before-work with n_sigs=100 and gas=10; BatchTooLarge for n_sigs=257 H-2: replace U256::to::<T>() with saturating_to + explicit domain check - BLOCKHASH (0x40): .to::<u64>() -> .saturating_to::<u64>(); u64::MAX > any plausible block height so the DB naturally returns B256::ZERO - PQVERIFY (0xB0): .to::<u8>() -> .saturating_to::<u8>(); unknown algo_id now returns InterpreterError::InvalidPqAlgo(id) instead of silently pushing 0 (no other .to::<T>() calls on user-controlled stack data) - Tests: BLOCKHASH with U256::MAX returns ZERO without panic; PQVERIFY with algo_id=255 returns InvalidPqAlgo without panic Audit ref: 2026-05-24 consolidated report Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bfac0c1 to
6e56d5f
Compare
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.
Summary
Fixes two P0/P1 security findings from the 2026-05-24 consolidated audit report.
C-2 — Batch precompile charges gas after work (free DoS)
File:
crates/pqvm-precompiles/src/lib.rsRoot cause:
verify_ml_dsa_65_batchexecuted the full verification loop before gas was charged, allowing an attacker to force unbounded CPU work at zero effective cost.Fix:
MAX_BATCH_SIGNATURES: u32 = 256; inputs with a larger count are rejected immediately withPrecompileError::BatchTooLargebefore any work begins.n_sigs;total_costis computed viachecked_muland charged before the verification loop.BatchTooLarge { n_sigs, max },GasOverflow.New tests:
batch_verify_oog_before_verification: n_sigs=100, gas_limit=10 → OOG error, no verification performed.batch_verify_rejects_too_many_sigs: n_sigs=257 →BatchTooLargeerror.H-2 — Interpreter panics on attacker-controlled operands
File:
crates/pqvm-interpreter/src/lib.rsRoot cause: Two
U256::to::<T>()calls on stack values would panic on overflow;PQVERIFYalso silently accepted unknownalgo_idvalues.Fix:
.to::<u64>()→.saturating_to::<u64>(). Values >u64::MAXsaturate tou64::MAX, which exceeds any plausible block height; the DB naturally returnsB256::ZERO, matching EVM semantics..to::<u8>()→.saturating_to::<u8>(). Unknownalgo_idafter the saturating cast now returnsInterpreterError::InvalidPqAlgo(id)instead of silently pushing 0..to::<T>()calls on user-controlled stack data exist (verified withrg).New tests:
blockhash_with_oversized_block_number_returns_zero: stack-top =U256::MAX→ returnsB256::ZEROwithout panic.pqverify_with_unknown_algo_returns_invalid_pq_algo:algo_id=255→InvalidPqAlgo(0xff)without panic.Test results
All 55 workspace tests pass.
cargo clippy --workspace --all-targets -- -D warningsclean.