fix(realloc): only top up rent when the target holds less than the new minimum - #8
Open
SwineCoder101 wants to merge 2 commits into
Open
fix(realloc): only top up rent when the target holds less than the new minimum#8SwineCoder101 wants to merge 2 commits into
SwineCoder101 wants to merge 2 commits into
Conversation
…lding more than the new rent-exempt minimum
SwineCoder101
force-pushed
the
fix/realloc-lamport-underflow
branch
from
August 27, 2026 12:23
279b7b4 to
608f775
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.
Bug:
ReallocateWithoutZeroInitpanics when the target account already holds more than the new rent-exempt minimumThe native and pinocchio
reallocprograms compute the rent top-up aslamports_required - target_account.lamports()with a plain unchecked subtraction. Whenever the target account already holds more lamports than the enlarged rent-exempt minimum, the subtraction overflows and the instruction aborts, so the account can never be reallocated. Anyone can put any target account into that state with a single system transfer (the surplus needed here is about 84k lamports, well under a cent), making this a cheap, permanent denial of service against the instruction. Severity: medium (no funds at risk, but the instruction is bricked for that account).Affected
basics/realloc/native/program/src/instructions/reallocate.rs(reallocate_without_zero_init)basics/realloc/pinocchio/program/src/instructions/reallocate.rs(reallocate_without_zero_init)basics/realloc/anchoruses Anchor'sreallocconstraint, which only transfers when the new minimum exceeds the current balance.Functionality
ReallocateWithoutZeroInitgrows an existingAddressInfoaccount into anEnhancedAddressInfoaccount: it computes the rent-exempt minimum for the larger size, has the payer top the account up so it stays rent exempt, resizes the account and writes the extended data.The bug
reallocate.rs:24:let diff = lamports_required - target_account.lamports();reallocate.rs:19:let diff = lamports_required - target_account.lamports();Both assume the target holds at most the new minimum. The programs are built with
overflow-checks = true(rootCargo.toml), so whentarget_account.lamports() > lamports_requiredthe subtraction panics withattempt to subtract with overflowand the transaction fails withProgramFailedToComplete. Without overflow checks it would wrap to roughlyu64::MAXand the system transfer would fail with insufficient funds; either way the instruction cannot succeed.Concrete scenario: an
AddressInfoaccount is created (25 bytes, rent exempt). Anyone sends it enough lamports to exceed the rent-exempt minimum for 37 bytes (any amount above ~83,520 lamports at default rent). Every subsequentReallocateWithoutZeroInitfor that account now fails, regardless of who signs as payer.Reproduce
Test (both variants,
tests/realloc.test.ts):Reallocate WITHOUT zero init when the account already holds more than the new rent-exempt minimum. It creates a fresh account, sends it the enlarged rent-exempt minimum with a plain system transfer, then callsReallocateWithoutZeroInit, and asserts the enlarged data, that the target balance is unchanged and that the payer was only debited the transaction fee.Before the fix:
Fix
Both variants now compute
lamports_required.saturating_sub(target_account.lamports())and only invoke the system transfer when the result is non-zero. An account that already covers the new minimum is resized without touching the payer; an under-funded one is topped up exactly as before. This mirrors what Anchor'sreallocconstraint does.Verification
(identical output for native and pinocchio; the Rust
test_reallocintegration test in eachprogram/tests/test.rsalso passes.)