BlockDev Sp. Z o.o · Report #672664
Steal collateral during `end` process, by earning DSR interest after `flow`.
How it was found
Summary:
The end contract in MCD controls the process of shutting down
the MCD contracts and allowing for users to redeem their DAI for
collateral -- presumably to migrate to a new implementation of DAI.
The process, however, doesn't prevent the continued functioniong
of DAI savings accounts (pot contract), which allows for continued
minting of DAI after all other contracts have been "caged", resulting
in theft (possibly involuntary) of collateral.
Detailed Description
The end contract is responsible for orchestrating the complex sequence
of steps for shutting down the MCD eco-system, settling all existing DAI
into collateral during the process.
The first step in the process is the method cage, which ensures that other
MCD contracts stop operating in the normal way, and enter a "not-live" mode.
In particular, the vat contract is updated to prevent the creation of new
CDP's, and also prevents the accrual of interest (vat.fold). This is obtained by
calling the cage method in the vat contract.
Puzzingly, however, the end.cage method doesn't affect the state of the pot
(savings account) contract, allowing for interests to be continuously earned
-- and new DAI to be minted --indefinitely during all the phases of the end
process. Most significantly, it allows a user to mint new DAI even after the
final DAI/collateral rate has been fixed (end.flow).
The consequence is that it's possible to inflate the DAI supply so that there isn't enough collateral for all of it to be redeemed. In that case the last users to try to redeem will have their collateral stolen by the faster ones, as they might well be unable to redeem any DAI at all.
An example might help clarify the problem:
-
Suppose there are two users, Ali and Bob, who each control 50% percent of the DAI supply, lets say 10 DAI each.
-
Now let's assume the
endprocess is initiated and proceeds as usual -- eventually reaching theflowstage, with a fixed exchange rate of 1 DAI / ETH. -
Let's also assume that there is a DSR rate of 100% a month (unrealistic, but makes the numbers easier).
-
After the
end.flowis called, Ali notices that the he can still usepotto earn interests, so he deposits all his DAI inpot. Meanwhile Bob can't do the same as his funds are locked inside a Dapp (let's say an Augur market). -
After one month, Ali calls
pot.exitand gets back 20 DAI. That corresponds to the total original supply of DAI beforeend.flowwas called. So, Ali callsend.packandend.cashto convert his 20 DAI into 20 ETH -- all the collateral in the MCD contracts. -
When Bob tries to redeem his DAI, there is no collateral left. His
end.cashcalls fail and he ends up with no tokens -- DAI or ETH -- at all.
Steps to Reproduce
I've attached to this report a version of end.t.sol that adds a test scenario
(test_steal_collateral_using_dsr_after_thaw) to reproduce this attack (in fact, the example above).
Please don't hesitate to contact me if you need more help reproducing it.
Possible Remediation
The issue could be completely prevented by introducing a cage functionality into
the pot contract, and not allowing the pot.drip method to be called when
not in live mode.
Please note that the above solution is provided as proof that the reported issue is fixable. I make no claim that the above is the best available solution.
Impact
Please refer to the "Impact Analysis" field for more details.
Final Note
Please don't hesitate to contact me if you need any further clarification around this issue, or help reproducing and evaluating it.
Impact
Impact Analysis
As clearly demonstrated above, the reported bug can be used to steal collateral
from the end contract. Even more disturbingly, the bug can likely cause users
that own DSR deposits to unwittingly steal collateral in case of a shutdown.
Let's evaluate how much collateral can be stolen in this scenarios. The amount stolen depends on three factors:
1 - DSR savings rate.
2 - Portion of DAI kept in DSR deposits.
3 - Time distribution of users calling end.pack.
It's impossible to know beforehand either. But we can make educated guesses about a worst-case scenario.
It's possible that the DSR rate will be set at a high value at some point. Considering that the previous incarnation of DAI saw a the CDP rate reach 25% at some point, it's definitely possible for DSR to reach a slightly lower rate, say 20%. Furthermore, it's likely all users (including Dapps) will keep their DAI holdings in DSR deposits, doing so has a possible upside, and minimal gas costs.
As for the time-distribution of users redeeming their DAI, it's again entirely possible that a large portion of the DAI supply will be used to interact with Dapps rather than held speculatively. Augur V2, for example, has plans to use DAI for making bets on prediction markets. Since these markets might take quite a long time to be resolved -- up to several months -- it's unlikely that a DAI shutdown would cause an immediate withdrawal of DAI by Augur users -- if the reported vulnerability isn't known.
Other Dapps might well have similar characteristics, though it's again impossible to know beforehand.
Given the above -- DSR rates up to 20% and most of DAI locked in DSR deposits inside Dapps for months -- it's perfectly possible that the bug leads to a loss of 10% or more of the collateral in the MCD contracts.
That scenario might happen even without an intentional attack.