Summary
The internal function ProxyFactory::_distribute
, which is publically called by 5 functions, has as a parameter bytes calldata data
, which the protocol assumes will be called with the function selector of the Distributor::distribute
function.
Vulnerability Details
Whoever, a call to for example Distributor::getConstants
will pass.
function test_distributePhantomFunctionCheck() setUpContestForNameAndSentAmountToken("James", jpycv2Address, 10000 ether) public {
bytes32 randomId_ = keccak256(abi.encode("James", "001"));
bytes memory data = abi.encodeWithSelector(Distributor.getConstants.selector);
vm.startPrank(organizer);
deployedProxy = proxyFactory.deployProxyAndDistribute(randomId_, address(distributor), data);
vm.stopPrank();
}
Impact
A call to for example Distributor::getConstants
will pass, emitting the ProxyFactory::Distributed
event, which can cause problems in the indexing and/or logs tracing understanding of how the contract is behaving.
Tools Used
Manual review and Foundry.
Recommendations
Add the following require
function _distribute(address proxy, bytes calldata data) internal {
(bool success,) = proxy.call(data);
if (!success) revert ProxyFactory__DelegateCallFailed();
+ require(bytes4(data[:4]) == 0x3cc83ebe, "Wrong function selector");
emit Distributed(proxy, data);
}
POC of Recommendation:
function test_distributePhantomFunctionCheck() setUpContestForNameAndSentAmountToken("James", jpycv2Address, 10000 ether) public {
bytes32 randomId_ = keccak256(abi.encode("James", "001"));
address[] memory winners = new address[](1);
winners[0] = user1;
uint256[] memory percentages_ = new uint256[](1);
percentages_[0] = 9500;
bytes memory data = abi.encodeWithSelector(Distributor.distribute.selector, jpycv2Address, winners, percentages_, "");
vm.startPrank(organizer);
deployedProxy = proxyFactory.deployProxyAndDistribute(randomId_, address(distributor), data);
vm.stopPrank();
}
No other call which is not directed to Distributor::distribute
is possible to the contract, all others will revert.