Frax Ether Liquid Staking contest - csanuragjain's results

A liquid ETH staking derivative designed to uniquely leverage the Frax Finance ecosystem.

General Information

Platform: Code4rena

Start Date: 22/09/2022

Pot Size: $30,000 USDC

Total HM: 12

Participants: 133

Period: 3 days

Judge: 0xean

Total Solo HM: 2

Id: 165

League: ETH

Frax Finance

Findings Distribution

Researcher Performance

Rank: 53/133

Findings: 2

Award: $48.01

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

19.982 USDC - $19.98

Labels

bug
duplicate
2 (Med Risk)

External Links

Lines of code

https://github.com/code-423n4/2022-09-frax/blob/main/src/frxETHMinter.sol#L191

Vulnerability details

Impact

Normally Admin should only be allowed to transfer upto currentWithheldETH to himself. But using recoverEther function, Admin can transfer all Ether (which were meant for validators) to himself

Proof of Concept

  1. Observe the recoverEther function
function recoverEther(uint256 amount) external onlyByOwnGov { (bool success,) = address(owner).call{ value: amount }(""); require(success, "Invalid transfer"); emit EmergencyEtherRecovered(amount); }
  1. Observe there is no restriction placed on this function and governance can use this to extract all ether which were meant to be used for validators

Remove this function if not required

#0 - FortisFortuna

2022-09-25T21:23:09Z

We are well aware of the permission structure. The owner will most likely be a large multisig. We mentioned the Frax Multisig in the scope too. If moving funds, it is assumed someone in the multisig would catch an invalid or malicious address.

#1 - joestakey

2022-09-26T15:04:26Z

Duplicate of #107

Lines of code

https://github.com/corddry/ERC4626/blob/643cd044fac34bcbf64e1c3790a5126fec0dbec1/src/xERC4626.sol#L37

Vulnerability details

Impact

In constructor of xERC4626.sol, there is no check to validate if rewardsCycleLength<= block.timestamp.safeCastTo32(). This can cause the full reward distribution to fail since rewardsCycleEnd will become 0 in this case

Proof of Concept

  1. xERC4626.sol is deployed with _rewardsCycleLength as 2000000000
  2. rewardsCycleEnd gets calculated like below
rewardsCycleEnd = (block.timestamp.safeCastTo32() / rewardsCycleLength) * rewardsCycleLength;
  1. Since division is happening first so this becomes :
rewardsCycleEnd= (1663949858/2000000000)*2000000000 = 0*2000000000=0
  1. Since rewardsCycleEnd is 0, syncRewards which is responsible for addition of reward will fail due to division by zero
uint32 end = ((timestamp + rewardsCycleLength) / rewardsCycleLength) * rewardsCycleLength; // Since rewardsCycleLength is 0 so this fails as division by zero

In constructor add a new check like below

require(rewardsCycleLength<= block.timestamp.safeCastTo32(), "Invalid reward cycle");

#0 - FortisFortuna

2022-09-26T01:46:32Z

2000000000 seconds is like 63 years. Not going to happen in a real life scenario.

#1 - csanuragjain

2022-09-26T06:32:14Z

@FortisFortuna 2000000000 seconds is just an example (probably a bad example). Any value above block.timestamp.safeCastTo32() will cause rewardsCycleEnd to be 0

#2 - FortisFortuna

2022-09-26T22:55:37Z

Current block.timestamp is like what, 53+ years since the epoch. The reward period is going to be nowhere close to that

#3 - 0xean

2022-10-12T14:26:33Z

This really comes down to input sanitization and the wardens suggestion protects against only a really extreme edge case. Downgrading to QA

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