Skip to content

fix: prevent u64 underflow in validator_claim_fees and use on-chain Rent sysvar#179

Open
amathxbt wants to merge 1 commit into
magicblock-labs:mainfrom
amathxbt:fix/claim-fees-underflow-rent-sysvar
Open

fix: prevent u64 underflow in validator_claim_fees and use on-chain Rent sysvar#179
amathxbt wants to merge 1 commit into
magicblock-labs:mainfrom
amathxbt:fix/claim-fees-underflow-rent-sysvar

Conversation

@amathxbt
Copy link
Copy Markdown

@amathxbt amathxbt commented May 3, 2026

Summary

This PR fixes two related bugs in process_validator_claim_fees (src/processor/validator_claim_fees.rs).


Bug 1 — Rent::default() uses hardcoded values, not the on-chain schedule

// Before
let min_rent = Rent::default().minimum_balance(8);

Rent::default() constructs a Rent struct with compile-time defaults that may drift from the actual on-chain rent configuration. The correct approach is to read the Sysvar from the runtime via Rent::get()?. Additionally the hardcoded data-length argument 8 is replaced with the actual validator_fees_vault.data_len() so the minimum-balance threshold is always correct regardless of account size.


Bug 2 — Unchecked subtraction can underflow and bypass the insufficient-funds guard

// Before (appears twice)
let amount = args.amount.unwrap_or(validator_fees_vault.lamports() - min_rent);
if validator_fees_vault.lamports() - min_rent < amount { ... }

Both subtractions are plain u64 arithmetic. If the vault ever holds fewer lamports than min_rent (possible after a prior accounting error or if a griefing vector reduces the balance below rent-exemption), the subtraction wraps around in release builds to a very large u64. This makes amount a huge default value and causes the guard condition to evaluate incorrectly, potentially allowing an unauthorised withdrawal of an arbitrary amount.

Fix: Compute the available balance once with checked_sub(...).ok_or(ProgramError::InsufficientFunds)? and reuse the result, eliminating both raw subtractions.


Fix

// After
let min_rent = Rent::get()?.minimum_balance(validator_fees_vault.data_len());
let available = validator_fees_vault
    .lamports()
    .checked_sub(min_rent)
    .ok_or(ProgramError::InsufficientFunds)?;
let amount = args.amount.unwrap_or(available);
if available < amount { ... }

Impact

  • Severity: Critical — arithmetic underflow in an on-chain fee-claim instruction can lead to fund loss
  • Affected component: delegation-program / src/processor/validator_claim_fees.rs

Summary by CodeRabbit

  • Bug Fixes
    • Improved accuracy of rent-exempt balance calculations in validator fee claiming by using actual account data instead of hardcoded values.
    • Enhanced safety checks to prevent arithmetic underflow and provide clearer error messages for insufficient funds scenarios.

…ent sysvar

Two related bugs in process_validator_claim_fees:

1. Rent::default() returns hardcoded default values that may differ from
   the actual on-chain rent schedule. Replaced with Rent::get()? which
   reads the current on-chain Sysvar.

2. Both occurrences of validator_fees_vault.lamports() - min_rent were
   plain u64 subtractions. If the vault somehow holds fewer lamports than
   min_rent (e.g. due to a prior accounting bug or a griefing vector that
   drains rent below the exemption threshold), the subtraction wraps around
   to a very large u64 in release builds, bypassing the insufficient-funds
   guard and potentially allowing an attacker to drain an arbitrary amount.

Fix: use checked_sub(...).ok_or(ProgramError::InsufficientFunds) so the
instruction returns an error instead of underflowing.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

The process_validator_claim_fees function was updated to improve rent-exempt balance calculations. The change replaces hardcoded rent calculations with dynamic computation using Rent::get() and the actual validator_fees_vault account data length. A new underflow-safe checked_sub operation derives the available lamports above the rent-exempt minimum. When insufficient funds are detected, the function now returns ProgramError::InsufficientFunds. The default claimed amount and insufficient-funds validation logic were updated to reference the newly computed available value instead of direct subtraction from vault lamports.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/processor/validator_claim_fees.rs`:
- Around line 53-56: The protocol_claim_fees.rs code still uses
Rent::default().minimum_balance(8) and performs an unchecked subtraction;
replace that pattern by retrieving the on-chain rent with Rent::get()? and
computing minimum_balance using the actual account data length (e.g.,
fees_vault.data_len()), assign to a min_rent variable, and use a checked
subtraction (checked_sub) when subtracting min_rent from balances to avoid
underflow—locate the logic around fees_vault and the current
minimum_balance/subtraction and update it to mirror the validator_claim_fees.rs
approach.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 99f817bb-4215-4bd0-9aa7-d99d62da5a91

📥 Commits

Reviewing files that changed from the base of the PR and between f555df8 and b2e626b.

📒 Files selected for processing (1)
  • src/processor/validator_claim_fees.rs

Comment on lines +53 to +56
// Use the on-chain Rent sysvar (not `Rent::default()`) and the actual
// account data length so the minimum-balance threshold is always accurate.
let min_rent =
Rent::get()?.minimum_balance(validator_fees_vault.data_len());
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

protocol_claim_fees.rs retains the same unfixed pattern.

src/processor/protocol_claim_fees.rs:53-60 still uses Rent::default().minimum_balance(8) with unchecked subtraction — the exact vulnerability this PR was raised to fix. Consider applying the same Rent::get()?.minimum_balance(fees_vault.data_len()) + checked_sub treatment there before the next release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/processor/validator_claim_fees.rs` around lines 53 - 56, The
protocol_claim_fees.rs code still uses Rent::default().minimum_balance(8) and
performs an unchecked subtraction; replace that pattern by retrieving the
on-chain rent with Rent::get()? and computing minimum_balance using the actual
account data length (e.g., fees_vault.data_len()), assign to a min_rent
variable, and use a checked subtraction (checked_sub) when subtracting min_rent
from balances to avoid underflow—locate the logic around fees_vault and the
current minimum_balance/subtraction and update it to mirror the
validator_claim_fees.rs approach.

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.

1 participant