Platform: Code4rena
Start Date: 14/10/2022
Pot Size: $100,000 USDC
Total HM: 12
Participants: 75
Period: 9 days
Judge: GalloDaSballo
Total Solo HM: 1
Id: 171
League: ETH
Rank: 61/75
Findings: 1
Award: $0.33
🌟 Selected for report: 0
🚀 Solo Findings: 0
0.3268 USDC - $0.33
https://github.com/code-423n4/2022-10-traderjoe/blob/main//src/LBToken.sol#L182-L195
Users can "mint" free LPTokens to themselves. They can then redeem these LBTokens to drain assets from the respective LBPair-instance. To do that, a user first has to obtain some LBTokens. He can then transfer the tokens to himself. Due to a bug in the internal accounting logic, his balance will increase after the transfer.
The bug can also be used to break the following contract-invariant, which can then lead to unexpected behaviors in different parts of the contract:
forall id in uint256 . LBToken.totalSupply(id) = sum({LBToken.balanceOf(user, id) | user in address})
Here is a simple exploit that demonstrates the vulnerability.
In this example, a malicious user doubles his LBToken balance with a single call to safeTransferFrom
(notice that this bug also works with the safeBatchTransferFrom
-function).
Let $id
be the id of an arbitrary LBToken bin.
Let $alice
be the address of an attacker.
Let $amount
be $alice
's balance of $id
, i.e. _balances[$id][$alice] = $amount
.
Assume $amount > 0
.
Now, $alice
executes a token transfer with herself as _from
and _to
-address:
safeTransferFrom($alice, $alice, $id, $amount)
.
After the transfer, $alice
's balance will have doubled, i.e., _balances[$id][$alice] = 2 * $amount
.
Hence, $alice
could double her balance almost for free (only paying gas costs).
See below for the mechanized exploit code.
Here is a foundry test that demonstrates the exploit. Notice a passing test means that the exploit was successful.
diff --git a/test/LBToken.t.sol b/test/LBToken.t.sol index d263153..517721a 100644 --- a/test/LBToken.t.sol +++ b/test/LBToken.t.sol @@ -27,6 +27,18 @@ contract LiquidityBinTokenTest is TestHelper { pair = createLBPairDefaultFees(token6D, token18D); } + function testSafeTransferExploit() public { + uint256 amountIn = 1e18; + + (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, 5, 0); + + uint256 id = _ids[0]; + uint256 amount = pair.balanceOf(DEV, id); + + pair.safeTransferFrom(DEV, DEV, id, amount); + assertEq(pair.balanceOf(DEV, id), 2 * amount); + } + function testSafeBatchTransferFrom() public { uint256 amountIn = 1e18;
None.
The problem is that the internal _transfer
-function does not handle the case correctly when the _from
-address equals the _to
-address.
Here is what the _transfer
-function does wrong that enables the above attack:
_from
and _to
from storage and caches them in local memory._to
-address and store the result in the storage._from
-address and store the result in the storage.Notice when we have _from = _to
, step 3 will overwrite the effect of step 2. Hence, we only increment the receiver's balance but do not decrease the original owner's balance. Since both addresses are the same, we only increment the user's balance.
_from = _to
.#0 - Shungy
2022-10-23T21:50:17Z
I believe this finding to be valid.
Duplicate: https://github.com/code-423n4/2022-10-traderjoe-findings/issues/266
#1 - GalloDaSballo
2022-10-26T16:36:13Z
#2 - c4-judge
2022-11-23T18:28:40Z
GalloDaSballo marked the issue as not a duplicate
#3 - c4-judge
2022-11-23T18:29:40Z
GalloDaSballo marked the issue as duplicate of #299
#4 - Simon-Busch
2022-12-05T06:40:37Z
Marked this issue as Satisfactory as requested by @GalloDaSballo