Platform: Code4rena
Start Date: 25/01/2023
Pot Size: $36,500 USDC
Total HM: 11
Participants: 173
Period: 5 days
Judge: kirk-baird
Total Solo HM: 1
Id: 208
League: ETH
Rank: 7/173
Findings: 2
Award: $1,173.86
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: carlitox477
Also found by: 0xRobocop, 0xbepresent, ArmedGoose, Atarpara, IllIllI, Tointer, UdarTeam, adriro, betweenETHlines, cryptojedi88, evan, glcanvas, gzeon, horsefacts, ladboy233, libratus, lukris02, luxartvinsec, manikantanynala97, minhquanym, mookimgo, p4st13r4, simon135, thekmj, trustindistrust
18.6976 USDC - $18.70
It is possible that an attacker can cause users who are trying to claim tokens, to be dosed (out of gas)
When a user calls claim
function, it figures out how many tokens the msg.sender
owns.
https://github.com/rabbitholegg/quest-protocol/blob/bd213e8629bb8587dd4bb35f3e9e8f8e42b40336/contracts/RabbitHoleReceipt.sol#L109-L118
uint msgSenderBalance = balanceOf(claimingAddress_); uint[] memory tokenIdsForQuest = new uint[](msgSenderBalance); uint foundTokens = 0; for (uint i = 0; i < msgSenderBalance; i++) {
uint256[] memory tokens = rabbitHoleReceiptContract.getOwnedTokenIdsOfQuest(questId, msg.sender); if (tokens.length == 0) revert NoTokensToClaim(); uint256 redeemableTokenCount = 0; for (uint256 i = 0; i < tokens.length; i++) { if (!isClaimed(tokens[i])) { redeemableTokenCount++; }
this goes through the tokens and assigns them claimed https://github.com/rabbitholegg/quest-protocol/blob/068d628f019e9469aecbf676370075c1f6c980fd/contracts/Quest.sol#L113
for (uint256 i = 0; i < tokenIds_.length; i++) {
these 3 code snippets loop over msg.sender
tokens.
since it loops over tokens that a user owns an attacker can transfer claimed tokens to a user and cause them high gas costs and possibly revert (out of gas)
ex:
Alice(attacker)
bob(victim)
5000 recipient tokens
Alice owns 1000
claim
functionanother scenario would be where token owners want to gang up on Bob
so they cause bob to revert
Bob can only claim rewards if he sends the tokens to another address or burns them
But doing so would still cost a lot of gas.
Vim
Make the claim
function based on user input tokenId
which then the function can do the checking with out the high gas cost
///pseudocode function claim(uint tokenId) if (isclaimed(tokenId){ revert }
#0 - c4-judge
2023-02-05T06:11:16Z
kirk-baird marked the issue as duplicate of #135
#1 - c4-judge
2023-02-14T09:17:53Z
kirk-baird marked the issue as satisfactory
🌟 Selected for report: simon135
Also found by: 0x4non, ArmedGoose, ForkEth, zaskoh
1155.155 USDC - $1,155.16
If the reward token is erc1155/erc777
an attacker can reenter and then buy/transfer another unclaimed token to the attacker address and then the var
redeemTokens
wont be equal to how many tokens were actually redeemed.
ex:
reward token is an erc1155 that has _afterTokenTransfer
Alice(attacker) has 2 receipt tokens, the first one is on a smart contract that will do the reentrancy, and the second one is on Alice's address but is approved to transfer to the smart contract(the own that holds the first receipt)
claim
rewardsIERC1155(rewardToken).safeTransferFrom(address(this), msg.sender, rewardAmountInWeiOrTokenId, amount_, '0x00');
_afterTokenTransfer
which causes the sc to call a function in its fallback function that transfers the approved token to the sctry IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
redeemedTokens
= tokens that are redeemed is false because it doesn't account for the first token that we reentered.
The issue is worse with erc777
tokens because of the fact that accounting will be in the withdrawRemainingTokens
functionuint256 unclaimedTokens = (receiptRedeemers() - redeemedTokens) * rewardAmountInWeiOrTokenId; uint256 nonClaimableTokens = IERC20(rewardToken).balanceOf(address(this)) - protocolFee() - unclaimedTokens; IERC20(rewardToken).safeTransfer(to_, nonClaimableTokens);
after the reentrancy
ex: redeemedTokens=9
but should be 10
receiptRedeemers()=12
rewardAmountInWeiOrTokenId=1e5
unclaimedTokens=300000
assuming they are some tokens left
balance(address(this)=201000
and protocolFee=500
nonClaimableTokens=201000 - 500 - 300000
it would revert ( negative numbers with uint) and funds would be stuck in the contract forever
The real estimate for nonClaimableTokens=201000-500-200000=500
and the owner can get funds out
but 500 wei will be lost in the contract
and it can get worse with large amounts of quests and the attacker reentering multiple times to cause a bigger gap between the real redeemedTokens
add nonReentrancy modifier
#0 - c4-judge
2023-02-06T09:17:43Z
kirk-baird marked the issue as duplicate of #239
#1 - c4-judge
2023-02-14T09:22:02Z
kirk-baird changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-02-14T09:22:07Z
kirk-baird marked the issue as satisfactory
#3 - c4-judge
2023-02-14T09:22:17Z
kirk-baird marked the issue as selected for report
#4 - c4-sponsor
2023-04-11T20:04:26Z
waynehoover marked the issue as sponsor confirmed