Sherlock contest - gpersoon's results

Decentralized exploit protection.

General Information

Platform: Code4rena

Start Date: 22/07/2021

Pot Size: $80,000 USDC

Total HM: 6

Participants: 14

Period: 7 days

Judge: ghoulsol

Total Solo HM: 3

Id: 21

League: ETH

Sherlock

Findings Distribution

Researcher Performance

Rank: 2/14

Findings: 5

Award: $17,918.99

🌟 Selected for report: 10

🚀 Solo Findings: 2

Findings Information

🌟 Selected for report: cmichel

Also found by: gpersoon, walker

Labels

bug
duplicate
3 (High Risk)

Awards

3366.2338 USDC - $3,366.23

External Links

Handle

gpersoon

Vulnerability details

Impact

If one of the protocols doesn't have enough funds in its protocolBalance, then _payOffDebt will revert when trying to subtract the debt. This also means the function payOffDebtAll will revert. As this function is called from several other functions, including from the Payout function (via _doSherX), it could block a lot of functions, including Payout.

Normally a low balance shouldn't happen because the sherlock DAO will encourage the protocols to fund their account. However if a protocol would be in trouble and couldn't fund its balance, it would block the smart contracts unnecessarily.

Note1: a workaround would be to increase to balance of the protocol by the sherlock DAO via depositProtocolBalance Note2: a protocol (in panic) could make it worse by withdrawing their balance via withdrawProtocolBalance Note3: it will be even worse if an attacker got access to the protocolAgent account and can do withdrawProtocolBalance

Proof of Concept

// https://github.com/code-423n4/2021-07-sherlock/blob/main/contracts/libraries/LibPool.sol#L84 function payOffDebtAll(IERC20 _token) external { PoolStorage.Base storage ps = PoolStorage.ps(_token); uint256 blocks = block.number.sub(ps.totalPremiumLastPaid);

uint256 totalAccruedDebt; for (uint256 i = 0; i < ps.protocols.length; i++) { totalAccruedDebt = totalAccruedDebt.add(_payOffDebt(ps, ps.protocols[i], blocks)); } // move funds to the sherX etf ps.sherXUnderlying = ps.sherXUnderlying.add(totalAccruedDebt); ps.totalPremiumLastPaid = uint40(block.number);

}

function _payOffDebt( PoolStorage.Base storage ps,bytes32 _protocol,uint256 _blocks) private returns (uint256 debt) { debt = _accruedDebt(ps, _protocol, _blocks); ps.protocolBalance[_protocol] = ps.protocolBalance[_protocol].sub(debt); // might revert if protocolBalance is too low }

Tools Used

Redesign the functions payOffDebtAll and _payOffDebt, where the block period calculation (with totalPremiumLastPaid) is moved to _payOffDebt and calculated per protocol. If a protocol can't pay, the function _payOffDebt could skip this protocol. Then the sherlock DAO should take appropriate actions to fix the problems.

#0 - Evert0x

2021-07-30T14:45:13Z

#119

#1 - ghoul-sol

2021-08-29T23:42:34Z

Duplicate of #119 so high risk

Findings Information

🌟 Selected for report: gpersoon

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

3740.2597 USDC - $3,740.26

External Links

Handle

gpersoon

Vulnerability details

Impact

GovDev.sol has a function updateSolution to upgrade parts of the contract via the Diamond construction. Via updateSolution any functionality can be changed and all the funds can be accessed/rugged. Even if this is well intended the project could still be called out resulting in a reputation risk, see for example: https://twitter.com/RugDocIO/status/1411732108029181960

Note: there is a function transferGovDev which can be used to disable the updateSolution

Proof of Concept

// https://github.com/code-423n4/2021-07-sherlock/blob/main/contracts/facets/GovDev.sol#L25 function updateSolution(IDiamondCut.FacetCut[] memory _diamondCut,address _init,bytes memory _calldata) external override { require(msg.sender == LibDiamond.contractOwner(), 'NOT_DEV'); return LibDiamond.diamondCut(_diamondCut, _init, _calldata); } }

Tools Used

Apply extra safeguards for example to limit the time period where updateSolution can be used.

#0 - Evert0x

2021-07-30T14:44:11Z

Fair point, although we are not anonymous, we still want to mitigate this risk.

I'm thinking something like this

  • update is pushed, everyone can review the code changes
  • 14 days of waiting, people are able to get their funds out
  • update is executed.

Downside is that it doesn't allow us to fix potential critical issues fast.

Findings Information

🌟 Selected for report: gpersoon

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

3740.2597 USDC - $3,740.26

External Links

Handle

gpersoon

Vulnerability details

Impact

When a large payout occurs, it will lower unallocatedSherX. This could mean some parties might not be able to get their Yield.

The first couple of users (for which harvest is called or which transfer tokens) will be able to get their full Yield, until the moment unallocatedSherX is depleted. The next users don't get any yield at all. This doesn't seem fair.

Proof of Concept

// https://github.com/code-423n4/2021-07-sherlock/blob/main/contracts/facets/SherX.sol#L309 function doYield(ILock token,address from, address to, uint256 amount) private { ... ps.unallocatedSherX = ps.unallocatedSherX.sub(withdrawable_amount);

//https://github.com/code-423n4/2021-07-sherlock/blob/main/contracts/facets/Payout.sol#L108 function payout( address _payout, IERC20[] memory _tokens, uint256[] memory _firstMoneyOut, uint256[] memory _amounts, uint256[] memory _unallocatedSherX, address _exclude ) external override onlyGovPayout { // all pools (including SherX pool) can be deducted fmo and balance // deducting balance will reduce the users underlying value of stake token // for every pool, _unallocatedSherX can be deducted, this will decrease outstanding SherX rewards // for users that did not claim them (e.g materialized them and included in SherX pool) .... // Subtract from unallocated, as the tokens are now allocated to this payout call ps.unallocatedSherX = ps.unallocatedSherX.sub(unallocatedSherX);

Tools Used

If unallocatedSherX is insufficient to provide for all the yields, only give the yields partly (so that each user gets their fair share).

#0 - Evert0x

2021-07-30T16:11:47Z

Not only unallocatedSherX is subtracted but also sWeight, which is used to calculate the reward. I wrote some extra tests and in my experience the remaining SherX (in the unallocatedSherX variable) is splitted in a fair way.

#1 - Evert0x

2021-08-03T10:19:06Z

Together with gpersoon I discussed both issue #49 and #50 and based on both findings we found a med-risk issue. In case payout() is called with _unallocatedSherX > 0 and a user called harvest() before the payout call. It blocks the user from calling harvest() again. + blocks the lock token transfer.

Mitigations step is to stop calling payout() with _unallocatedSherX > 0

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