-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Attacker can force the failure of transactions that use tryCatch
#18
Comments
@Picodes we tend to disagree with the severity here - gas attacks are possible in almost all cases of using Ambire accounts through a relayer. It's also an inherent design compromise of ERC-4337 as well, and the only way to counter it is with appropriate offchain checks/reputation systems and griefing protections. Also, the solution seems too finicky. What if the |
The main issue here is that the nonce is incremented despite the fact that the transaction wasn't executed as intended, which would force the user to resign the payload and would be a griefing attack against the user. I do break an important invariant which is that if the nonce is incremented, the transaction signed by the user was included as he intended. Also, I think this can be used within |
Picodes marked the issue as satisfactory |
I'm not sure I undestand, the whole point of signing something that calls into tryCatch is that you don't care about the case where the nonce is incremented but the transaction is failing. What am I missing? |
You don't care if the transactions fail because the sub-call is invalid, but you do if it's because the relayer manipulated the gas, right? |
Ok, I see the point here - prob repeating stuff that others said before but, trying to simplify - the relayer can rug users by taking their fee regardless of the fact that the inner transactions fail due to the relayer using a lower gasLimit. This would be possible if some of the sub-transactions use However, I'm not sure how the mitigation would work. Can't the relayer still calculate a "right" gas limit for which the |
My understanding is that as using |
I see, this sounds reasonable, i need a bit more time eto think about it and if it is, we'll apply this mitigation |
Lines of code
https://github.com/AmbireTech/ambire-common/blob/5c54f8005e90ad481df8e34e85718f3d2bfa2ace/contracts/AmbireAccount.sol#L119-L123
Vulnerability details
Attacker can force the failure of transactions that use
tryCatch
An attacker or malicious relayer can force the failure of transactions that rely on
tryCatch()
by carefully choosing the gas limit.Impact
The
tryCatch()
function present in the AmbireAccount contract can be used to execute a call in the context of a wallet that is eventually allowed to fail, i.e. the operation doesn't revert if the call fails.https://github.com/AmbireTech/ambire-common/blob/5c54f8005e90ad481df8e34e85718f3d2bfa2ace/contracts/AmbireAccount.sol#L119-L123
EIP-150 introduces the "rule of 1/64th" in which 1/64th of the available is reserved in the calling context and the rest of it is forward to the external call. This means that, potentially, the called function can run out of gas, while the calling context may have some gas to eventually continue and finish execution successfully.
A malicious relayer, or a malicious actor that front-runs the transaction, can carefully choose the gas limit to make the call to
tryCatch()
fail due out of gas, while still saving some gas in the main context to continue execution. Even if the underlying call intryCatch()
would succeed, an attacker can force its failure, while the main call to the wallet is successfully executed.Proof of Concept
The following test reproduces the attack. The user creates a transaction to execute a call using
tryCatch()
to a function of the TestTryCatch contract, which simulates some operations that consume gas. The attacker then executes the bundle by carefully choosing the gas limit (450,000 units of gas in this case) so that the call to TestTryCatch fails due to out of gas, but the main call toexecute()
in the wallet (here simplified by usingexecuteBySender()
to avoid signatures) gets correctly executed.Note: the snippet shows only the relevant code for the test. Full test file can be found here.
Recommendation
The context that does the call in
tryCatch()
can check the remaining gas after the call to determine if the remaining amount is greater than 1/64 of the available gas before the external call.Assessed type
Other
The text was updated successfully, but these errors were encountered: