Summary
The HorseStore.huff::MAIN()
macro is missing a revert statement after the function selector, leaving the protocol vulnerable to
unintended function calls. If an nonexistent function is invoked, it could lead to the execution of the wrong function.
Vulnerability Details
When the protocol is called with a nonexistent function, it defaults to calling GET_TOTAL_SUPPLY()
. Due to the absence of a revert
statement after the selector checks and not finding a matching function, it falls through to GET_TOTAL_SUPPLY()
.
To rectify this, a revert should be added after the checks.
Additionally, there is a reference to MINT_HORSE()
at the end of the functions, which should be removed.
#define macro MAIN() = takes (0) returns (0) {
0x00 calldataload 0xE0 shr
dup1 __FUNC_SIG(totalSupply) eq totalSupply jumpi
....
dup1 __FUNC_SIG(ownerOf)eq ownerOf jumpi
@> missing revert after no function match the unexsistent function, will hit the first one
totalSupply:
GET_TOTAL_SUPPLY()
Impact
Calling the protocol with an existing function may lead to an unintended function being executed, potentially causing issues.
While the current scenario with GET_TOTAL_SUPPLY()
is harmless, if a critical function were in its place, it could pose a significant
security risk to the protocol.
A test scenario demonstrates calling the protocol with a non-existing function, such as thisFunctionNotExists
,
resulting in the execution of the GET_TOTAL_SUPPLY()
function and retrieving the total supply as a result.
function testMint() public {
vm.startPrank(user);
horseStoreHuff.mintHorse();
(bool success, bytes memory result) = address(horseStoreHuff).call(abi.encodeWithSignature("thisFunctionNotExists()"));
uint256 totalSupply = abi.decode(result, (uint256));
console.log("RESULT ", totalSupply);
}
Output
Tools Used
Manual review
Recommendations
Please consider adding a revert statement after the selectors check to enhance security. Additionally, remove the reference to
MINT_HORSE()
at the end.
#define macro MAIN() = takes (0) returns (0) {
// Identify which function is being called.
0x00 calldataload 0xE0 shr
dup1 __FUNC_SIG(totalSupply) eq totalSupply jumpi
dup1 __FUNC_SIG(feedHorse) eq feedHorse jumpi
dup1 __FUNC_SIG(isHappyHorse) eq isHappyHorse jumpi
dup1 __FUNC_SIG(horseIdToFedTimeStamp) eq horseIdToFedTimeStamp jumpi
dup1 __FUNC_SIG(mintHorse) eq mintHorse jumpi
dup1 __FUNC_SIG(HORSE_HAPPY_IF_FED_WITHIN) eq horseHappyIfFedWithin jumpi
dup1 __FUNC_SIG(approve) eq approve jumpi
dup1 __FUNC_SIG(setApprovalForAll) eq setApprovalForAll jumpi
dup1 __FUNC_SIG(transferFrom) eq transferFrom jumpi
dup1 __FUNC_SIG(name) eq name jumpi
dup1 __FUNC_SIG(symbol) eq symbol jumpi
dup1 __FUNC_SIG(tokenURI) eq tokenURI jumpi
dup1 __FUNC_SIG(supportsInterface)eq supportsInterface jumpi
dup1 __FUNC_SIG(getApproved) eq getApproved jumpi
dup1 __FUNC_SIG(isApprovedForAll) eq isApprovedForAll jumpi
dup1 __FUNC_SIG(balanceOf) eq balanceOf jumpi
dup1 __FUNC_SIG(ownerOf)eq ownerOf jumpi
+ 0x00 0x00 revert //Revert if no match found
totalSupply:
GET_TOTAL_SUPPLY()
feedHorse:
FEED_HORSE()
isHappyHorse:
IS_HAPPY_HORSE()
mintHorse:
MINT_HORSE()
horseIdToFedTimeStamp:
GET_HORSE_FED_TIMESTAMP()
horseHappyIfFedWithin:
HORSE_HAPPY_IF_FED_WITHIN()
approve:
APPROVE()
setApprovalForAll:
SET_APPROVAL_FOR_ALL()
transferFrom:
TRANSFER_FROM()
name:
NAME()
symbol:
SYMBOL()
tokenURI:
TOKEN_URI()
supportsInterface:
SUPPORTS_INTERFACE()
getApproved:
GET_APPROVED()
isApprovedForAll:
IS_APPROVED_FOR_ALL()
balanceOf:
BALANCE_OF()
ownerOf:
OWNER_OF()
- MINT_HORSE()
}