-
Notifications
You must be signed in to change notification settings - Fork 0
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
Create a onlyWallet
guard to prevent reentrancy with lower guarantees
#140
Conversation
30d2551
to
09ee3ee
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious -- what do we mean by "obscure the hash preimage"?
to clarify my understanding of preimage attacks: they are attacks where it is possible to either:
- find x given y such that h(x) = y; that is, guess the input given output y for the hash function h
- find x' != x such that h(x') = h(x); that is, construct a hash collision
these attacks usually exploit qualities of the hash function itself, that is, they would be based on inadequacies of this particular implementation of keccak256
.
I don't quite follow how or why it would be that subtracting 1 from the hash obscures the preimage, unless we are assuming that the source code (and therefore the literal input, which is written in the source) is inaccessible.
In practice, in our case, the source is accessible -- quark will be open source.
While thinking about this problem, it occurs to me that since re-entrancy flags are ephemeral / transactional, we could actually use something like block.prevrandao
to (at least in theory) nondeterministically obscure the hash preimage vary the hash, in such a way that even by looking at the source it would be very difficult to predict the hash even knowing the input. This non-constancy would protect the re-entrancy guard from a previously-run script setting (and never clearing) the storage slot.
An interesting added protection of adding in a "salt" from prevrandao
is that a script cannot write to the storage slot a priori, and thereby grief a later script that depends on the slot for non-re-entrancy. With a constant slot, a script A that runs before a non-re-entrant script B could brick re-entrancy protections by doing sstore(slot, 1)
-- script B will revert until the flag is manually cleared.
I guess the point, though, is that by using an internal constant
the hash will get inlined at its use-sites and this may make it more difficult to find the hashed value, and even if you do, the input will be slightly obscured by the subtraction? I guess we're protecting the bytecode?
of course, if you just wanted to attack the storage slot, you could interpret the bytecode to the point where you can find the hash -- you don't need the input to write to the slot.
hmm. well, sorry for all the musings, but curious to hear what your thoughts are -- I am probably missing something?
Discussed with 🧇 offline about this. I'm coming around to the idea that Here's my original case for That being said,
The plan of action from here is to write more tests to fully understandable the attack vectors of the |
Agree with |
2e9a4c9
to
7f93be0
Compare
@@ -9,6 +9,7 @@ pragma solidity 0.8.19; | |||
contract Ethcall { | |||
/** | |||
* @notice Execute a single call | |||
* @dev Note: Does not use a reentrancy guard, so make sure to only call into trusted contracts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can consider adding the onlyWallet
guard since it should be much cheaper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, looks like adding the onlyWallet
guard prevents the callback flow from using the guarded function again because that flow uses delegatecall
instead of callcode
. Ethcall
is likely to be used in the callback flow, so we'll avoid adding the guard for now.
See commit 5fe6a2d for more in-depth documentation of this behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some minor comments, but overall lgtm
onlyWallet
guard to prevent reentrancy with lower guarantees
0f9cb50
to
76ddc0f
Compare
SEE EDIT
This gas optimized reentrancy guard (
nonReentrantOptimized
) saves around ~5k+ gas compared to the approach of writing the re-entrancy guard toStateManager
. We leave a note suggesting that it is not as safe as the version that writes toStateManager
.Regardless, this guard should be safe for most scripts since the modifier uses a storage slot addressed at a hash with an unknown string preimage. The guard is safe to use as long as scripts don't write directly to this storage slot in the same execution context.
I originally intended to add the guard to
Ethcall.sol
as well, but decided not to due to the extra 15k gas overhead. I also believe theEthcall
use-case is very different and it should really only be used to call into known and safe contracts. A note of caution is added toEthcall
to call out that it is not reentrancy safe.EDIT: After some discussion, we decided to use the
onlyWallet
guard, which is a cheaper reentrancy guard with weaker guarantees. It protects against typical reentrancy paths that involve acall
, but cannot protect against "recursive reentrancy" involvingdelegatecall
s. That being said, reentrancy attacks rarely, if ever, involvedelegatecall
s and there are loads more to worry about when a script usesdelegatecall
.