fix: F-2026-18201 | [Dual Defense] Staking Precompile and Vesting Underflow StateDB Balance Enabling Native Mint and Drain - #40
Merged
Conversation
Cherry-picks the security-relevant part of upstream cosmos/evm 264aa70 (cosmos#1176).
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.
Problem
stateObject.SubBalancesubtracts without checking the balance first:uint256is unsigned, so subtracting more than the account holds wraps to ~2^256 instead of erroring.That is reachable from the precompile balance handler.
precompiles/common/balance_handler.gotranslates bankcoin_spentevents intostateDB.SubBalance(...)with no balance check (only aBlockedAddrbypass). The EVM state view tracks spendable balance, but Cosmos lets vesting accounts delegate locked coins — so delegatingD> spendableSthrough the staking precompile makes the StateDB subtract more than it holds.x/vm/keeper/statedb.gothen reconciles the bogus EVM view back into bank:SetBalancecomputesdelta = amount - balanceand mints if positive / burns if negative. Payoffs:2^256 - Bso their balance wraps to ~0 and reconciliation burns their real coins.Reported by Hacken as F-2026-18201 (High). Same family as Evmos
GHSA-pxv8-qhrh-jc7vand cosmos/evmASA-2026-002.Fix
Cherry-picks the security-relevant part of upstream cosmos/evm
264aa70— "fix: harden statedb balance and event amount handling (cosmos#1176)", 2026-05-15:x/vm/statedb/state_object.go—SubBalancenow panics on underflow instead of wrapping. The panic is recovered by baseapp, the tx fails, and no mint/burn happens.precompiles/common/utils.go—ParseAmountis denom-aware (base vs extended denom) instead of always running the base amount through the 18-decimal conversion. This is the other half of the same upstream commit. It is a no-op for Push (app/config.gosetsExtendedDenom == BaseDenomat 18 decimals and precisebank is not wired), but keeps us aligned with upstream.A straight
git cherry-pick 264aa70does not apply — upstream bundled an unrelated "enforce 18-decimal coin configuration" change into the same commit, which conflicts with the fork inREADME.md,x/vm/types/denom_config.goandx/vm/wrappers/feemarket_test.go. Those hunks are deliberately not taken; only the balance/event hardening and its tests are.Provenance
Not Push-authored —
git blameon the affected files shows only upstream authors. The fix exists only on upstreammain; it is in no tagged release:mainSo upgrading to a released tag does not help — the patch has to be cherry-picked. Our fork is pinned at
v1.0.0-rc2.0.20260803..., descended from a June 2025 upstream point.Tests
x/vm/statedb/statedb_test.go—TestSubBalanceUnderflowPanics(from the upstream commit): subtracting 2 from a balance of 1 panics withstate balance underflow for <addr>: have=1 sub=2instead of wrapping.precompiles/common/balance_handler_test.go—TestAfterBalanceChangeSpendMoreThanBalancePanics(added here): reproduces the attack at the precompile boundary. A spender with a StateDB balance of 5 emits a bankcoin_spentevent for 10 — exactly what delegating locked coins through the staking precompile produces.BalanceHandler.AfterBalanceChangemust hard-fail, and the balance must not have wrapped to ~2^256.precompiles/common/balance_handler_test.go—TestParseAmount/unrelated_denom_is_ignored(from the upstream commit).Both new tests were verified to fail with the guard reverted and pass with it in place.
→ 12 packages ok, 0 failures.
Related
Chain-side companion PR: pushchain/push-chain-node#315 — blocks permissionless vesting-account creation in the ante handler, removing the precondition. The two are a dual defense: cosmos#315 removes the precondition, this PR removes the vulnerability.
After this PR merges, the
github.com/cosmos/evmpin inpush-chain-node/go.mod(currentlyv1.0.0-rc2.0.20260616081105-96231e7a76c0, which predates the guard) needs bumping so the chain actually picks it up.Parent finding:
F-2026-17758— the fork has ~14 months of upstream drift; if this security commit was missed, others were too.