Skip to content

apollo_consensus_orchestrator: anchor L1 gas-price margin to local reference and harden overflow#14589

Open
asaf-sw wants to merge 1 commit into
mainfrom
asaf/m17-gas-price-margin
Open

apollo_consensus_orchestrator: anchor L1 gas-price margin to local reference and harden overflow#14589
asaf-sw wants to merge 1 commit into
mainfrom
asaf/m17-gas-price-margin

Conversation

@asaf-sw

@asaf-sw asaf-sw commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

Fixes M-17. Rewrites within_margin in validate_proposal.rs so the L1 gas-price tolerance band is anchored to the validator's locally-trusted reference price instead of the proposer-supplied value, and replaces an unchecked u128 * u128 with saturating_mul.

Why

is_proposal_init_valid validates proposer-supplied L1 gas prices against the node's own oracle read via within_margin(proposed, reference, margin_percent). The margin was computed as (proposed * margin_percent) / 100 — anchored to the attacker-controlled value.

  • Asymmetric band (the finding): because the band width scaled with proposed, a malicious proposer could inflate the price up to ~reference / (1 - m)1.111 * reference at the default 10% margin, instead of the intended 1.10 * reference. PoC: proposed=1111, reference=1000, m=10 was accepted (margin = 1111*10/100 = 111, diff = 111) even though 1111 exceeds the intended 1100 ceiling. The extra ~1.1% propagates into user fees on every block the attacker proposes, and stacks with M-1.
  • Unchecked overflow (raised in analysis): the same line did a raw u128 * u128. Large WEI prices can make price * margin_percent exceed u128::MAX, panicking in debug / wrapping in release.

Fix

  • Anchor the margin to reference (reference.0.saturating_mul(margin_percent) / 100). The accepted band becomes the symmetric [reference*(1-m), reference*(1+m)].
  • saturating_mul removes the overflow panic on large legitimate prices.
  • Renamed params number1/number2proposed/reference so the trusted argument is unambiguous in the signature (the footgun that produced the bug). Call sites already pass (proposed, reference), so no call-site change was required.

Assumptions

  • Implemented a symmetric ±m band anchored to the local reference (recommended default; the band shape is a product question — documented in a code comment).
  • Kept integer truncation of the margin (marginally stricter than exact %), documented inline.
  • Deferred the optional bounds-returning refactor to keep the diff minimal; the anchor + overflow fix fully closes the finding.

Test coverage

Extended test_within_margin and added a dedicated overflow test:

  • inflated_proposed_rejected(1111, 1000, 10, false) — the M-17 exploit; verified it FAILS on the pre-fix code and passes after.
  • upper_bound_inclusive / lower_bound_inclusive (1100 and 900 vs 1000 at 10%).
  • deflated_proposed_rejected(889, 1000, 10, false) — symmetry.
  • large_identical_no_overflow / large_within_no_overflow (u128::MAX) and within_margin_large_reference_does_not_overflow — overflow hardening.

All 17 tests pass; clippy clean.

Internal analysis: docs/security-review/M-17.md.

🤖 Generated with Claude Code

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

asaf-sw commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

…ference and harden overflow

within_margin anchored the tolerance band to the proposer-supplied price (number1),
so the accepted band scaled with attacker input: a proposer could inflate the L1 gas
price up to ~1.111x the local reference instead of the intended 1.10x (M-17). The same
line also did an unchecked u128 * u128 that can overflow on large WEI prices.

Anchor the band to the locally-trusted reference and use saturating_mul. The accepted
band is now the symmetric [reference*(1-m), reference*(1+m)]. Parameters renamed to
proposed/reference to make the trusted argument unambiguous; call sites already pass
(proposed, reference) so no call-site change is needed.

Adds regression cases including the inflated-proposal exploit (1111 vs 1000 at 10% must
be rejected), boundary-inclusive cases, and overflow-hardening cases with u128::MAX.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@asaf-sw asaf-sw force-pushed the asaf/m17-gas-price-margin branch from 241b4cb to e12b551 Compare June 23, 2026 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants