Pieces Protocol

First Flight #32
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Invalid

Poor Input Parameter Validation in claimNft Function

Summary

The claimNft function lacks comprehensive input validation, performing only a basic zero-address check. This insufficient validation could lead to unexpected behaviors, contract state corruption, and potential exploits through malformed inputs or malicious contract interactions.

Vulnerability Detail

Current implementation shows minimal input validation:

function claimNft(address nftAddress) external {
// Only checks for zero address
if(nftAddress == address(0)) {
revert TokenDivider__NftAddressIsZero();
}
// No validation for:
// - Contract existence
// - NFT interface compliance
// - Token existence
// - Previous fractionalization
ERC20Info storage tokenInfo = nftToErc20Info[nftAddress];
// Continues with operations without further validation...
}

Impact

Insufficient input validation could result in:

  1. Interactions with non-existent or invalid NFT contracts

  2. Processing of non-compliant ERC721 tokens

  3. State corruption through invalid token IDs

  4. Permanent locking of funds or tokens

  5. DoS conditions through malformed inputs

Tool Used

Foundry

Proof of Concept

  1. Invalid Contract Attack

contract InputValidationAttacker {
TokenDivider target;
function attackWithInvalidAddress() external {
// Attack with non-contract address
address randomAddress = address(uint160(uint256(keccak256("random"))));
target.claimNft(randomAddress);
}
function attackWithNonCompliantContract() external {
// Deploy minimal non-ERC721 contract
NonCompliantContract malicious = new NonCompliantContract();
target.claimNft(address(malicious));
}
}
contract NonCompliantContract {
// Empty contract that doesn't implement ERC721
}
  1. Attack Scenarios

// Scenario 1: Non-existent Contract
function proveNonExistentContract() public {
address nonExistent = address(0x1); // Random address
target.claimNft(nonExistent);
// Function will fail with unclear error
}
// Scenario 2: Non-ERC721 Contract
function proveNonCompliantContract() public {
address nonCompliant = address(new NonCompliantContract());
target.claimNft(nonCompliant);
// Function might proceed with undefined behavior
}

Recommended Mitigation Steps

  1. Implement Comprehensive Input Validation

function claimNft(address nftAddress) external nonReentrant {
// Complete input validation
_validateClaimInputs(nftAddress);
// Proceed with claim logic
_processClaim(nftAddress);
}
function _validateClaimInputs(address nftAddress) private view {
// Basic address validation
require(nftAddress != address(0), "Zero address");
// Contract existence check
uint256 size;
assembly {
size := extcodesize(nftAddress)
}
require(size > 0, "Not a contract");
// Interface compliance check
require(
IERC165(nftAddress).supportsInterface(type(IERC721).interfaceId),
"Not ERC721"
);
// Fractionalization status check
ERC20Info storage tokenInfo = nftToErc20Info[nftAddress];
require(
tokenInfo.erc20Address != address(0),
"NFT not fractionalized"
);
// Token existence check
require(
IERC721(nftAddress).ownerOf(tokenInfo.tokenId) == address(this),
"NFT not in contract"
);
}
  1. Add Validation Modifiers

modifier validNFT(address nftAddress) {
require(nftAddress != address(0), "Zero address");
require(
nftToErc20Info[nftAddress].erc20Address != address(0),
"Not fractionalized"
);
_;
}
modifier validERC721(address nftAddress) {
require(nftAddress.code.length > 0, "Not a contract");
require(
IERC165(nftAddress).supportsInterface(type(IERC721).interfaceId),
"Not ERC721"
);
_;
}
  1. Implement Safe Type Checks

library AddressValidator {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
function isERC721(address token) internal view returns (bool) {
try IERC165(token).supportsInterface(type(IERC721).interfaceId) returns (bool supported) {
return supported;
} catch {
return false;
}
}
}
  1. Complete Implementation Example

contract TokenDivider {
using AddressValidator for address;
function claimNft(
address nftAddress
) external
nonReentrant
validNFT(nftAddress)
validERC721(nftAddress)
{
// Load validated info
ERC20Info memory tokenInfo = _getValidatedTokenInfo(nftAddress);
// Process claim with validated inputs
_processClaim(nftAddress, tokenInfo);
}
}

Additional Recommendations

  1. Add Input Boundaries

// Constants for validation
uint256 private constant MAX_TOKEN_ID = type(uint96).max;
uint256 private constant MAX_FRACTION_AMOUNT = 1e27;
  1. Implement Error Collection

error InvalidNFTAddress(string reason);
error InvalidTokenID(uint256 tokenId);
error ValidationFailed(string reason);
  1. Add Recovery Mechanisms

function recoverInvalidClaim(
address nftAddress,
address erc20Address
) external onlyOwner {
require(_isValidRecovery(nftAddress, erc20Address), "Invalid recovery");
_executeRecovery(nftAddress, erc20Address);
}

Updates

Lead Judging Commences

fishy Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.