Potential security implications of CREATE2? (EIP-1014)

Thanks for the detailed explanation with examples.

As @Arachnid explained on gitter, the “bytecode invariant” may not hold for contracts deployed with CREATE2 or even with CREATE if one of its ancestors were deployed with CREATE2.

Therefore, it’s going to be even more critical going forward that the security pattern leverages EXTCODEHASH before interacting with other contracts.

So it sounds like the bottomline is that these concerns are not new, have been deliberated, no mitigations are necessary and what is needed is incorporating this guideline in the security best practises.

This is a very interesting idea.

I really think this is an antipattern; for interoperability and for permissionless innovation, security models should not depend on called contracts having specific bytecode. Contracts should be designed to be secure regardless of the callee’s code.

…and it won’t help here; a malicious token contract could be deleted and replaced with one with a constructor that produces the same byte code, but allocates the initial balances arbitrarily.

3 Likes

Yes, that naturally depends on what properties you want guarantees on. I was more thinking of the scenario where you audit a potential ERC20 token before enrolling it, to ensure that it will behave in certain ways when invoked.

Darn! So EXTCODEHASH offers no protection against this scenario. Doesn’t this alone justify a mitigation measure? @Arachnid’s below suggestion on gitter seems elegant:

I honestly think the simplest solution to all of this would have been to modify self destruct to leave an account’s nonce intact. Selfdestruct is already an ineffective way to encourage freeing state, and this would solve the issues.

Does this have any other side-effects?

Some of the text at https://github.com/ethereum/EIPs/issues/684 from @vbuterin seems to perhaps raise this concern as well, but I don’t have all the insight/context and could be mistaken:

Specification

If a contract creation is attempted, due to either a creation transaction or the CREATE (or future CREATE2) opcode, and the destination address already has either nonzero nonce, or nonempty code, then the creation throws immediately, with exactly the same behavior as would arise if the first byte in the init code were an invalid opcode. This applies retroactively starting from genesis.

Rationale

This is the most correct approach for handling the case where a contract is created in a slot where a contract already exists, as the current behavior of overwriting contract code is highly unintuitive and dangerous.

Currently this is not an issue because there is no way to create a contract with the same address twice without spending >2^80 computational effort to find an address collision, but with #86 this will change. Hence it is important to have correct behavior for this situation in the long term. This can be safely applied retroactively for simplicity, because currently creating a contract with the same address twice is computationally infeasible.

1 Like

Is there any way to characterise the overall risk here given that EXTCODEHASH does not fully protect against all scenarios?

If the risk is minimal, that would justify status quo. If the risk of misuse is high and all mitigations require consensus-changes, then what is the best way forward?

“Can’t change the balances arbitrarily” seems like a pretty important property to preserve!

Again, relying on a callee contract to have specific behaviour is a bad pattern. We should be authoring contracts to be secure regardless of what a third-party contract does.

1 Like

Yes, defensive programming is something everyone should ideally practise and educate, but if there is a design flaw, it should be addressed as such without shifting that burden to the user/developer/auditor.

And this unintended/unintuitive side-effect of CREATE2 which can be maliciously exploited in multiple ways certainly looks like a flaw in opcode semantics. Yes, it may be too late to fix/remove it now before Constantinople (assuming the cost to fix > risk of exploitation) but has to be addressed soon if we believe this is indeed a design flaw.

If I’ve understood correctly then for it to be possible to mutate contract A’s byte code, contract A must somewhere call selfdestruct. If that’s the case, a big red warning on etherscan.io for post-C contracts which contain selfdestruct opcode would probably make this exploit / obfuscation near useless.

I do think it would pretty much kill off use of selfdestruct but don’t see that as problematic as it is fairly rarely used today due to their being little incentive to include it.

Right, and if that’s a concern, you should not let in contracts that can SELFDESTRUCT.

If, however, your main concern is that it doesn’t do a reentrancy-attack against you, then you might allow that but don’t allow change of code.

For this to work, you would also need to check for delegatecall and callcode

This might sound crazy, but hear me out: couldn’t this also be considered a feature? Obviously, there will be a lot of education, mitigation, etc. required in cases where immutability is the desired behavior, but in the case of upgradeable contracts this could be a great way to cut down on the overhead of needing to delegatecall out to a logic contract, especially in cases where persistent contract storage isn’t required.

I know this is likely a controversial perspective, and any upgradeability mechanism should of course be paired with adequate precautionary measures and / or governance, but if it works, it would be a pretty cool positive unintended consequence!

Interesting thread. I suggest modifying the semantics of CREATE2 so that the created contract starts with a unique nonce, using mechanism similar to what was suggested in dust removal replay protection EIPs

1 Like

disagree.
We need a robust immutable identity for the things we are assume to be immutable. Immutability check should work without lookup through the chain history.

In case of upgradable contracts we should just follow version paradigm: any change is a new version (with own immutable ID). It should be an explicit decision which version (inclusive HEAD) to follow. That is why I am not quite happy with current proxy based solution, but your proposal will make things even worse.

For my taste I would implement upgradable contracts by using ENS resolution.

Look, I agree that this will make things a little trickier than before. However, there will still be ways to perform the immutability check without looking at the history:

  • Ensure that no selfdestruct, delegatecall or callcode opcodes appear in the contract,
  • If there are delegatecall or callcode opcodes, ensure that it’s not possible to somehow reach a selfdestruct opcode,
  • Enforce that the contract has been created by some trusted source (such as an established CREATE2 factory contract that maintains a mapping of deployed contract addresses and will not allow a contract to be redeployed) via a require statement in the constructor or some other means.

The need for upgradeable contracts has been well-established at this point, and many of the most high-impact issues in Ethereum’s history thus far could have been mitigated with adequate upgradeability mechanisms in place. Obviously it’s not a paradigm that should be forced onto everyone, but if there are ways to implement it more efficiently, it will greatly benefit those that do need it. Both the proxy pattern and ENS resolution result in increased overhead vs. a direct equivalent, but an optimal upgradeable contract would operate on par with a standard contract from an efficiency standpoint. This could be a way to achieve that aim, assuming the corresponding risks can be properly addressed.

This is a good idea! A proxy contract which is rather easy - you send the seed and the init_code to this contract and it has a mapping(address => bool) which holds if a contract is deployed at this address (hence the return code of CREATE2 or generated by using the definition of address generation by CREATE2). It sets the value of the address key to true if a contract has been created successfully at this address. If the value of this key is true before creating the contract it reverts.

1 Like

Is there any design pattern that would want a contract deployed with CREATE1 by a contract deployed with CREATE2 that isn’t looking to take advantage of this? If there isn’t, any contract deployed by this method can just be flagged as being able to have its code change.

I think it might end up being a lot trickier than before. For example, it seems that the only possible secure contract is one that contains no selfdestruct, delegatecall, or callcode. Otherwise, you’d need to verify not only that delegated calls cannot possibly hit a selfdestruct opcode but also that the delegate contract contains no selfdesctruct opcode itself. What if the delegate contract, itself, contains delegated calls? The dynamics of this question get complicated pretty rapidly.

Also, as @carver pointed out, there are cases where you have to perform a similarly chained verification process in the reverse direction, verifying the properties of creator contracts and the creators of those creator contracts, and so on.

The recursive nature of this problem gives me the intuition that there are even more complex dynamics still lurking beneath the surface that may yet be discovered.

2 Likes

We can deploy a “Safe CREATE2 deployment” contract where it is not possible to recreate at the same address.

1 Like

Just in case this is actually a debate about removing CREATE2 from Constantinople, or delaying it, then I oppose both.

I think the checks @0age lists are easy enough to make:

Any system not following those parameters should raise red flags.

1 Like

Not to disagree with myself, but I will admit I am worried about an attack that includes some social engineering.

Specifically:

  1. Project team gets and audit on system using a DELEGATECALL proxy pattern for upgradability
  2. Project team “Forgets” to mention that it will be deployed using CREATE2.
  3. System looks OK to auditors.
  4. … the rest is self explanatory

The only real protection against this requires a lot of UX and engineering work by etherscan et al.

1 Like