Full Report
My co-worker Jason just published a super sick bug in the main implementation of EVM integration in Cosmos. Under the hood, the execution is done with Geth but the integration with Cosmos is complicated to do correctly with many different areas of state to consider. The stateDB in Geth contains a single journal of all state changes in the current transaction. When a new execution context is created, such as on a function call, a snapshot is taken. This adds a new revision and an index in the journal. In case of a revert, all changes within a particular jouralindex can be undone. In Cosmos, Commit() on a cached storage is what stores the data into permanent memory. It is crucial to ensure that the Cosmos storage and the Geth journal storage line up. However, during Evmos specific precompiles (such as for the Staking and Distribution module), it's possible to desync these two. The steps are outlined in the post well with a simple looking yet specific PoC contract: Contract calls an external function within a try/catch block. A new contract is created for a contract that will transfer ETH. A call is made to the Distribution precompile contract. This will trigger the Commit(). In particular, the balance of the ETH is saved in the target contract. Revert the call within the try/catch block. The rollback done by the EVM's state is not accurate now! Even though the contract shouldn't exist, it's still in storage and holds a balance. Withdraw the balance from the target contract that shouldn't exist still. In point 4 above, they have some notes on WHY this happens. The rollback on the revert does not reflect the changes in permanent storage. Since the contract creation happened post snapshot, the dirty mappings are removed after the revert. Since only the dirty accounts are touched, there's no reason to make any changes to the created contract. As a result, when the actual state is updated at the end of execution, the target contract is valid and alive, even though it should have been destroyed. This vulnerability required a very deep insight into how the state handling was being done by the EVM execution and by EVMOS. Overall, a solid vulnerability that was hard to wrap my head around but is amazing none-the-less.
Analysis Summary
# Vulnerability: Evmos Precompile State Commit Infinite Mint
## CVE Details
- **CVE ID**: CVE-2024-32644 (Referenced via GHSA-68fc-7mhg-6f6c)
- **CVSS Score**: 10.0 (Critical)
- **CWE**: CWE-670: Always-Incorrect Control Flow Implementation (State Desynchronization)
## Affected Systems
- **Products**: Evmos (Cosmos-based EVM implementation)
- **Versions**: Versions prior to v18.1.0
- **Configurations**: Chains utilizing Evmos-specific precompiles (specifically Staking and Distribution modules) that trigger a state `Commit()` during EVM execution.
## Vulnerability Description
The vulnerability arises from a desynchronization between the EVM's `StateDB` journal and the underlying Cosmos permanent storage (the Keeper/Bank module).
In Geth, the `StateDB` maintains a journal of cached changes. A `Snapshot` is taken when entering a new execution context (e.g., a `try/catch` block). If a revert occurs, the journal rolls back changes to that snapshot. However, certain Evmos precompiles (like Distribution) trigger an explicit `Commit()`, which flushes the current cached state into permanent Cosmos storage.
If a contract is created and funded *after* a snapshot but *before* a precompile-triggered `Commit()`, and the transaction then reverts:
1. The EVM journal rolls back, deleting the "dirty" mappings that mark the contract as existing.
2. The permanent storage, having been updated by the `Commit()`, still contains the contract and its balance.
3. The result is a "ghost" contract that should not exist according to the EVM logic but persists in state, allowing users to withdraw funds that were supposed to be reverted, leading to infinite minting.
## Exploitation
- **Status**: PoC available; privately disclosed and patched.
- **Complexity**: High (Requires deep understanding of EVM-Cosmos integration and specific precompile behavior).
- **Attack Vector**: Network (Smart Contract Interaction).
## Impact
- **Confidentiality**: None
- **Integrity**: Critical (Infinite minting of $EVMOS tokens; unauthorized state persistence).
- **Availability**: High (Can lead to chain halts or economic collapse via hyperinflation).
## Remediation
### Patches
- **Evmos v18.1.0**: Specifically addresses the handling of state commits within precompiles to prevent premature flushing of the journal to permanent storage.
### Workarounds
- No practical workarounds were provided other than upgrading the node software. The vendor previously took "precautionary measures" (likely disabling certain precompiles) before the public fix.
## Detection
- **Indicators of Compromise**: Discrepancies between the Total Supply of the native token and the sum of all account balances.
- **Detection Methods**: Monitoring for transactions that utilize `try/catch` blocks in conjunction with calls to the Distribution or Staking precompiles.
## References
- **Vendor Advisory**: [https://github.com/evmos/evmos/security/advisories/GHSA-68fc-7mhg-6f6c](https://github.com/evmos/evmos/security/advisories/GHSA-68fc-7mhg-6f6c)
- **Patch**: [https://github.com/evmos/evmos/commit/bb2d504eec9078d6eff6981fc0cb214e8a3ca496](https://github.com/evmos/evmos/commit/bb2d504eec9078d6eff6981fc0cb214e8a3ca496)
- **Technical Writeup**: [https://blog.asymmetric.re/evmos-precompile-state-commit-infinite-mint/](https://blog.asymmetric.re/evmos-precompile-state-commit-infinite-mint/)