Ondo Finance - Shubham's results

Institutional-Grade Finance, Now Onchain.

General Information

Platform: Code4rena

Start Date: 29/03/2024

Pot Size: $36,500 USDC

Total HM: 5

Participants: 72

Period: 5 days

Judge: 3docSec

Total Solo HM: 1

Id: 357

League: ETH

Ondo Finance

Findings Distribution

Researcher Performance

Rank: 17/72

Findings: 2

Award: $72.43

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Awards

64.1515 USDC - $64.15

Labels

bug
2 (Med Risk)
satisfactory
:robot:_26_group
duplicate-32

External Links

Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L632 https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L560 https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L597-L600

Vulnerability details

The function burn is made so the admin can burn rUSDY tokens from any account (this is stated in the comments). However, the admin can’t burn tokens if the account from which they’re trying to burn tokens is blocklisted/sanctioned/not on the allow-list.

Note - The current bug has been reported in the previous audit but still persists in the existing codebase. This does not come under publicly known issues in the current contest scope 'Sanction or KYC related edge cases' given that even though the blocklisted address's funds are locked, the admin has the power to burn the shares. It is also stated that having the BURNER role allows the user to 'burn other holders' rOUSG tokens'. But since the admin is unable to burn those shares, the given bug still persists & is valid.

Proof of Concept

Admin can call burn function to burn rOUSG tokens from any account as intended. This function calls _burnShares().

File: rOUSG.sol

  function burn(
    address _account,
    uint256 _amount
  ) external onlyRole(BURNER_ROLE) {
    uint256 ousgSharesAmount = getSharesByROUSG(_amount);
    if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER)
      revert UnwrapTooSmall();

    _burnShares(_account, ousgSharesAmount); --> @note

    ...

The _burnShares() calls _beforeTokenTransfer() to check if the account to burn is kyc'd or not.

File: rOUSG.sol

  function _burnShares(
    address _account,
    uint256 _sharesAmount
  ) internal whenNotPaused returns (uint256) {
    require(_account != address(0), "BURN_FROM_THE_ZERO_ADDRESS");

    _beforeTokenTransfer(_account, address(0), _sharesAmount);  --> @note
  ...
File: rOUSG.sol

  function _beforeTokenTransfer(
    address from,
    address to,
    uint256
  ) internal view {
    // Check constraints when `transferFrom` is called to facliitate
    // a transfer between two parties that are not `from` or `to`.
    if (from != msg.sender && to != msg.sender) {                                     
      require(_getKYCStatus(msg.sender), "rOUSG: 'sender' address not KYC'd");
    }

    if (from != address(0)) {
      // If not minting
      require(_getKYCStatus(from), "rOUSG: 'from' address not KYC'd");
    }

    if (to != address(0)) {
      // If not burning
      require(_getKYCStatus(to), "rOUSG: 'to' address not KYC'd");
    }
  }

In our case, the from would be the account from which we’re burning tokens, so it’ll enter in the 2nd if: if (from != address(0)). But given that the account is blocked/sanctioned/not on the allow-list, the transaction will revert and the tokens won’t be burned.

Impact

Tokens can't be burned from removed kyc/blocklisted accounts by the admin

Tools Used

Manual Review

When calling burn(), burn the shares of the contract directly without calling _burnShares(). This way the transaction does not call _beforeTokenTransfer and does not revert if the account the shares should be burned from is removed from the kyc list.

Assessed type

Invalid Validation

#0 - c4-pre-sort

2024-04-04T05:11:41Z

0xRobocop marked the issue as duplicate of #237

#1 - c4-judge

2024-04-09T08:32:12Z

3docSec marked the issue as satisfactory

Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L98-L102 https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L554 https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L567

Vulnerability details

Users can mint & redeem OUSG and rOUSG against USDC in the OUSGInstantManager contract. During this process, fee is collected from the user which can be same or different for either the minting or redeeming upto a certain limit. The fee can also be 0 if the protocol chooses so.

However both the minting & the redeeming fee is set to 0 at the time of deployment allowing early users to have an upper hand against the rest of the users.

Given that there is no documentation regarding this behaviour in the publicly known issues or intended by the developers & that the loss can be huge, the given issue comes under Medium severity.

Proof of Concept

The fee is set to 0 initially.

File: OUSGInstantManager.sol

  // Fee collected when minting OUSG (in basis points)
  uint256 public mintFee = 0;

  // Fee collected when redeeming OUSG (in basis points)
  uint256 public redeemFee = 0;

The fee can be set by a trusted address with an upper limit as can be seen in the require statements.

File: OUSGInstantManager.sol

  function setMintFee(
    uint256 _mintFee
  ) external override onlyRole(CONFIGURER_ROLE) {
    require(mintFee < 200, "OUSGInstantManager::setMintFee: Fee too high");
    emit MintFeeSet(mintFee, _mintFee);
    mintFee = _mintFee;
  }


  function setRedeemFee(
    uint256 _redeemFee
  ) external override onlyRole(CONFIGURER_ROLE) {
    require(redeemFee < 200, "OUSGInstantManager::setRedeemFee: Fee too high");
    emit RedeemFeeSet(redeemFee, _redeemFee);
    redeemFee = _redeemFee;
  }

However inside the constructor both the fees are not set & will be 0 until the set functions are called by the configurer. This would allow early users to mint/redeem tokens without paying anything to the protocol which would be a loss to the protocol.

Loss to the protocol is proportional to the amount of tokens being minted/redeemed.

In the event if an attacker carried out a flashloan with a huge amount of usdc to mint & redeem tokens, this would result in a huge loss to the protocol because the fee is set to 0 at this point.

Impact

Loss of funds to the protocol because no fee is assigned for minting & redeeming.

Tools Used

Manual Review

Set the minting & redeeming fee inside the constructor.

Assessed type

Error

#0 - c4-pre-sort

2024-04-04T02:14:54Z

0xRobocop marked the issue as duplicate of #276

#1 - c4-judge

2024-04-09T09:03:10Z

3docSec changed the severity to QA (Quality Assurance)

#2 - c4-judge

2024-04-09T09:04:48Z

3docSec marked the issue as grade-b

#3 - Shubh0412

2024-04-10T11:19:34Z

Hello @3docSec

The mentioned issue falls falls under Medium severity because of the following reasons :-

  1. The protocol is unable to collect fee while both minting & redeeming when the contract is deployed. There is no way to charge those early depositors later because the fee is set for everyone & not per individual.
  2. Just like the first depositor inflation attack vector which causes loss of funds to the depositor, this above finding lies in the same field where early users can front-run & mint/redeem before the fee is set by the admin, causing loss of funds to the protocol.
  3. Given that the minimum amount to mint is 100k, the portion of fee not collected is definitely greater than dust amount.

#4 - 3docSec

2024-04-10T11:57:19Z

Hi, there's a profound difference between the "first depositor inflation attack" that affects AMMs deployed in a permissionless manner, and this issue, which rather relates to a contract in a centralized protocol.

In this context of a centralized protocol, I would say that if admins intend to operate the protocol with a non-zero fee, it would be their mistake to not set these values transactionally with the deployment, and KYCing addresses before the contract is fully configured. The scenario this finding presents requires therefore an admin mistake, which alone is already sufficient for a QA judgment.

Risk is further mitigated by the fact that users are KYC'ed and admins can also remove them from the protocol and seize assets if they misbehave. This means that, with the attack you mentioned, one would risk 100% of their funds for front-running a max 2% fee

#5 - Shubh0412

2024-04-10T12:44:38Z

@3docSec Thank you for explaining the details behind your judgement.

Given the fact that the fee will definitely be 0 at the time of deployment & is later configurable by the admin, leaves the possibility of a bug. But given your reasoning about the time when users are being KYC'ed, thats upto the protocol for decide.

Will leave the final decision in your hands 👍

#6 - 3docSec

2024-04-10T13:11:16Z

Thanks, QA is the final decision 👍

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter