Parity Proposals’ Potential Problems

Zombie cats

Be careful when you resurrect the dead.

Since the second Parity Multisig hack that froze a total of 514,774 ETH (242 million USD at the time of writing), there has been an ongoing debate about possible ways to unfreeze the funds and return them to their rightful owners. Yesterday, Parity technologies released a blog post and a draft EIP, consisting of four variants of a proposal that would allow the stuck funds to be unfrozen.

All of the Parity proposals essentially address the same issue. In general, they allow self-destructed contracts to be revived through the re-deployment of a new contract at that address. In the following discussion, we will start out by thinking about the security implications of such contract revival proposals in general terms, and then comment on the specifics of Parity’s four proposals. We conclude the post with a recommendation for how a potential unfreeze of funds should work.

A General Security Problem

The Parity proposal allows deployers of contracts to deploy new, different contract code in a dead contract’s stead. Any smart contracts that have been deployed with a self-destruct opcode now allow their creator to re-instantiate them with their choice of code after a self destruct. This allows creators of contracts with self-destruct to self-destruct and re-deploy their contract, potentially leading to loss of users’ funds.

A concrete example: let’s say you had a contract like 0x’s deposit contract, responsible for holding a number of ERC20 tokens as part of its operation. If such a contract could somehow be self-destructed, the creator can attempt to invoke the self-destruction mechanism and replace the deposit contract with a forwarder to themselves. Unwitting users may then send funds to the creators rather than the intended decentralized exchange. We discuss more concrete examples later in the post.

This example may seem contrived, but with the complex networks of interacting, custodial, and interdependent contracts we are building, any contract that relies on the code in contracts it interacts with not changing can be affected by intentionally or accidentally malicious contracts implementing this mechanism. The important security invariant that after deployment, code at address A can only change to empty is broken for any callers.

The Ethereum developers have so far been very conservative with breaking invariants, a strategy that we applaud. For instance, the adoption of EIP-86 was deferred due to concerns about breaking exactly the same invariant that this proposal modifies.

As we were writing this post, Nick Johnson published an excellent analysis that also argues against breaking invariants and looks at the proposed changes from a network-safety point of view. Our posts don’t overlap much, so it’s definitely worth reading his take.

On Semantic Compatibility

The fundamental property violated by Parity’s proposed changes is semantic compatibility. In general, at the time a contract is written, an author expects the contract to operate in a manner determined by the defined and documented semantics of the underlying language and platform (and any core existing contracts). When an auditor audits a contract, the same is true: auditors have to reason quite intimately about very specific edge cases and interactions of EVM constructs, and cannot make assumptions about the behavior of any code in isolation.

The proposed changes would introduce a major change to the semantics and security model of the EVM retroactively, potentially altering the assumptions of programmers, languages, and higher level tools made in the process of building a contract.

A big emphasis in the Ethereum community recently has been placed in high-assurance software development, including rigorous development and testing practices and use of formal models and tools. The proposed changes substantially weaken the security model for any contracts using self-destruct: in high-assurance, safety critical software development, any changes to the underlying platform are rigorously validated, tested, and re-evaluated for effects on higher level software that builds on them. Put another way, no vehicle manufacturer would upgrade its operating system in production without re-testing, re-auditing, and re-evaluating all the software that runs on this OS for bad interactions, side effects, and bugs. And for the proposed change, that includes every smart contract on the network today.

With regards to audits and formal verification, it is important to note that any such work performed on pre-fork contracts may need to be re-done post-fork, specifically if the verification of these contracts used the valid semantic assumptions of unchanging code in other contracts, or if the verified contract contained the self-destruct operation. This could set a precedent that imposes repeated ecosystem costs for an expensive process that ideally should only be required once.

Not all semantics-breaking changes are bad. In some cases, new semantics are introduced to components of the system explicitly specified as changeable (e.g. new opcodes, or new pre-compiles). It is important to not let backwards compatibility become a dogma, but for security, care and minimally invasive procedures are still required.

The Parity Proposals

We noted a number of implementation questions with the concrete code that Parity has proposed, specifically in their Proposals C and D. These are orthogonal to the higher level security issues described above, but nonetheless introduce substantial complexity into the Ethereum platform.

  1. Hacky contract proxies: function setupProxyForContract(uint nonce, address destination) public { proxy[address(keccak256(msg.sender, nonce))] = destination; } is used to set the proxy for contracts in Proposal D. This allows the creator of the contract to set the proxy to any code they want at any time in the future (note that the setupProxyForContract guard does not have a single-call check, and can be used to swap out proxy contracts many times and on the fly; this allows contract creators to essentially hot-swap recovery contracts from under their users). Currently, contract creators hold no special power over contracts they created; this proposal would change that.
  2. Hacky contract proxies 2: The above code does not handle cases where it was a contract that created a contract. In these cases, this creator contract must explicitly include proxy setup functionality, with old contracts that created contracts not able to participate in this proxy process. For a general solution, all cases should be handled.
  3. Asymmetry between ether and tokens: In what is presumably an attempt to reduce the risk of revived contracts stealing funds, Proposals B, C, and D don’t mark the fallback function in the proxy contract as payable. Therefore, revived contracts can receive token payments, but not ether payments. This inconsistency introduces additional complexity in reasoning about the behaviour of revived contracts.
  4. Conflicting mention / security properties of tx.origin and msg.sender: The proposal comment // please note the usage of tx.origin does not seem to match the code below it (same as in (1)). Tx.origin is not compatible with contracts created by contracts being revived (e.g. multisigs that generically forward call data). We believe a discussion of this choice should be a part of the proposal.
  5. Light clients: In this proposal, users can deploy code generating arbitrary logs at dead contract addresses. Especially in Proposal D, any user can generate any log event for any destructed address enabling proxying. In particular, this can allow them to fool light clients and applications that rely on logging code being consistent with the audited contracts they deploy. This is likely to pose a security threat to a wide range of applications.
  6. Inconsistent delays: Delays are used in Proposal B, but not C or D. It is unclear to us whether these were intentionally omitted, because some problems they would help mitigate exist in all three proposals.
  7. msg.sender “spoofing”: Revived contracts will carry the same msg.sender as their previously destructed counterparts. Users can no longer rely on the code of a contract staying consistent across calls to external systems that use msg.sender for authentication (like ERC20 functionality). Two examples of this are provided in the next section.

While the proposed mechanisms (“create contract proxy at any nonce for this address”) would allow victims of the second Parity multisig hack to recover their losses, it isn’t clear that they merit adoption in their own right. A proposal for a general mechanism should be convincing on its own, ignoring the amount of funds from a single hack that could be restored if the proposal were adopted.

Especially proposals C and D would introduce significant additional technical complexity, and make the already difficult problem of reasoning about interactions between smart contracts even harder.

Vulnerable Examples

Here are a few concrete examples of contracts which may be vulnerable to a few of the problems we discussed above.

ERC20 Exchange

Bob’s full-featured exchange contract is responsible for swapping tokens on a blockchain. It’s tied to Bob’s (centralized) web UI, so it has a hard-coded self-destruct function that only Bob can call, recovering funds in case the contract is somehow compromised or to be upgraded. The contract was created by a temporary key used by Bob’s employee, Dave, who after all was just deploying a no-owner contract to the blockchain.

Sensing an incoming hack, Bob self-destructs his contract and reclaims all the users’ funds. Unfortunately, some hard-coded client contracts and exchanges still send funds to Bob’s contract, leaving them stuck there for Dave to steal (if he saved the key).

Oracle

Town Crier is an oracle service in production on Ethereum today that uses Intel's SGX. Like all good smart contract citizens, a Town Crier-like system can include self-destruct capabilities, where IC3 can destruct a contract in the event of an SGX compromise or failure. In the Town Crier security model, you are trusting IC3 for availability but not integrity: when you get answers to your queries, you know they are right. The self destruct provides no security risk in the old EVM model, as you trust IC3 for availability regardless. The current Town Crier contract implements a similar permanent kill switch.

Such a system could also send all its query responses through a proxy contract, that checks that the transaction is really being sent by Town Crier, and charges the user a fee once the query is successfully validated. In fact, this is exactly what Town Crier does (line 182).

With a feature like this, it’s easy to see what can go wrong: IC3 maliciously self-destructs the Town Crier contract, installing its own proxy that sends fake data to Town Crier clients. Because these clients use msg.sender for authentication, they assume the real Town Crier contract is sending them a call (an assumption that, until these proposals, was founded on the EVM semantics).

This proposal makes the Town Crier paradigm impossible unless the Town Crier contract can be validated to not have self-destruct functionality. This defeats the purpose of self-destruct as a cleanup incentive, and forces all security-critical contracts to enforce and audit the absence of a self-destruct, a non-trivial task, especially if delegatecalls are used.

In general, making contract code mutable breaks the use of msg.sender for code authentication and validation, a common use-pattern in both oracles and ERC20 contracts (where, for example, users transfer tokens to an address they know is a multisig to secure them).

Bad Miner

Alice, Bob, and Carol are starting an e-cats based venture together, and have decided to open a Kitty-Backed-Token (KBT), and collect cats together in their contract. Unfortunately, a bad apple hacks their kitty tokens and begins inflating his own supply, withdrawing a higher and higher share of kitties. Luckily, the users of KittyToken can vote to self-destruct the contract before it is too late, allowing its original deployers Alice, Bob, and Carol to collect the cats and save them from certain death.

The Ethereum network has implemented Parity Proposal D, which allows users to set their own proxies for the contract. As soon as the period of time opens to create forwarders, a miner named Dave creates his own proxy and inserts it first into his block, transferring out all the rare kitties in the contract to himself. Dave need not own any KBT to do this, since any user can set their own resolver and essentially spoof their msg.sender as being the new zombie contract, the DeadKittyToken (DKT).

These kitties sit on external contracts, and again have the flaw of using msg.sender for authentication of transfers. Users assume this is OK, because msg.sender can only be the smart contract they audited, but the sneaky code change allows the evil Dave to send all the kitties to 0xdead.

Our Suggestions

A number of steps should be taken to resolve this anti-pattern for future contracts, as well as to potentially deal with previously lost funds. We recommend an alternative set of actions:

Separate funds-recovery for previously vulnerable contracts from changes that need to happen to both the EVM and its tools to avoid similar losses in future contracts, using a clean-slate approach to design the latter. EIP86 seems like the most studied candidate for introducing a redeployment mechanism to Ethereum. We recommend a modification to Solidity’s Security Considerations to cover self-destruct based antipatterns, and a static warning in the Solidity compiler for use of self-destruct, especially when calling a library containing the operation. In our view, this should be sufficient to ensure funds are not lost going forward, specifically in its ability to allow users to demand redeployable contracts.

Offer the community the choice of a fork for past contracts, which should consist of a target contracts curated by a community review process over a significant length of time, remediating losses in previously affected contracts. This fork should be offered in a same manner as the DAO fork, and should not be officially supported by client developers or the Ethereum Foundation (yes, including Parity).

The anti-patterns leading to these funds losses are not a severe and general enough issue to require breaking changes to established EVM behavior, and should be handled through changes to tooling and potential case-by-case remediation for contracts before these changes occurred (we propose this as an option, but do not provide an opinion on whether to recover the funds in this article).

Conclusion

We do not believe the funds loss of recent self-destruct vulnerabilities to be general enough to require a general solution. Such a general solution can be harmful, both in the practical sense of allowing potential future funds loss vectors and in the theoretical sense of setting poor precedents that have the potential to impede the process of reliable software development on Ethereum.

We advocate for separating funds-recovery for past vulnerable contracts, which should be discussed as potential contract-specific forks on their own merits, from changes that need to happen to both the EVM and its tools to avoid the vulnerability of future contracts.

Acknowledgments

Sincere thanks to Ari Juels and Emin Gün Sirer for reading and providing comments on earlier versions of this post.

Share on Linkedin
Share on Reddit
comments powered by Disqus