You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Auth uses the msg.sig value to determine the function selector of the function being called and verifies it with the Authority contract, if set. However, msg.sig returns the function selector of the initial function call in the context of execution, and not necessarily the function selector of the function that calls isAuthorized.
There are two cases where this distinction may come up:
If public function with the isAuthorized modifier is called internally, the msg.sig it will check is the external function that called it. This is also the case for the for the setAuthority and transferOwnership functions.
If an internal function uses the isAuthorized modifier, it will also only verify the external function. This is not a common pattern but is sometimes used.
The cases where this can be an issue are fairly rare, but developers could make assumptions and incorrectly authorize important functions which could lead to quite severe implications.
It is suggested that a notice is added to notify developers if this behaviour, because it is not directly apparent. Additionally you could add another modifier with a function selector input parameter to allow users control over which selector is verified.
Here is a sample implementation and corresponding forge test:
// SPDX-License-Identifier: UNLICENSEDpragma solidity^0.8.13;
import {console} from"forge-std/console.sol";
import {Auth, Authority} from"lib/solmate/src/auth/Auth.sol";
contractAuthImplementationisAuth {
uint256public number;
bytes4 shouldNotBeCalledByAnyoneInternalSelector =bytes4(keccak256(bytes("shouldNotBeCalledByAnyoneInternal()")));
constructor(addressowner) Auth(owner, Authority(address(this))) {}
function shouldNotBeCalledByAnyoneInternal() requiresAuth internal {}
function shouldNotBeCalledByAnyonePublic() requiresAuth public {}
function externalCall() requiresAuth external {
// these should not be callable by anyone other than the owner, but because an external function initiated it, the msg.sig == externalCall.selector // rather than the expected shouldNotBeCalledByAnyoneInternalSelector or shouldNotBeCalledByAnyonePublic.selectorshouldNotBeCalledByAnyoneInternal();
shouldNotBeCalledByAnyonePublic();
}
function externalTransferOwnership(addressnewOwner) requiresAuth external {
transferOwnership(newOwner);
require(msg.sender== owner);
}
// contract is it's own authorityfunction canCall(
addressuser,
addresstarget,
bytes4functionSig
) externalviewreturns (bool) {
// anyone can call if(functionSig ==this.externalTransferOwnership.selector) {
returntrue;
}
// non owner cannot callif(functionSig == shouldNotBeCalledByAnyoneInternalSelector) {
// note this will never be reached, since it always uses the external function selectorreturnfalse;
}
if(functionSig ==this.shouldNotBeCalledByAnyonePublic.selector) {
returnfalse;
}
// non owner cannot transfer ownershipif(functionSig ==this.transferOwnership.selector) {
returnfalse;
}
returntrue;
}
}
// SPDX-License-Identifier: UNLICENSEDpragma solidity^0.8.13;
import {Test, console} from"forge-std/Test.sol";
import {AuthImplementation, Authority} from"../src/AuthImplementation.sol";
contractAuthTestisTest {
AuthImplementation public authImplementation;
address bob = vm.addr(0xDAD);
address alice = vm.addr(0xFAE);
address attacker = vm.addr(0xDEAD);
function setUp() public {
authImplementation =newAuthImplementation(bob);
}
function test_nonOwnerCanTransferOwnership() public {
// transfers ownership normally
vm.prank(bob);
authImplementation.transferOwnership(alice);
assertEq(authImplementation.owner(), alice);
// bob can no longer transfer ownership
vm.expectRevert();
vm.prank(bob);
authImplementation.transferOwnership(bob);
// the attacker cannot transfer ownership normally
vm.expectRevert();
vm.prank(attacker);
authImplementation.transferOwnership(attacker);
// they can however do it through the external function that anyone can call, which calls transferOwnership internally
vm.prank(attacker);
authImplementation.externalTransferOwnership(attacker);
assertEq(authImplementation.owner(), attacker);
}
function test_canCallAuthorizedFunctionsInternallyWithoutAuthorization() public {
// owner can call all isauthorized functions
vm.prank(bob);
authImplementation.shouldNotBeCalledByAnyonePublic();
// attacker cannot directly call
vm.expectRevert();
vm.prank(attacker);
authImplementation.shouldNotBeCalledByAnyonePublic();
// they can however do it through the external function that anyone can call
vm.prank(attacker);
authImplementation.externalCall();
}
}
The text was updated successfully, but these errors were encountered:
Auth uses the msg.sig value to determine the function selector of the function being called and verifies it with the Authority contract, if set. However, msg.sig returns the function selector of the initial function call in the context of execution, and not necessarily the function selector of the function that calls isAuthorized.
There are two cases where this distinction may come up:
The cases where this can be an issue are fairly rare, but developers could make assumptions and incorrectly authorize important functions which could lead to quite severe implications.
It is suggested that a notice is added to notify developers if this behaviour, because it is not directly apparent. Additionally you could add another modifier with a function selector input parameter to allow users control over which selector is verified.
Here is a sample implementation and corresponding forge test:
The text was updated successfully, but these errors were encountered: