fix(l1): stop eth_feeHistory aborting when the window is below retained history - #7207
fix(l1): stop eth_feeHistory aborting when the window is below retained history#7207ilitteri wants to merge 1 commit into
Conversation
get_range clamps its two bounds independently: the finish is capped by the latest block, the start is raised to the earliest block still held. On a snap-synced node the earliest block is the pivot, so a request whose whole window lies below the pivot yields start > end. The caller then computes `(end_block - start_block + 1) as usize`. With overflow checks that panics; in release it wraps, and the following `vec![0_u64; count + 1]` asks the allocator for ~1.8e19 elements, which aborts the process. That makes a single unauthenticated eth_feeHistory call a remote kill on any snap-synced node. Answer the empty history the caller is entitled to instead, matching the existing zero-block-count path.
|
🤖 Kimi Code ReviewThis PR fixes a critical DoS vulnerability where an unauthenticated Security Assessment
Code Quality
Test Coverage
Minor Suggestions
Verdict: LGTM. The fix correctly prevents the arithmetic underflow/overflow and subsequent memory allocation failure. The tests validate both the security fix and correct functional behavior. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Claude Code ReviewThis is a small, well-targeted fix. Here's the review: Review: Stop
|
🤖 Codex Code ReviewFindings
The guard itself looks correct and important from a security perspective: it closes a remote allocation-abort path in an unauthenticated RPC method. I did not find other correctness or EVM/consensus issues in the diff. I couldn’t run the Rust tests in this environment because Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Lines of code reportTotal lines added: Detailed view |
8be0945 to
f991bf4
Compare
|
/kimi |
|
/kimi |
🤖 Kimi Code ReviewAutomated review by Kimi (Moonshot AI) |
|
/kimi |
🤖 Kimi Code ReviewI'll review this PR focusing on the security-critical fix for the SummaryThis PR fixes a critical unauthenticated RPC denial-of-service vulnerability where a Detailed Review
|
| Aspect | Status | Notes |
|---|---|---|
| Unauthenticated DoS | Fixed | The start_block > end_block check prevents the u64 underflow |
| Input validation | Adequate | Bounds are properly clamped; the fix handles the edge case |
| Resource exhaustion | Fixed | No longer possible to trigger vec![0; ~1.8e19] |
Severity if unpatched: Critical — any peer can kill the node with a single RPC request.
Recommendations
-
Verify
FeeHistoryResponse::default()completeness — EnsureoldestBlockfield behavior matches Ethereum JSON-RPC spec for empty history responses. -
Consider defensive programming in
get_range— While the fix at the call site is correct, consider whetherget_rangeitself should guaranteestart <= endas a postcondition, or return aResult/Optionindicating empty range. This would prevent similar bugs at other call sites. -
Add test for exact
oldestBlockin empty response — The first test verifiesbaseFeePerGasandgasUsedRatiolengths but doesn't assertoldestBlockvalue.
Conclusion
Approve with minor suggestions. The fix correctly addresses a critical vulnerability. The tests are well-designed and cover both the crash scenario and normal clamping behavior. The code is readable with excellent comments explaining the snap-sync context.
Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt
|
/codex |
🤖 Codex Code Review
The I could not run the tests here: local Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Motivation
get_rangeclamps its two bounds independently — the finish is capped by the latest block, the start is raised to the earliest block we still hold (crates/networking/rpc/eth/fee_market.rs:231-234):On a snap-synced node the earliest block is the pivot, so a request whose window lies entirely below the pivot yields
start > end. The caller then computes (:104-105):With overflow checks that panics. In release it wraps, and the allocation asks for roughly 1.8e19 elements, which aborts the process. A single unauthenticated
eth_feeHistorycall is therefore a remote kill on any snap-synced node — that is, on the standard mainnet deployment.Reproduced against a store whose earliest block is 5:
This will get easier to hit, not harder: history pruning (#6673) makes a non-zero earliest block the normal steady state rather than a snap-sync artifact.
Description
Return the empty fee history the caller is entitled to when the requested window lies entirely below retained history, matching the existing
block_count == 0path immediately above. Partially-available windows are unaffected and still serve the retained part witholdestBlockclamped up.Tests
Two tests in
test/tests/rpc/fee_history_range_tests.rs, against a store withearliest = 5andearliest = 4respectively:attempt to subtract with overflowwithout the fixoldestBlockclamped to the earliest retained block, so the fix does not turn a servable request into an empty one