LSD Network - Stakehouse contest - chaduke's results

A permissionless 3 pool liquid staking solution for Ethereum.

General Information

Platform: Code4rena

Start Date: 11/11/2022

Pot Size: $90,500 USDC

Total HM: 52

Participants: 92

Period: 7 days

Judge: LSDan

Total Solo HM: 20

Id: 182

League: ETH

Stakehouse Protocol

Findings Distribution

Researcher Performance

Rank: 40/92

Findings: 3

Award: $189.50

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: ladboy233

Also found by: Trust, chaduke, joestakey

Labels

bug
2 (Med Risk)
downgraded by judge
partial-50
satisfactory
duplicate-106

Awards

61.5174 USDC - $61.52

External Links

Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L202

Vulnerability details

Impact

Detailed description of the impact of this finding. The implementation of executeAsSmartWallet() is too powerful. It allows a dao to drain any node runner's wallet anytime.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept. Let's say Dao's address is A, and a node runner's address is B. Deploy the following contract at address C. Suppose there are 5 eth in the wallet.

Basically, Dao just needs to call from contract at address A the following line (if A is an EOA, then dao can just change to a new Dao address, which is a contract):

executeAsSmartWallet(B, A, abi.encodeWithSelector(SendMeETH.thanks.selector, 5 eth);

In other words, Dao can ask B's smart wallet to execute a function of A (say thanks()) while sending A 5 eth (or whatever the ETH balance of B's smart wallet is).

Tools Used

Remix

In summary, a dao should not be able to ask the smart wallet to call an arbitrary external contract and function, this is very risky. This not only makes the dao overly powerful, but also a huge security concern in case the dao's address is compromised.

#0 - c4-judge

2022-11-20T15:04:06Z

dmvt marked the issue as unsatisfactory: Overinflated severity

#1 - dmvt

2022-11-20T21:23:50Z

The quality of the report in #106 has changed my mind.

#2 - c4-judge

2022-11-20T21:24:08Z

dmvt marked the issue as duplicate of #106

#3 - c4-judge

2022-11-20T21:24:19Z

dmvt marked the issue as partial-50

#4 - c4-judge

2022-11-20T21:24:27Z

dmvt changed the severity to 2 (Med Risk)

#5 - c4-judge

2022-11-29T22:59:01Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: rbserver

Also found by: chaduke

Labels

2 (Med Risk)
partial-25
duplicate-383

Awards

75.9475 USDC - $75.95

External Links

Judge has assessed an item in Issue #16 as M risk. The relevant finding follows:

AQ6: https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L356 This function provides too much power to Dao, if the dao calls the function, then he can be the node runner of each smart wallet and then call withdrawETHForKnot to drain each smart wallet.

#0 - c4-judge

2022-11-29T15:13:24Z

dmvt marked the issue as duplicate of #383

#1 - c4-judge

2022-12-02T22:08:16Z

dmvt marked the issue as partial-25

GA1: https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LPTokenFactory.sol#L18-L22 It is better NOT to pass the implementation as an argument, which makes it hard to verify and trust its correctness. Use the following code instead:

constructor(address _lpTokenImplementation) { lpTokenImplementation = address(new LPToken()); }

GA2: https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L805

It should be

bytes[] memory _blsPublicKeyOfKnots = new bytes[1];

GA3: In LiquidStakingManager.sol#L805, in many occasions, it calls the execute() function in the smart wall to call a function in the ``getTransctionRouter()''. It might be better to define that function (or a wrapper) in OwnableSmartWallet explicit and call it instead.

GA4: https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L343 A node runner should be able to withdraw all the balance in the smart wallet instead of a fix 4eth in case some more ETH has been sent to the smart wallet by accident.

GA5: https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L829 sETH was received in this line from the stakehouse to the smartWallet first, then transferred to the LiquidStakingManger, and then finally transferred to the syndicate in the account of the smartwallet. The accounting can be done more efficiently, by receiving the sETH to the syndicate directly and then gives credit to the corresponding smartwallet/blsPubickey directly.

AQ6: https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L356 This function provides too much power to Dao, if the dao calls the function, then he can be the node runner of each smart wallet and then call withdrawETHForKnot to drain each smart wallet.

#0 - c4-judge

2022-11-29T15:14:40Z

dmvt marked the issue as grade-b

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