Boot Finance contest - gpersoon's results

Custom DEX AMM for Defi Projects

General Information

Platform: Code4rena

Start Date: 04/11/2021

Pot Size: $50,000 USDC

Total HM: 20

Participants: 28

Period: 7 days

Judge: 0xean

Total Solo HM: 11

Id: 51

League: ETH

Boot Finance

Findings Distribution

Researcher Performance

Rank: 8/28

Findings: 4

Award: $2,360.32

🌟 Selected for report: 4

πŸš€ Solo Findings: 2

Findings Information

🌟 Selected for report: gpersoon

Also found by: elprofesor, fr0zn, pauliax

Labels

bug
3 (High Risk)
resolved
sponsor confirmed

Awards

529.9131 USDC - $529.91

External Links

Handle

gpersoon

Vulnerability details

Impact

Suppose someone claims the last part of his airdrop via claimExact() of AirdropDistribution.sol Then airdrop[msg.sender].amount will be set to 0.

Suppose you then call validate() again. The check "airdrop[msg.sender].amount == 0" will allow you to continue, because amount has just be set to 0. In the next part of the function, airdrop[msg.sender] is overwritten with fresh values and airdrop[msg.sender].claimed will be reset to 0.

Now you can claim your airdrop again (as long as there are tokens present in the contract)

Note: The function claim() prevents this from happening via "assert(airdrop[msg.sender].amount - claimable != 0);", which has its own problems, see other reported issues.

Proof of Concept

// https://github.com/code-423n4/2021-11-bootfinance/blob/7c457b2b5ba6b2c887dafdf7428fd577e405d652/vesting/contracts/AirdropDistribution.sol#L555-L563

function claimExact(uint256 _value) external nonReentrant { require(msg.sender != address(0)); require(airdrop[msg.sender].amount != 0);

uint256 avail = _available_supply(); uint256 claimable = avail * airdrop[msg.sender].fraction / 10**18; // if (airdrop[msg.sender].claimed != 0){ claimable -= airdrop[msg.sender].claimed; } require(airdrop[msg.sender].amount >= claimable); // amount can be equal to claimable require(_value <= claimable); // _value can be equal to claimable airdrop[msg.sender].amount -= _value; // amount will be set to 0 with the last claim

// https://github.com/code-423n4/2021-11-bootfinance/blob/7c457b2b5ba6b2c887dafdf7428fd577e405d652/vesting/contracts/AirdropDistribution.sol#L504-L517 function validate() external nonReentrant { ... require(airdrop[msg.sender].amount == 0, "Already validated."); ... Airdrop memory newAirdrop = Airdrop(airdroppable, 0, airdroppable, 10**18 * airdroppable / airdrop_supply); airdrop[msg.sender] = newAirdrop; validated[msg.sender] = 1; // this is set, but isn't checked on entry of this function

Tools Used

Add the following to validate() : require(validated[msg.sender]== 0, "Already validated.");

#0 - chickenpie347

2021-11-10T02:20:20Z

Addressed in issue #101

#1 - CloudEllie

2022-01-05T02:21:46Z

Added labels to match duplicate issue #101

Findings Information

🌟 Selected for report: gpersoon

Labels

bug
2 (Med Risk)
disagree with severity
resolved
sponsor confirmed

Awards

872.285 USDC - $872.28

External Links

Handle

gpersoon

Vulnerability details

Impact

Suppose you are an investor and want to claim the last part of your claimable tokens (or your entire set of claimable tokens if you haven't claimed anything yet). Then you call the function claim() of InvestorDistribution.sol, which has the following statement: "require(investors[msg.sender].amount - claimable != 0);" This statement will prevent you from claiming your tokens because it will stop execution.

Note: with the function claimExact() it is possible to claim the last part.

Proof of Concept

// https://github.com/code-423n4/2021-11-bootfinance/blob/7c457b2b5ba6b2c887dafdf7428fd577e405d652/vesting/contracts/InvestorDistribution.sol#L113-L128

function claim() external nonReentrant { ... require(investors[msg.sender].amount - claimable != 0); investors[msg.sender].amount -= claimable;

Tools Used

Remove the require statement.

#0 - chickenpie347

2021-11-10T02:25:14Z

Duplicate of issue #130

#1 - chickenpie347

2021-11-10T02:26:45Z

I just noticed it's different files. The AirdropDistrbution.sol and InvestorDistribution.sol contracts were built on the same base, with slight changes.

#2 - 0xean

2022-01-08T02:22:46Z

Downgrading to medium risk as an alternative path does exist for claiming the drop. Funds are not lost, but the availability of them is compromised. Per Docs:

2 β€” Med: Assets not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements. 3 β€” High: Assets can be stolen/lost/compromised directly (or indirectly if there is a valid attack path that does not have hand-wavy hypotheticals).

Findings Information

🌟 Selected for report: gpersoon

Labels

bug
2 (Med Risk)
disagree with severity
resolved
sponsor confirmed

Awards

872.285 USDC - $872.28

External Links

Handle

gpersoon

Vulnerability details

Impact

Suppose you are eligible for the last part of your airdrop (or your entire airdrop if you haven't claimed anything yet). Then you call the function claim() of AirdropDistribution.sol, which has the following statement: "assert(airdrop[msg.sender].amount - claimable != 0);" This statement will prevent you from claiming your airdrop because it will stop execution.

Note: with the function claimExact() it is possible to claim the last part.

Proof of Concept

// https://github.com/code-423n4/2021-11-bootfinance/blob/7c457b2b5ba6b2c887dafdf7428fd577e405d652/vesting/contracts/AirdropDistribution.sol#L522-L536

function claim() external nonReentrant { .. assert(airdrop[msg.sender].amount - claimable != 0); airdrop[msg.sender].amount -= claimable;

Tools Used

Remove the assert statement. Also add the following to validate() , to prevent claiming the airdrop again: require(validated[msg.sender]== 0, "Already validated.");

#0 - chickenpie347

2021-11-10T14:08:57Z

Patched it with assert(airdrop[msg.sender].amount - claimable >= 0); the >=0 check is just to ensure the claimant does not end up claiming more than allocated due to any fringe case.

#1 - 0xean

2022-01-08T02:22:41Z

Downgrading to medium risk as an alternative path does exist for claiming the drop. Funds are not lost, but the availability of them is compromised. Per Docs:

2 β€” Med: Assets not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements. 3 β€” High: Assets can be stolen/lost/compromised directly (or indirectly if there is a valid attack path that does not have hand-wavy hypotheticals).

Findings Information

🌟 Selected for report: gpersoon

Also found by: WatchPug, cmichel, hyh, leastwood, pauliax

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

85.8459 USDC - $85.85

External Links

Handle

gpersoon

Vulnerability details

Impact

Anyone can call the function vest() of Vesting.sol, for example with a smail "_amount" of tokens, for any _beneficiary.

The function overwrites the value of benRevocable[_beneficiary], effectively erasing any previous value.

So you can set any _beneficiary to Revocable. Although revoke() is only callable by the owner, this is circumventing the entire mechanism of benRevocable.

Proof of Concept

// https://github.com/code-423n4/2021-11-bootfinance/blob/7c457b2b5ba6b2c887dafdf7428fd577e405d652/vesting/contracts/Vesting.sol#L73-L98

function vest(address _beneficiary, uint256 _amount, uint256 _isRevocable) external payable whenNotPaused { ... if(_isRevocable == 0){ benRevocable[_beneficiary] = [false,false]; // just overwrites the value } else if(_isRevocable == 1){ benRevocable[_beneficiary] = [true,false]; // just overwrites the value }

Tools Used

Whitelist the calling of vest() Or check if values for benRevocable are already set.

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