Summary
A security vulnerability was identified in the testFuzzCallBaseUri function while testing the TokenUtil::_callBaseUri logic. The function attempts to retrieve a base URI from an ERC721 or ERC1155 contract by trying two different function signatures (_baseUri() and baseUri()). However, when called on addresses that do not implement these signatures, the function causes unexpected reverts.
Vulnerability Details
The TokenUtil::_callBaseUri function tries to execute static calls to check for _baseUri() and baseUri() functions in the provided contract address. If these function signatures do not exist, it results in a revert, which was observed during fuzz testing.
function _callBaseUri(address collection) internal view returns (bool, string memory) {
bool success;
uint256 returnSize;
uint256 returnValue;
bytes memory ret;
bytes[2] memory encodedSignatures =
[abi.encodeWithSignature("_baseUri()"), abi.encodeWithSignature("baseUri()")];
for (uint256 i = 0; i < 2; i++) {
bytes memory encodedParams = encodedSignatures[i];
assembly {
success := staticcall(gas(), collection, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
if success {
returnSize := returndatasize()
returnValue := mload(0x00)
ret := mload(0x40)
mstore(ret, returnSize)
returndatacopy(add(ret, 0x20), 0, returnSize)
mstore(0x40, add(add(ret, 0x20), add(returnSize, 0x20)))
}
}
if (success && returnSize >= 0x20 && returnValue > 0) {
return (true, abi.decode(ret, (string)));
}
}
return (false, "");
}
POC
The fuzz test for the testFuzzCallBaseUri function resulted in a failure when called on the address `0x0000000000000000000000000000000000000002. This indicates a revert during execution due to non-existent function signatures.
pragma solidity ^0.8.0;
import "../../src/token/TokenUtil.sol";
import "forge-std/Test.sol";
import "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
import "openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol";
contract TokenUtilFuzzTest is Test {
DummyERC721 private erc721;
DummyERC1155 private erc1155;
function setUp() public {
erc721 = new DummyERC721("Dummy ERC721", "DERC721");
erc1155 = new DummyERC1155("");
}
function testFuzzCallBaseUri(address collection) public {
uint256 size;
assembly {
size := extcodesize(collection)
}
if (size == 0) {
vm.expectRevert();
TokenUtil._callBaseUri(collection);
return;
}
bool success;
string memory result;
(success, result) = TokenUtil._callBaseUri(collection);
if (success) {
emit log_string(result);
}
}
}
contract DummyERC721 is ERC721 {
constructor(string memory name, string memory symbol) ERC721(name, symbol) {}
function _baseURI() internal view virtual override returns (string memory) {
return "https://dummybaseuri.com/";
}
function mint(address to, uint256 tokenId) public {
_mint(to, tokenId);
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_exists(tokenId), "ERC721: invalid token ID");
return super.tokenURI(tokenId);
}
}
contract DummyERC1155 is ERC1155 {
constructor(string memory initialUri) ERC1155(initialUri) {}
function mint(address to, uint256 id, uint256 amount, bytes memory data) public {
_mint(to, id, amount, data);
}
function uri(uint256 tokenId) public view virtual override returns (string memory) {
return string(abi.encodePacked("https://dummyuri.com/token/", Strings.toString(tokenId)));
}
}
Impact
Security Impact: Medium
The primary risk is that the TokenUtil::_callBaseUri function may cause unexpected reverts for contracts that do not implement the expected function signatures. This has the potential to disrupt the functioning of dApps relying on this utility, leading to degraded user experience or unexpected failures in contract interactions.
Tools Used
Recommendations
To reduce the risk of unexpected reverts, it is recommended to update the _callBaseUri function to include better error handling.