Skip to content

fix(costs): check size arithmetic for overflow#737

Open
QuantumExplorer wants to merge 2 commits into
developfrom
codex/fix-684-checked-size-arithmetic
Open

fix(costs): check size arithmetic for overflow#737
QuantumExplorer wants to merge 2 commits into
developfrom
codex/fix-684-checked-size-arithmetic

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

  • add checked arithmetic to element required-space helpers and cost verification paths
  • return overflow errors from root key storage-cost sizing instead of wrapping
  • check batch JIT value-defined cost sums before returning Merk costs
  • add overflow regressions for element sizing and cost storage arithmetic

Fixes #684.

Verification

  • cargo test -p grovedb-element --test element_constructors_helpers
  • cargo test -p grovedb-costs --test coverage_regression
  • cargo test -p grovedb test_batch_replace_item_with_sum_item_flags_update --lib
  • cargo check -p grovedb --lib
  • git diff --check

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Storage cost calculations now properly detect and reject arithmetic overflow conditions instead of silently wrapping or saturating values, providing clearer error reporting.
  • Tests

    • Added comprehensive overflow detection tests to verify the system correctly handles edge cases in cost computations.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@QuantumExplorer has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 40 minutes and 53 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b66d2687-257e-4300-8ff5-ed9b1cd0c84a

📥 Commits

Reviewing files that changed from the base of the PR and between fbf64e1 and 98db1fd.

📒 Files selected for processing (2)
  • costs/tests/coverage_regression.rs
  • merk/src/merk/mod.rs
📝 Walkthrough

Walkthrough

This PR adds overflow detection to arithmetic operations used in element sizing and storage cost computation. New Overflow error variants are introduced in the costs and grovedb-element crates, and checked arithmetic helpers replace unchecked and saturating arithmetic throughout the cost computation paths. Key changes include making KeyValueStorageCost::for_updated_root_cost fallible, updating element sizing helpers to detect u32 overflow, and adding regression tests to verify overflow rejection.

Changes

Overflow Detection in Size Arithmetic and Storage Costs

Layer / File(s) Summary
Error Type Extensions
costs/src/error.rs, grovedb-element/src/error.rs
Adds Overflow(&'static str) variant to Error and ElementError enums with display annotations for arithmetic overflow cases.
Costs Crate Checked Arithmetic Infrastructure
costs/src/lib.rs
Introduces private helper methods checked_len_with_required_space, checked_add, and checked_sub on OperationCost for overflow-safe u32 arithmetic that return Error::Overflow on failure.
OperationCost Arithmetic Refactor
costs/src/lib.rs
Replaces unchecked and saturating arithmetic in OperationCost::add_key_value_storage_costs with checked helpers, adding explicit overflow/underflow error points for key length, child option removal, child/sum-tree lengths, and required space addition.
KeyValueStorageCost Fallible Constructor
costs/src/storage_cost/key_value_cost.rs
Changes for_updated_root_cost to return Result<Self, Error> with checked arithmetic for required-space computation and removed-bytes derivation. Adds Debug to derive list and imports Error type.
StorageCost Verification Overflow Detection
costs/src/storage_cost/mod.rs
Updates StorageCost::verify to use checked_add for computing the total of added_bytes + replaced_bytes, returning Error::Overflow on overflow.
Element Sizing Helper Overflow Safety
grovedb-element/src/element/helpers.rs
Introduces Element::checked_required_space_sum helper for overflow-safe u32 summation and refactors required_item_space, required_item_with_sum_item_space, and required_reference_with_sum_item_space to use checked arithmetic.
Batch Value-Defined-Cost Overflow Detection
grovedb/src/batch/mod.rs
Introduces checked_value_defined_cost helper closure in batch element-flag update logic that sums u32 cost components with overflow detection, raising MerkError::Overflow on overflow.
Merk Root-Key Cost Error Handling
merk/src/merk/mod.rs
Updates Merk::commit to wrap the fallible KeyValueStorageCost::for_updated_root_cost(...) call with cost_return_on_error_no_add! and map errors to Error::CostsError.
Overflow Detection Regression Tests
costs/tests/coverage_regression.rs, grovedb-element/tests/element_constructors_helpers.rs
Adds panic branch for unexpected error variants, unwraps fallible for_updated_root_cost calls with result field assertions, and introduces checked_storage_cost_arithmetic_rejects_overflow and required_space_helpers_reject_u32_overflow regression tests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #684 ([audit][high] Unchecked size arithmetic can undercount storage costs) — This PR directly addresses the audit findings by implementing checked arithmetic and explicit overflow errors across all referenced vulnerable paths: element sizing helpers, operation cost computation, storage cost verification, and batch processing.

Poem

🐰 A rabbit hops through arithmetic,
Checking bounds with logic cryptic,
No more wraps at MAX's gate—
Overflow errors seal the fate!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed All coding requirements from issue #684 are met: checked arithmetic is added to element sizing helpers [grovedb-element/src/element/helpers.rs], cost verification paths [costs/src/lib.rs, costs/src/storage_cost/], root key storage-cost sizing [costs/src/storage_cost/key_value_cost.rs], and batch value-defined cost sums [grovedb/src/batch/mod.rs]; comprehensive overflow tests are added [grovedb-element/tests/ and costs/tests/].
Out of Scope Changes check ✅ Passed All changes directly address issue #684's requirements; no unrelated modifications to error types, helper logic, or test coverage were introduced outside the scope of fixing unchecked arithmetic vulnerabilities.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title accurately and concisely summarizes the main objective of the PR: adding checked arithmetic to prevent overflow in cost-related size calculations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-684-checked-size-arithmetic

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
costs/src/storage_cost/key_value_cost.rs (1)

62-65: 💤 Low value

Minor duplication of with_required_space helper.

This helper duplicates the logic in OperationCost::checked_len_with_required_space. Consider extracting to a shared utility if more call sites emerge, but acceptable as-is for locality.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@costs/src/storage_cost/key_value_cost.rs` around lines 62 - 65, The helper
function with_required_space duplicates logic already implemented in
OperationCost::checked_len_with_required_space; replace calls to
with_required_space (or remove the helper) and reuse
OperationCost::checked_len_with_required_space to avoid duplication, or extract
the shared checked-add logic into a new utility function used by both
with_required_space and OperationCost::checked_len_with_required_space so both
call the single implementation; update references to the old helper accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@grovedb/src/batch/mod.rs`:
- Around line 2957-2964: The unchecked cast "item_value.len() as u32" can
truncate; replace it with a fallible conversion using
u32::try_from(item_value.len()) and propagate/map the Err to MerkError::Overflow
where item_len is used (the variable assigned before computing
sum_item_value_cost and passed into checked_value_defined_cost). Update the
surrounding code paths (the block that computes item_len and then
sum_item_value_cost) to handle the Result and return or convert the error to
MerkError::Overflow so overflow is caught instead of silently truncating.

In `@merk/src/merk/mod.rs`:
- Around line 425-431: Replace the lossy casts where
key_updates.updated_root_key_from.as_ref().map(|k| k.len() as u32) and
tree_key.len() as u32 are used: perform safe conversions with u32::try_from(...)
for both the optional updated_root_key length and tree_key.len(), map any
TryFromIntError into Error::CorruptedData with a short contextual message
(mentioning updated_root_key or tree_key), and then pass those results into
for_updated_root_cost(...), finally mapping that call's error into
Error::CostsError as before; reference the symbols
key_updates.updated_root_key_from, tree_key.len(), for_updated_root_cost,
Error::CorruptedData and Error::CostsError when making the changes.

---

Nitpick comments:
In `@costs/src/storage_cost/key_value_cost.rs`:
- Around line 62-65: The helper function with_required_space duplicates logic
already implemented in OperationCost::checked_len_with_required_space; replace
calls to with_required_space (or remove the helper) and reuse
OperationCost::checked_len_with_required_space to avoid duplication, or extract
the shared checked-add logic into a new utility function used by both
with_required_space and OperationCost::checked_len_with_required_space so both
call the single implementation; update references to the old helper accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52811e08-dd70-4822-914c-d894862fb170

📥 Commits

Reviewing files that changed from the base of the PR and between a2129a9 and fbf64e1.

📒 Files selected for processing (10)
  • costs/src/error.rs
  • costs/src/lib.rs
  • costs/src/storage_cost/key_value_cost.rs
  • costs/src/storage_cost/mod.rs
  • costs/tests/coverage_regression.rs
  • grovedb-element/src/element/helpers.rs
  • grovedb-element/src/error.rs
  • grovedb-element/tests/element_constructors_helpers.rs
  • grovedb/src/batch/mod.rs
  • merk/src/merk/mod.rs

Comment thread grovedb/src/batch/mod.rs
Comment on lines 2957 to +2964
let item_len = item_value.len() as u32;
let sum_item_value_cost = SUM_ITEM_COST_SIZE
+ flags_len
+ flags_len.required_space() as u32
+ item_len
+ item_len.required_space() as u32
+ wrapper_overhead;
let sum_item_value_cost = checked_value_defined_cost(&[
SUM_ITEM_COST_SIZE,
flags_len,
flags_len.required_space() as u32,
item_len,
item_len.required_space() as u32,
wrapper_overhead,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify remaining potentially truncating usize->u32 casts in this file.
rg -nP '\bas\s*u32\b' --type=rust -C2

Repository: dashpay/grovedb

Length of output: 50372


🏁 Script executed:

# Check the file size first
wc -l grovedb/src/batch/mod.rs

Repository: dashpay/grovedb

Length of output: 89


🏁 Script executed:

# Read the specific lines in question
sed -n '2950,2970p' grovedb/src/batch/mod.rs

Repository: dashpay/grovedb

Length of output: 1351


🏁 Script executed:

# Also check a broader context around the closure to understand the error handling pattern
sed -n '2910,2980p' grovedb/src/batch/mod.rs

Repository: dashpay/grovedb

Length of output: 4500


Unchecked usize -> u32 cast can bypass overflow safety.

Line 2957 uses item_value.len() as u32, which silently truncates for values exceeding u32::MAX and undercounts cost before your checked sum runs. Convert with u32::try_from(item_value.len()) and map the error to MerkError::Overflow.

Suggested fix
-                                        let item_len = item_value.len() as u32;
+                                        let item_len = u32::try_from(item_value.len()).map_err(|_| {
+                                            MerkError::Overflow("value defined cost overflow")
+                                        })?;
                                         let sum_item_value_cost = checked_value_defined_cost(&[
                                             SUM_ITEM_COST_SIZE,
                                             flags_len,
                                             flags_len.required_space() as u32,
                                             item_len,
                                             item_len.required_space() as u32,
                                             wrapper_overhead,
                                         ])?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@grovedb/src/batch/mod.rs` around lines 2957 - 2964, The unchecked cast
"item_value.len() as u32" can truncate; replace it with a fallible conversion
using u32::try_from(item_value.len()) and propagate/map the Err to
MerkError::Overflow where item_len is used (the variable assigned before
computing sum_item_value_cost and passed into checked_value_defined_cost).
Update the surrounding code paths (the block that computes item_len and then
sum_item_value_cost) to handle the Result and return or convert the error to
MerkError::Overflow so overflow is caught instead of silently truncating.

Comment thread merk/src/merk/mod.rs
Comment on lines +425 to +431
key_updates
.updated_root_key_from
.as_ref()
.map(|k| k.len() as u32),
tree_key.len() as u32,
)
.map_err(Error::CostsError)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify current call-site uses lossy casts and inspect surrounding code.
rg -n --type=rust -C4 'for_updated_root_cost\(|len\(\)\s+as\s+u32|u32::try_from\(' merk/src/merk/mod.rs

# Verify constructor signature for root-cost sizing in costs crate.
rg -n --type=rust -C3 'fn\s+for_updated_root_cost\s*\(' costs/src/storage_cost/key_value_cost.rs

Repository: dashpay/grovedb

Length of output: 1174


Replace lossy usize → u32 casts with safe conversions and wrap errors with context.

Lines 428 and 429 use as u32 to cast usize values, which silently truncates on platforms where usize exceeds 32 bits. Additionally, line 431's .map_err(Error::CostsError) does not follow the required error-wrapping pattern. Use u32::try_from(...) for safe conversion and wrap conversion failures with Error::CorruptedData containing contextual messages before passing to for_updated_root_cost(...).

Suggested patch
-                            Some(cost_return_on_error_no_add!(
-                                inner_cost,
-                                KeyValueStorageCost::for_updated_root_cost(
-                                    key_updates
-                                        .updated_root_key_from
-                                        .as_ref()
-                                        .map(|k| k.len() as u32),
-                                    tree_key.len() as u32,
-                                )
-                                .map_err(Error::CostsError)
-                            ))
+                            let previous_root_len = cost_return_on_error_no_add!(
+                                inner_cost,
+                                key_updates
+                                    .updated_root_key_from
+                                    .as_ref()
+                                    .map(|k| {
+                                        u32::try_from(k.len()).map_err(|e| {
+                                            Error::CorruptedData(format!(
+                                                "updated root key length overflow: {}",
+                                                e
+                                            ))
+                                        })
+                                    })
+                                    .transpose()
+                            );
+                            let new_root_len = cost_return_on_error_no_add!(
+                                inner_cost,
+                                u32::try_from(tree_key.len()).map_err(|e| {
+                                    Error::CorruptedData(format!(
+                                        "new root key length overflow: {}",
+                                        e
+                                    ))
+                                })
+                            );
+                            Some(cost_return_on_error_no_add!(
+                                inner_cost,
+                                KeyValueStorageCost::for_updated_root_cost(
+                                    previous_root_len,
+                                    new_root_len,
+                                )
+                                .map_err(Error::CostsError)
+                            ))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@merk/src/merk/mod.rs` around lines 425 - 431, Replace the lossy casts where
key_updates.updated_root_key_from.as_ref().map(|k| k.len() as u32) and
tree_key.len() as u32 are used: perform safe conversions with u32::try_from(...)
for both the optional updated_root_key length and tree_key.len(), map any
TryFromIntError into Error::CorruptedData with a short contextual message
(mentioning updated_root_key or tree_key), and then pass those results into
for_updated_root_cost(...), finally mapping that call's error into
Error::CostsError as before; reference the symbols
key_updates.updated_root_key_from, tree_key.len(), for_updated_root_cost,
Error::CorruptedData and Error::CostsError when making the changes.

@codecov

codecov Bot commented May 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.48%. Comparing base (60f2968) to head (98db1fd).
⚠️ Report is 3 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #737      +/-   ##
===========================================
+ Coverage    91.42%   91.48%   +0.05%     
===========================================
  Files          236      236              
  Lines        67053    67210     +157     
===========================================
+ Hits         61305    61487     +182     
+ Misses        5748     5723      -25     
Components Coverage Δ
grovedb-core 88.95% <100.00%> (+<0.01%) ⬆️
merk 92.40% <100.00%> (+0.13%) ⬆️
storage 86.36% <ø> (ø)
commitment-tree 96.43% <ø> (ø)
mmr 96.79% <ø> (+0.03%) ⬆️
bulk-append-tree 89.39% <ø> (+0.13%) ⬆️
element 97.40% <100.00%> (+0.03%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@QuantumExplorer QuantumExplorer changed the title Check size arithmetic in cost helpers fix(costs): check size arithmetic for overflow May 21, 2026
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.

[audit][high] Unchecked size arithmetic can undercount storage costs

1 participant