Streaming Protocol contest - hack3r-0m's results

General Information

Platform: Code4rena

Start Date: 30/11/2021

Pot Size: $100,000 USDC

Total HM: 15

Participants: 36

Period: 7 days

Judge: 0xean

Total Solo HM: 4

Id: 62

League: ETH

Streaming Protocol

Findings Distribution

Researcher Performance

Rank: 1/36

Findings: 4

Award: $11,111.38

🌟 Selected for report: 2

πŸš€ Solo Findings: 1

Findings Information

🌟 Selected for report: cmichel

Also found by: 0x0x0x, Ruhum, gpersoon, gzeon, hack3r-0m, pauliax

Labels

bug
duplicate
3 (High Risk)

Awards

611.7761 USDC - $611.78

External Links

Handle

hack3r-0m

Vulnerability details

Impact

https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L795-L803

createStream does not check if deposit token and reward token are different addresses.

Proof of Concept

Not Required

Tools Used

Manual Review

add check require(rewardToken != depositToken)

#0 - 0xean

2022-01-14T21:04:00Z

dupe of #215

Findings Information

🌟 Selected for report: WatchPug

Also found by: Jujic, hack3r-0m

Labels

bug
duplicate
3 (High Risk)

Awards

2175.7011 USDC - $2,175.70

External Links

Handle

hack3r-0m

Vulnerability details

Impact

https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L733-L751

  • user approves token x to stream contract (approval amount is type(uint256).max)
  • user calls createIncentive(token x, someAmount)
  • incentives[x] = someAmount
  • creator calls claimIncentive(token x)
  • incentives[x] = 0
  • governance can arbitraryCall with data as ERC20(token x).transferFrom(victim, self)
  • since user had provided max approval and require(incentives[who] == 0, "inc"); check passes
  • governance can drain anyone's balance who incentivezed stream with high approval. (techically remaining approval > 0)

max approval is very common since one does not want to approve many times due to high gas fees.

Tools Used

Manual Review

add a mapping to keep track of whether incentive of particular token was claimed by creator and set it to true. In arbitraryCall, that mapping[who] should be false proving nobody has incentivised that token before.

#0 - 0xean

2022-01-14T21:40:48Z

dupe of #258

Findings Information

🌟 Selected for report: hack3r-0m

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

8058.1521 USDC - $8,058.15

External Links

Handle

hack3r-0m

Vulnerability details

Impact

https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L229

reverts due to overflow for higher values (but strictly less than type(uint112).max) and hence when user calls exit or withdraw function it will revert and that user will not able to withdraw funds permanentaly.

Proof of Concept

Attaching diff to modify tests to reproduce behaviour:

diff --git a/Streaming/src/test/Locke.t.sol b/Streaming/src/test/Locke.t.sol index 2be8db0..aba19ce 100644 --- a/Streaming/src/test/Locke.t.sol +++ b/Streaming/src/test/Locke.t.sol @@ -166,14 +166,14 @@ contract StreamTest is LockeTest { ); testTokenA.approve(address(stream), type(uint256).max); - stream.fundStream((10**14)*10**18); + stream.fundStream(1000); - alice.doStake(stream, address(testTokenB), (10**13)*10**18); + alice.doStake(stream, address(testTokenB), 100); hevm.warp(startTime + minStreamDuration / 2); // move to half done - bob.doStake(stream, address(testTokenB), (10**13)*10**18); + bob.doStake(stream, address(testTokenB), 100); hevm.warp(startTime + minStreamDuration / 2 + minStreamDuration / 10); @@ -182,10 +182,10 @@ contract StreamTest is LockeTest { hevm.warp(startTime + minStreamDuration + 1); // warp to end of stream - // alice.doClaimReward(stream); - // assertEq(testTokenA.balanceOf(address(alice)), 533*(10**15)); - // bob.doClaimReward(stream); - // assertEq(testTokenA.balanceOf(address(bob)), 466*(10**15)); + alice.doClaimReward(stream); + assertEq(testTokenA.balanceOf(address(alice)), 533); + bob.doClaimReward(stream); + assertEq(testTokenA.balanceOf(address(bob)), 466); } function test_stake() public { diff --git a/Streaming/src/test/utils/LockeTest.sol b/Streaming/src/test/utils/LockeTest.sol index eb38060..a479875 100644 --- a/Streaming/src/test/utils/LockeTest.sol +++ b/Streaming/src/test/utils/LockeTest.sol @@ -90,11 +90,11 @@ abstract contract LockeTest is TestHelpers { testTokenA = ERC20(address(new TestToken("Test Token A", "TTA", 18))); testTokenB = ERC20(address(new TestToken("Test Token B", "TTB", 18))); testTokenC = ERC20(address(new TestToken("Test Token C", "TTC", 18))); - write_balanceOf_ts(address(testTokenA), address(this), (10**14)*10**18); - write_balanceOf_ts(address(testTokenB), address(this), (10**14)*10**18); - write_balanceOf_ts(address(testTokenC), address(this), (10**14)*10**18); - assertEq(testTokenA.balanceOf(address(this)), (10**14)*10**18); - assertEq(testTokenB.balanceOf(address(this)), (10**14)*10**18); + write_balanceOf_ts(address(testTokenA), address(this), 100*10**18); + write_balanceOf_ts(address(testTokenB), address(this), 100*10**18); + write_balanceOf_ts(address(testTokenC), address(this), 100*10**18); + assertEq(testTokenA.balanceOf(address(this)), 100*10**18); + assertEq(testTokenB.balanceOf(address(this)), 100*10**18); defaultStreamFactory = new StreamFactory(address(this), address(this));

Tools Used

Manual Review

Consider doing arithmetic operations in two steps or upcasting to u256 and then downcasting. Alternatively, find a threshold where it breaks and add require condition to not allow total stake per user greater than threshhold.

#0 - itsmetechjay

2021-12-08T14:51:04Z

Per the warden's request, I added this sentence to the mitigation steps: "Alternatively, find a threshold where it breaks and add require condition to not allow total stake per user greater than threshhold."

Findings Information

🌟 Selected for report: hack3r-0m

Also found by: Jujic, toastedsteaksandwich

Labels

bug
1 (Low Risk)
disagree with severity
sponsor acknowledged

Awards

217.5701 USDC - $217.57

External Links

Handle

hack3r-0m

Vulnerability details

Impact

https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L397

https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L711

Due to integer divison of solidity, (999 * 10) / 10000 = 0, so for all values fee * amount < 10000, user does not have to pay if fee is enabled or fee during executing flashloan.

A user can deposit total amount into chunks such that on every fund / flashloan, user does not have to pay fee.

Similiary, (1999 * 10) / 10000 = 1 can be exploited.

Proof of Concept

(included above)

Tools Used

Manual Review

Use FixedPoint Arithmetic library or scale up amount (amount ** 10^18) and scale down(result / (10**18) after performing divison

#0 - brockelmore

2021-12-08T22:49:23Z

duplicates: #148, #198, arguably #145 as well

#1 - brockelmore

2021-12-08T22:51:32Z

We dont want to collect dust anyway. We may end up changing this, but not sure atm.

#2 - 0xean

2022-01-15T01:52:09Z

Marking down to low-risk as the costs of doing so would probably outweigh any benefits on any chain with reasonable gas prices. Increasing the number of decimals used to calc fees could resolve this issue pretty easily.

Findings Information

🌟 Selected for report: gpersoon

Also found by: GiveMeTestEther, Meta0xNull, bitbopper, hack3r-0m, pauliax, pedroais, wuwe1

Labels

bug
duplicate
1 (Low Risk)

Awards

48.1774 USDC - $48.18

External Links

Handle

hack3r-0m

Vulnerability details

https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L197-L201

who is not used anywhere and can be remvoed since it costs gas for copying it into memory.

#0 - 0xean

2022-01-17T13:48:52Z

dupe of #125

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