Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: low
Invalid

Arbitrary ERC20 Withdrawal in withdrawInheritedFunds Function

Summary

The InheritanceManager::withdrawInheritedFunds function allows beneficiaries to withdraw any ERC20 token held by the contract, without restricting which token may be withdrawn. This design permits the dispersion of any ERC20 token—including those not intended by the contract owner—to beneficiaries. Although the function includes a check to ensure that inheritance has been triggered (isInherited), it lacks any additional validation on the _asset parameter. This means that if any arbitrary ERC20 token is sent to the contract, beneficiaries can withdraw it, which may lead to unintended or malicious token interactions.

Vulnerability Details

Code Snippet

/**
* @dev called by the beneficiaries to disperse remaining assets within the contract in equal parts.
* @notice use address(0) to disperse ether
* @param _asset asset address to disperse
*/
function withdrawInheritedFunds(address _asset) external {
if (!isInherited) {
revert NotYetInherited();
}
uint256 divisor = beneficiaries.length;
if (_asset == address(0)) {
uint256 ethAmountAvailable = address(this).balance;
uint256 amountPerBeneficiary = ethAmountAvailable / divisor;
for (uint256 i = 0; i < divisor; i++) {
address payable beneficiary = payable(beneficiaries[i]);
(bool success,) = beneficiary.call{value: amountPerBeneficiary}("");
require(success, "something went wrong");
}
} else {
uint256 assetAmountAvailable = IERC20(_asset).balanceOf(address(this));
uint256 amountPerBeneficiary = assetAmountAvailable / divisor;
for (uint256 i = 0; i < divisor; i++) {
// @audit-issue No restriction on which ERC20 token can be withdrawn; any ERC20 token held by the contract can be dispersed
@> IERC20(_asset).safeTransfer(beneficiaries[i], amountPerBeneficiary);
}
}
}

Issue Explanation

  • Lack of Token Restriction:
    The function does not limit the type of ERC20 token that can be withdrawn. Beneficiaries can call withdrawInheritedFunds with any ERC20 token address, meaning that if an unintended or malicious token is present in the contract, it will be automatically dispersed among the beneficiaries.

  • Impact on Fund Distribution:
    This unrestricted design could lead to scenarios where tokens not meant for inheritance (e.g., tokens sent by mistake or malicious tokens designed to exploit further logic in beneficiary contracts) are distributed, potentially causing confusion or loss of funds.

  • Potential for Malicious Exploitation:
    An attacker might deliberately send a malicious ERC20 token to the contract. Since the function will distribute any token passed as _asset, beneficiaries could end up receiving and interacting with harmful tokens without proper safeguards in place.

Impact

  • Incorrect Asset Distribution:
    Funds may be dispersed in an unintended manner if arbitrary tokens are withdrawn, which could lead to financial losses or mismanagement of assets.

  • Security Risk Through Malicious Tokens:
    Beneficiaries may inadvertently interact with malicious tokens if such tokens are withdrawn from the contract, potentially exposing them to further exploits.

  • Operational Complexity:
    Without restrictions, the contract's inheritance mechanism may handle tokens that the owner did not intend to include, leading to additional complexity in managing and accounting for assets.

Recommendations

Implement a Whitelist for Allowed Tokens:
Introduce a mechanism to restrict withdrawals to a predefined list of approved ERC20 tokens. This ensures that only tokens intended for inheritance are subject to withdrawal.

mapping(address => bool) public allowedTokens;
function setAllowedToken(address _token, bool _status) external onlyOwner {
allowedTokens[_token] = _status;
}
function withdrawInheritedFunds(address _asset) external {
if (!isInherited) {
revert NotYetInherited();
}
if (_asset != address(0)) {
require(allowedTokens[_asset], "Token not allowed");
}
uint256 divisor = beneficiaries.length;
if (_asset == address(0)) {
uint256 ethAmountAvailable = address(this).balance;
uint256 amountPerBeneficiary = ethAmountAvailable / divisor;
for (uint256 i = 0; i < divisor; i++) {
address payable beneficiary = payable(beneficiaries[i]);
(bool success,) = beneficiary.call{value: amountPerBeneficiary}("");
require(success, "Transfer failed");
}
} else {
uint256 assetAmountAvailable = IERC20(_asset).balanceOf(address(this));
uint256 amountPerBeneficiary = assetAmountAvailable / divisor;
for (uint256 i = 0; i < divisor; i++) {
IERC20(_asset).safeTransfer(beneficiaries[i], amountPerBeneficiary);
}
}
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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