- Current Status: Issue Fixed
- Affected Network: Testnet, Mainnet
The vulnerability outlined could have allowed someone to send a malicious transaction that abused a check of Cadence types (such as a token’s vault type), which would have allowed someone to change fields of a value they should not have had authority over, e.g. a token balance.
The initial report of this vulnerability only affected Testnet, as the initial report relied on the deployment of a contract, and there are additional security audit checks for contracts being deployed on Mainnet at the moment. The issue was promptly addressed and fixed right after the discovery.
A follow-up report five days after the initial report demonstrated that the bug could also be used without deploying a contract. The fix for the initial report already sufficiently addressed this second report.
Summary of Impact
- This specific Cadence vulnerability exposed the possibility of type confusion. This was not a design flaw in the language or the storage API, but a bug in the implementation of Cadence.
- Borrowing a capability from a non-signer account (
PublicAccount) did only perform a static type check that the borrowed type is a subtype of the linked capability and was missing a dynamic type check that the stored value is a subtype of the borrowed type.
- This vulnerability could have been used to mutate private fields of values that the code should have no access to.
- For example, a user could have deployed a function to modify the balance of a fungible token, effectively generating a mint mechanic for themselves on any fungible token.
- This issue was the result of an implementation bug, missing to make an additional dynamic check that was documented in the language reference, and not a flaw of the design of the type system.
Addressing the Issue
Check the stored value is a subtype of the borrowed type type.
- Perform a dynamic check that the dynamic type of the stored value is a subtype of the borrowed type when the capability is borrowed or checked.
- Perform a dynamic check that the dynamic type of the referenced value is a subtype of the borrowed type on each dereference, because the dynamic type of the stored value could have changed since the borrow, e.g. it was replaced. A future version of Cadence may replace this check with another mechanism, for example ensuring the value in the borrowed storage path is not replaced.
In addition, we implemented further defensive safe-guards to avoid other potential type confusion, in the form of defensive dynamic checks:
- Check that argument passed in a function invocation has a type that is a subtype of the parameter type.
- Check the value type in transfers (e.g. variable declaration, assignment, etc.), ensure it is a subtype of the target type.
As core contributors to the Flow ecosystem, we take reported issues very seriously and would like to thank Deniz Mert Edincik for finding and reporting this issue responsibly through our Responsible Disclosure Policy.
- Issue first reported, initially flagged as low severity (assumed misunderstanding of capabilities being latent)
- Reporting party followed up with more examples and a functional proof of concept (PoC)
- Security team evaluated report and PoC, flagged it as high severity, and determined it could be addressed with a hotfix to Testnet and Mainnet
- Patch development started
- Patch finished and merged to private internal repository
- Patch tested in Flow Go test locally
- Patch deployed onto Testnet (EN2) to smoke test compatibility with old EN software
- Re-ran received PoC transaction, it correctly failed now
- Deployed PoC and related contracts to a new account, ran PoC transaction, it correctly failed now
- Reporting party followed up with an additional PoC which demonstrated without the need to deploy a contract
- New PoC reproduced, team determined that it used the already reported bug, and the existing fix prevents the new PoC as well
- Public disclosure