Platform: Code4rena
Start Date: 09/02/2024
Pot Size: $60,500 USDC
Total HM: 17
Participants: 283
Period: 12 days
Judge:
Id: 328
League: ETH
Rank: 259/283
Findings: 3
Award: $0.14
π Selected for report: 0
π Solo Findings: 0
π Selected for report: CodeWasp
Also found by: 0x13, 0xAlix2, 0xAsen, 0xCiphky, 0xE1, 0xLogos, 0xaghas, 0xlemon, 0xlyov, 0xvj, ADM, Aamir, BARW, Bauchibred, DMoore, DanielArmstrong, Draiakoo, Fulum, GhK3Ndf, Josh4324, Kalogerone, KmanOfficial, Krace, KupiaSec, Limbooo, MidgarAudits, MrPotatoMagic, PedroZurdo, Pelz, Tendency, _eperezok, adam-idarrha, al88nsk, alexxander, alexzoid, aslanbek, blutorque, btk, c0pp3rscr3w3r, cartlex_, cats, d3e4, deadrxsezzz, denzi_, devblixt, dimulski, erosjohn, evmboi32, fnanni, grearlake, hulkvision, immeas, israeladelaja, jaydhales, jesjupyter, jnforja, juancito, klau5, korok, ktg, ladboy233, matejdb, merlinboii, novamanbg, nuthan2x, oualidpro, peanuts, petro_1912, pkqs90, pynschon, radev_sw, rouhsamad, sashik_eth, shaka, sobieski, soliditywala, stackachu, tallo, thank_you, ubl4nk, vnavascues, web3pwn, xchen1130, zhaojohnson
0.1044 USDC - $0.10
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol?plain=1#L355-L365 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol?plain=1#L539-L545
The FighterFarm contract has a _ableToTransfer() function that is called when safeTransferFrom() is called. However, this function is not applied to both safeTransferFrom() functions that are available to ERC721 contracts.
Because of this, fighter owners can freely transfer fighters bypassing _ableToTransfer() which prevents the following:
The ERC721 contract has the following function that is not overridden in the FighterFarm contract:
/** * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data ) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved"); _safeTransfer(from, to, tokenId, data); }
This function is not overridden in the FighterFarm contract and therefore does not include the _ableToTransfer() guard check.
To test this, add the following test to the FighterFarm.t.sol file:
function testSafeTransferFromBypass() public { _mintFromMergingPool(_ownerAddress); assertEq(_fighterFarmContract.ownerOf(0), _ownerAddress); // AUDIT: we are auditing the _fighterFarmContract.addStaker(_ownerAddress); _fighterFarmContract.updateFighterStaking(0, true); _fighterFarmContract.safeTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, 0, ""); assertEq(_fighterFarmContract.ownerOf(0), _DELEGATED_ADDRESS); }
Manual inspection and slither.
Override the additional safeTransferFrom() function and add the _ableToTransfer() guard check.
function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data ) public override(ERC721, IERC721) { require(_ableToTransfer(tokenId, to)); _safeTransfer(from, to, tokenId, data); }
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol?plain=1#L355-L365
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol?plain=1#L539-L545
Access Control
#0 - c4-pre-sort
2024-02-23T05:52:53Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T05:53:00Z
raymondfam marked the issue as duplicate of #739
#2 - c4-judge
2024-03-11T02:09:26Z
HickupHH3 changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-11T02:53:12Z
HickupHH3 marked the issue as satisfactory
π Selected for report: Aamir
Also found by: 0rpse, 0x11singh99, 0x13, 0xAlix2, 0xAsen, 0xBinChook, 0xCiphky, 0xE1, 0xKowalski, 0xLogos, 0xWallSecurity, 0xaghas, 0xbranded, 0xlemon, 0xlyov, 0xpoor4ever, 0xprinc, 0xvj, ADM, Aymen0909, BARW, Bauchibred, Breeje, CodeWasp, DMoore, DeFiHackLabs, Draiakoo, Fulum, GhK3Ndf, Greed, Jorgect, Josh4324, Kalogerone, KmanOfficial, Krace, Limbooo, McToady, MidgarAudits, MrPotatoMagic, PedroZurdo, Pelz, Ryonen, SovaSlava, SpicyMeatball, Tendency, Timenov, ZanyBonzy, _eperezok, al88nsk, alexxander, alexzoid, aslanbek, blutorque, btk, cartlex_, cats, csanuragjain, deadrxsezzz, denzi_, devblixt, dimulski, djxploit, erosjohn, evmboi32, fnanni, grearlake, haxatron, hulkvision, immeas, israeladelaja, jaydhales, jesjupyter, jnforja, josephdara, juancito, kiqo, klau5, korok, krikolkk, ktg, kutugu, ladboy233, lil_eth, m4ttm, matejdb, merlinboii, n0kto, ni8mare, novamanbg, nuthan2x, oualidpro, pa6kuda, peter, petro_1912, pkqs90, pynschon, sandy, sashik_eth, shaflow2, shaka, sobieski, soliditywala, solmaxis69, stackachu, tallo, thank_you, tpiliposian, ubl4nk, visualbits, vnavascues, web3pwn, xchen1130, zhaojohnson
0.0037 USDC - $0.00
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol?plain=1#L291-L303
Owners of game items can bypass the transferable check in safeTransferFrom() by calling ERC1155.safeBatchTransferFrom(). This is because the GameItems contract does not override the ERC1155.safeBatchTransferFrom() function.
The GameItems.safeTransferFrom() function has the following check:
function safeTransferFrom( address from, address to, uint256 tokenId, uint256 amount, bytes memory data ) public override(ERC1155) { // AUDIT: custom check added by the protocol here to confirm only transferable game items can be transferred. require(allGameItemAttributes[tokenId].transferable); super.safeTransferFrom(from, to, tokenId, amount, data); }
However, GameItems inherits from ERC1155 which has a safeBatchTransferFrom() function that can be used to transfer game items, regardless if they are allowed to be transferred.
You can see this in the following forge test below which can be added to the GameItems.t.sol file:
function testSafeTransferFromBypass() public { _fundUserWith4kNeuronByTreasury(_ownerAddress); _gameItemsContract.mint(0, 1); uint256[] memory ids = new uint256[](1); uint256[] memory amounts = new uint256[](1); ids[0] = 0; amounts[0] = 1; _gameItemsContract.adjustTransferability(0, false); _gameItemsContract.safeBatchTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, ids, amounts, ""); assertEq(_gameItemsContract.balanceOf(_DELEGATED_ADDRESS, 0), 1); assertEq(_gameItemsContract.balanceOf(_ownerAddress, 0), 0); }
Manual inspection
Override the ERC1155.safeBatchTransferFrom() function and confirm that only transferrable game items are transferrable.
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol?plain=1#L291-L303
Access Control
#0 - c4-pre-sort
2024-02-22T04:38:25Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T04:39:14Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:29:42Z
raymondfam marked the issue as duplicate of #575
#3 - c4-judge
2024-03-05T04:47:38Z
HickupHH3 changed the severity to 3 (High Risk)
#4 - c4-judge
2024-03-05T04:58:24Z
HickupHH3 marked the issue as satisfactory
π Selected for report: klau5
Also found by: 0rpse, 0xBinChook, 0xDetermination, 0xGreyWolf, 0xLogos, 0xWallSecurity, 0xaghas, 0xgrbr, 0xkaju, 0xlyov, AlexCzm, BARW, Blank_Space, BoRonGod, Daniel526, DanielArmstrong, Draiakoo, FloatingPragma, Giorgio, Greed, Jorgect, Matue, McToady, MidgarAudits, Nyxaris, PUSH0, PedroZurdo, Pelz, PoeAudits, Silvermist, SpicyMeatball, Tekken, Tricko, Tumelo_Crypto, VAD37, WoolCentaur, Zac, alexzoid, andywer, aslanbek, bgsmallerbear, cats, d3e4, desaperh, dimulski, dutra, erosjohn, evmboi32, favelanky, fnanni, forkforkdog, gesha17, givn, grearlake, haxatron, honey-k12, iamandreiski, immeas, juancito, kaveyjoe, ke1caM, kiqo, klau5, korok, lil_eth, lsaudit, n0kto, ni8mare, niser93, pa6kuda, peanuts, peter, shaka, sl1, soliditywala, solmaxis69, t0x1c, tallo, thank_you, tpiliposian, visualbits, vnavascues, web3pwn, yotov721
0.0352 USDC - $0.04
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol?plain=1#L379
Users can arbitrarily control the rarityRank value, a value designed to determine what attribute a fighter is to retrieve, including the fighter receives a rare attribute. Since the user can control the rarityRank via dna, a user can call FighterFarm.reRoll() to re-roll their physical attributes to receive rare attributes.
To begin, DNA from re-rolls are calculated via the fighter owner's address. A fighter owner can transfer the fighter to any arbitrary address they control. Below we can see how the msg.sender acts as an input to generate a dna:
uint256 dna = uint256(keccak256(abi.encode(msg.sender, tokenId, numRerolls[tokenId])));
Next, we'll consider that in order to determine the rarity of attributes, AI Arena utilizes the following formula in the AiArenaHelper contract:
function createPhysicalAttributes( ... // AUDIT: the rarityRank is calculated based on the modulus of dna. Since dna is a user-controlled value, the hacker also controls the value of the rarityRank. Note that rarityRank is a poor name here as it should be randomNumber. uint256 rarityRank = (dna / attributeToDnaDivisor[attributes[i]]) % 100; uint256 attributeIndex = dnaToIndex(generation, rarityRank, attributes[i]); ... } function dnaToIndex(uint256 generation, uint256 rarityRank, string memory attribute) public view returns (uint256 attributeProbabilityIndex) { uint8[] memory attrProbabilities = getAttributeProbabilities(generation, attribute); uint256 cumProb = 0; uint256 attrProbabilitiesLength = attrProbabilities.length; for (uint8 i = 0; i < attrProbabilitiesLength; i++) { cumProb += attrProbabilities[I]; // The rarityRank is used to generate attribute probabilities. By controlling the rarityRank, the fighter owner can also control the value of attributeProbabilityIndex if (cumProb >= rarityRank) { attributeProbabilityIndex = i + 1; break; } } return attributeProbabilityIndex; }
As you can see above, the dna's modulus is used to calculate what's called the rarityRank, a pseudorandom number. Although as mentioned previously, it is not a pseudo random number as the user can control the dna value via the fighter owner address.
In anticipation of a re-roll, the fighter owner can generate a series of random addresses via Vanity ETH and determine off-chain which address will give them the best dna to receive rare attributes.
This can result in the following scenario:
Manual inspection.
The protocol should not rely on the msg.sender as that value is user-controlled. Instead the protocol should rely on a different pseudorandom number such as block.timestamp and block.difficulty. Although these value could be manipulated by miners, the difficulty is quite high to manipulate such values.
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol?plain=1#L379
en/de-code
#0 - c4-pre-sort
2024-02-24T02:05:40Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-24T02:06:00Z
raymondfam marked the issue as duplicate of #53
#2 - c4-judge
2024-03-06T03:49:26Z
HickupHH3 changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-06T03:54:17Z
HickupHH3 marked the issue as satisfactory
#4 - c4-judge
2024-03-15T02:10:54Z
HickupHH3 changed the severity to 2 (Med Risk)
#5 - c4-judge
2024-03-22T04:23:14Z
HickupHH3 marked the issue as duplicate of #376