Home - Coinspect Security

Compound TUSD Integration

Total Losses

No Loss

Date

Network

ethereum logo ethereum

Categories

business logic data validation

Step-by-step

  1. Call sweepToken specifying the secondary address of tUSD.
  2. Take advantage of the new price of tUSD now that there is no underlying balance.

Detailed Description

The issue was discovered by ChainSecurity during their audit of Compound.

The most important fact to understand is that the tUSD has two contracts. This is similar in how a proxy contract works, but there are implementation differences (tUSD was developed before proxy standards were popularized).

tUSD has a primary contract and a legacy contract. The legacy contract delegates its calls to the primary contract. Note how this is different from current proxy designs: the legacy contract delegates call to the current one, but the current one can still be used directly!

Now, Compound implemented a sweepToken method. This method is supposed to transfer all the balances of a token from the contract to an admin. This is useful in case users mistakenly send a token (say, USDC) by mistake to the contract. With this, they can call sweepToken and contact the admin so their funds are returned.

pragma solidity ^0.8.6;

function sweepToken(EIP20NonStandardInterface token) override external {
    require(address(token) != underlying, "CErc20::sweepToken: can not sweep underlying token");
    uint256 balance = token.balanceOf(address(this));
    token.transfer(admin, balance);
}

It is important for this method to check that token is not its underlying! If it were, one could transfer all of the balance’s of the contract to the admin. Remember, this is intended for mistakes. The contract is supposed to have balances of its underlying!

Now we have the two pieces of the puzzle to understand the vulnerability. This sweepToken does not work for tokens like tUSD. An attacker can supply the address of the legacy tUSD contract, which will pass the require clause (because the legacy one is not underlying) but will return the balances of the primary tUSD and transfer from it!

This causes the internal exchange rate of the contract to change, which elevates this vulnerablity from a griefing to a lucrative exploit for an attacker.

Possible mitigations

  • ChainSecurity proposes an interesting fix: checking the underlying balance before and after the transfer to make sure it stays the same.