AI Arena - c0pp3rscr3w3r's results

In AI Arena you train an AI character to battle in a platform fighting game. Imagine a cross between PokΓ©mon and Super Smash Bros, but the characters are AIs, and you can train them to learn almost any skill in preparation for battle.

General Information

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

AI Arena

Findings Distribution

Researcher Performance

Rank: 177/283

Findings: 2

Award: $7.39

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L363

Vulnerability details

Impact

Staked tokens are not meant to be transferred, but they can be transferred to another address by simply using another function for transferring that was not overridden.

Proof of Concept

FighterFarm.sol, locks staked tokens by having _ableToTransfer return false when token is staked.

    function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) {
        return (
          _isApprovedOrOwner(msg.sender, tokenId) &&
          balanceOf(to) < MAX_FIGHTERS_ALLOWED &&
          !fighterStaked[tokenId]
        );
    }

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L539-L544

This check is done in safeTransferFrom and transferFrom

    function safeTransferFrom(
        address from, 
        address to, 
        uint256 tokenId
    ) 
        public 
        override(ERC721, IERC721)
    {
        require(_ableToTransfer(tokenId, to));
        _safeTransfer(from, to, tokenId, "");
    }
.
.
.
    function transferFrom(
        address from, 
        address to, 
        uint256 tokenId
    ) 
        public 
        override(ERC721, IERC721)
    {
        require(_ableToTransfer(tokenId, to));
        _transfer(from, to, tokenId);
    }
.
.
.

However there's another public safeTransferFrom with a different parameter list that will be public and available for user use but not have _ableToTransfer check

In ERC721.sol

    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 with this parameter list is not overridden and hence appears for user to use without the _ableToTransfer require statement.

Can be further be verified by using console prints on the safeTransfer functions. One on the
function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data ) in ERC721.sol

and another in FighterFarm.sol

Tools Used

manual analysis

override all forms of transfer in ERC721.sol

Assessed type

Access Control

#0 - c4-pre-sort

2024-02-23T06:00:39Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-23T06:00:48Z

raymondfam marked the issue as duplicate of #739

#2 - c4-judge

2024-03-11T02:57:50Z

HickupHH3 marked the issue as satisfactory

Awards

7.2869 USDC - $7.29

Labels

bug
2 (Med Risk)
insufficient quality report
partial-25
:robot:_153_group
duplicate-1507

External Links

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L268-L276

Vulnerability details

Impact

Address with hasStakerRole in Fighter farm can lock any token id he/she wants without any sort of permission from user

Proof of Concept

    function updateFighterStaking(uint256 tokenId, bool stakingStatus) external {
        require(hasStakerRole[msg.sender]);
        fighterStaked[tokenId] = stakingStatus;
        if (stakingStatus) {
            emit Locked(tokenId);
        } else {
            emit Unlocked(tokenId);
        }
    }

The staker address can behave maliciously and lock any token id they want. The fact that once an address that has been added to hasStakerRole, can't be removed makes this worse as the bad actor cannot be removed from this abusive form of authority.

Staking affects an user's ability to transfer

function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) { return ( _isApprovedOrOwner(msg.sender, tokenId) && balanceOf(to) < MAX_FIGHTERS_ALLOWED && !fighterStaked[tokenId] ); }

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L363

person with hasStakerRole, cannot be revoked from his authority

    function addStaker(address newStaker) external {
        require(msg.sender == _ownerAddress);
        hasStakerRole[newStaker] = true;
    }

there are no other functions that modify staker. https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L139

Tools Used

manual analysis

Have both of these mitigations.

  • Have an user's explicit permission stored in state to stake his/her token id.
  • Have an option for owner to remove staker authority from an address.

Assessed type

DoS

#0 - c4-pre-sort

2024-02-24T06:25:25Z

raymondfam marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-02-24T06:25:32Z

raymondfam marked the issue as duplicate of #20

#2 - HickupHH3

2024-03-05T10:06:05Z

Ignoring the privileged access with the hasStakerRole, this touches a little on the inability to remove stakers

#3 - c4-judge

2024-03-05T10:06:09Z

HickupHH3 marked the issue as partial-25

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