Skip to content
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

Add audit fixes #41

Merged
merged 15 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"no-empty-blocks": "off",
"not-rely-on-time": "off",
"one-contract-per-file": "off",
"var-name-mixedcase": "off"
"var-name-mixedcase": "off",
"immutable-vars-naming": "off"
}
}
22 changes: 16 additions & 6 deletions src/EmailRecoveryManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -441,17 +441,27 @@ abstract contract EmailRecoveryManager is
}

/**
* @notice Removes all state related to an account.
* @notice Removes all state related to msg.sender.
* @dev In order to prevent unexpected behaviour when reinstalling account modules, the module
* should be deinitialized. This should include remove state accociated with an account.
*/
function deInitRecoveryModule() internal onlyWhenNotRecovering {
delete recoveryConfigs[msg.sender];
delete recoveryRequests[msg.sender];
address account = msg.sender;
deInitRecoveryModule(account);
}

/**
* @notice Removes all state related to an account.
* @dev In order to prevent unexpected behaviour when reinstalling account modules, the module
* should be deinitialized. This should include remove state accociated with an account.
*/
function deInitRecoveryModule(address account) internal onlyWhenNotRecovering {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For completeness even though risk is low, can you add unit tests for this new function?

You can just replicate EmailRecoveryManager_deInitRecoveryModule_Test, but for the new internal function with the account argument. You could even add tests in the same file.

Would need to update the harness to expose the internal function

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I've just added deInitRecoveryModuleWithAddress.t.sol.

delete recoveryConfigs[account];
delete recoveryRequests[account];

removeAllGuardians(msg.sender);
delete guardianConfigs[msg.sender];
removeAllGuardians(account);
delete guardianConfigs[account];

emit RecoveryDeInitialized(msg.sender);
emit RecoveryDeInitialized(account);
}
}
2 changes: 1 addition & 1 deletion src/handlers/EmailRecoverySubjectHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler {
bytes[] calldata subjectParams
)
public
view
pure
returns (address)
{
if (templateIdx != 0) {
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IEmailRecoveryManager.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import { GuardianStorage, GuardianStatus } from "../libraries/EnumerableGuardianMap.sol";
import { GuardianStatus } from "../libraries/EnumerableGuardianMap.sol";

interface IEmailRecoveryManager {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
Expand Down
6 changes: 5 additions & 1 deletion src/interfaces/ISafe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ interface ISafe {
)
external
returns (bool success);
function isModuleEnabled(address module) external view returns (bool);
function isModuleEnabled(address module) external view returns (bool);
function addOwnerWithThreshold(address owner, uint256 _threshold) external;
function removeOwner(address prevOwner, address owner, uint256 _threshold) external;
function swapOwner(address prevOwner, address oldOwner, address newOwner) external;
function changeThreshold(uint256 _threshold) external;
}
30 changes: 17 additions & 13 deletions src/modules/EmailRecoveryModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { IModule } from "erc7579/interfaces/IERC7579Module.sol";
import { ISafe } from "../interfaces/ISafe.sol";
import { IEmailRecoveryModule } from "../interfaces/IEmailRecoveryModule.sol";
import { EmailRecoveryManager } from "../EmailRecoveryManager.sol";
import { GuardianManager } from "../GuardianManager.sol";

/**
* @title EmailRecoveryModule
Expand Down Expand Up @@ -53,13 +52,21 @@ contract EmailRecoveryModule is EmailRecoveryManager, ERC7579ExecutorBase, IEmai
if (_validator == address(0)) {
revert InvalidValidator(_validator);
}
if (
_selector == IModule.onUninstall.selector || _selector == IModule.onInstall.selector
|| _selector == IERC7579Account.execute.selector
|| _selector == ISafe.setFallbackHandler.selector
|| _selector == ISafe.setGuard.selector || _selector == bytes4(0)
) {
revert InvalidSelector(_selector);
if(_validator == msg.sender) {
if (
_selector != ISafe.addOwnerWithThreshold.selector && _selector != ISafe.removeOwner.selector
&& _selector != ISafe.swapOwner.selector
&& _selector != ISafe.changeThreshold.selector
) {
revert InvalidSelector(_selector);
}
} else {
if (
_selector == IModule.onInstall.selector || _selector == IModule.onUninstall.selector
|| _selector == bytes4(0)
) {
revert InvalidSelector(_selector);
}
}

validator = _validator;
Expand Down Expand Up @@ -140,14 +147,11 @@ contract EmailRecoveryModule is EmailRecoveryManager, ERC7579ExecutorBase, IEmai
* being recovered. recoveryData = abi.encode(validator, recoveryFunctionCalldata)
*/
function recover(address account, bytes calldata recoveryData) internal override {
(address validator, bytes memory recoveryCalldata) =
(, bytes memory recoveryCalldata) =
abi.decode(recoveryData, (address, bytes));

if (validator == address(0)) {
revert InvalidValidator(validator);
}

bytes4 calldataSelector;
// solhint-disable-next-line no-inline-assembly
assembly {
calldataSelector := mload(add(recoveryCalldata, 32))
}
Expand Down
3 changes: 2 additions & 1 deletion src/modules/SafeEmailRecoveryModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ contract SafeEmailRecoveryModule is EmailRecoveryManager {
}

bytes4 calldataSelector;
// solhint-disable-next-line no-inline-assembly
assembly {
calldataSelector := mload(add(recoveryCalldata, 32))
}
Expand Down Expand Up @@ -92,6 +93,6 @@ contract SafeEmailRecoveryModule is EmailRecoveryManager {
if (ISafe(account).isModuleEnabled(address(this)) == true) {
revert ResetFailed(account);
}
deInitRecoveryModule();
deInitRecoveryModule(account);
}
}
27 changes: 18 additions & 9 deletions src/modules/UniversalEmailRecoveryModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ERC7579ExecutorBase } from "@rhinestone/modulekit/src/Modules.sol";
import { IERC7579Account } from "erc7579/interfaces/IERC7579Account.sol";
import { IModule } from "erc7579/interfaces/IERC7579Module.sol";
import { ISafe } from "../interfaces/ISafe.sol";
import { SentinelListLib, SENTINEL, ZERO_ADDRESS } from "sentinellist/SentinelList.sol";
import { SentinelListLib, SENTINEL } from "sentinellist/SentinelList.sol";
import { IUniversalEmailRecoveryModule } from "../interfaces/IUniversalEmailRecoveryModule.sol";
import { EmailRecoveryManager } from "../EmailRecoveryManager.sol";

Expand Down Expand Up @@ -68,14 +68,22 @@ contract UniversalEmailRecoveryModule is
* @notice Modifier to check whether the selector is safe. Reverts if the selector is for
* "onInstall" or "onUninstall"
*/
modifier withoutUnsafeSelector(bytes4 selector) {
if (
selector == IModule.onUninstall.selector || selector == IModule.onInstall.selector
|| selector == IERC7579Account.execute.selector
|| selector == ISafe.setFallbackHandler.selector || selector == ISafe.setGuard.selector
modifier withoutUnsafeSelector(address validator, bytes4 selector) {
if(validator == msg.sender) {
if (
selector != ISafe.addOwnerWithThreshold.selector && selector != ISafe.removeOwner.selector
&& selector != ISafe.swapOwner.selector
&& selector != ISafe.changeThreshold.selector
) {
revert InvalidSelector(selector);
}
} else {
if (
selector == IModule.onInstall.selector || selector == IModule.onUninstall.selector
|| selector == bytes4(0)
) {
revert InvalidSelector(selector);
) {
revert InvalidSelector(selector);
}
}
_;
}
Expand Down Expand Up @@ -149,7 +157,7 @@ contract UniversalEmailRecoveryModule is
)
public
onlyWhenInitialized
withoutUnsafeSelector(recoverySelector)
withoutUnsafeSelector(validator, recoverySelector)
{
if (
!IERC7579Account(msg.sender).isModuleInstalled(
Expand Down Expand Up @@ -270,6 +278,7 @@ contract UniversalEmailRecoveryModule is
}

bytes4 selector;
// solhint-disable-next-line no-inline-assembly
assembly {
selector := mload(add(recoveryCalldata, 32))
}
Expand Down
2 changes: 1 addition & 1 deletion test/unit/UnitBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol";
import { OwnableValidator } from "src/test/OwnableValidator.sol";
import { MockGroth16Verifier } from "src/test/MockGroth16Verifier.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

abstract contract UnitBase is RhinestoneModuleKit, Test {
using ModuleKitHelpers for *;
Expand Down
69 changes: 48 additions & 21 deletions test/unit/modules/EmailRecoveryModule/constructor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IERC7579Account } from "erc7579/interfaces/IERC7579Account.sol";
import { ISafe } from "src/interfaces/ISafe.sol";
import { EmailRecoveryModuleBase } from "./EmailRecoveryModuleBase.t.sol";
import { EmailRecoveryModule } from "src/modules/EmailRecoveryModule.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";

contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase {
function setUp() public override {
Expand All @@ -28,42 +29,59 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase {
);
}

function test_Constructor_RevertWhen_UnsafeOnInstallSelector() public {
vm.expectRevert(
abi.encodeWithSelector(
EmailRecoveryModule.InvalidSelector.selector, IModule.onInstall.selector
)
function test_Constructor_When_SafeAddOwnerSelector() public {
_skipIfNotSafeAccountType();
new EmailRecoveryModule(
address(verifier),
address(dkimRegistry),
address(emailAuthImpl),
address(emailRecoveryHandler),
validatorAddress,
ISafe.addOwnerWithThreshold.selector
);
}

function test_Constructor_When_SafeRemoveOwnerSelector() public {
_skipIfNotSafeAccountType();
new EmailRecoveryModule(
address(verifier),
address(dkimRegistry),
address(emailAuthImpl),
address(emailRecoveryHandler),
validatorAddress,
IModule.onInstall.selector
ISafe.removeOwner.selector
);
}

function test_Constructor_RevertWhen_UnsafeOnUninstallSelector() public {
vm.expectRevert(
abi.encodeWithSelector(
EmailRecoveryModule.InvalidSelector.selector, IModule.onUninstall.selector
)
function test_Constructor_When_SafeSwapOwnerSelector() public {
_skipIfNotSafeAccountType();
new EmailRecoveryModule(
address(verifier),
address(dkimRegistry),
address(emailAuthImpl),
address(emailRecoveryHandler),
validatorAddress,
ISafe.swapOwner.selector
);
}

function test_Constructor_When_SafeChangeThresholdSelector() public {
_skipIfNotSafeAccountType();
new EmailRecoveryModule(
address(verifier),
address(dkimRegistry),
address(emailAuthImpl),
address(emailRecoveryHandler),
validatorAddress,
IModule.onUninstall.selector
ISafe.changeThreshold.selector
);
}

function test_Constructor_RevertWhen_UnsafeExecuteSelector() public {
function test_Constructor_RevertWhen_SafeNotAllowedSelector() public {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick:

Don't think this minor duplication matters, but fyi test_Constructor_RevertWhen_SafeNotAllowedSelector is just replicating the same logic as test_Constructor_RevertWhen_UnsafeOnInstallSelector, as the second function still gets called when the account type is Safe. Same for test_AllowValidatorRecovery_RevertWhen_SafeNotAllowedSelector in allowValidatorRecovery.t.sol

Happy to leave as is

_skipIfNotSafeAccountType();
vm.expectRevert(
abi.encodeWithSelector(
EmailRecoveryModule.InvalidSelector.selector, IERC7579Account.execute.selector
EmailRecoveryModule.InvalidSelector.selector, IModule.onInstall.selector
)
);
new EmailRecoveryModule(
Expand All @@ -72,14 +90,14 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase {
address(emailAuthImpl),
address(emailRecoveryHandler),
validatorAddress,
IERC7579Account.execute.selector
IModule.onInstall.selector
);
}

function test_Constructor_RevertWhen_UnsafeSetFallbackHandlerSelector() public {
function test_Constructor_RevertWhen_UnsafeOnInstallSelector() public {
vm.expectRevert(
abi.encodeWithSelector(
EmailRecoveryModule.InvalidSelector.selector, ISafe.setFallbackHandler.selector
EmailRecoveryModule.InvalidSelector.selector, IModule.onInstall.selector
)
);
new EmailRecoveryModule(
Expand All @@ -88,14 +106,14 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase {
address(emailAuthImpl),
address(emailRecoveryHandler),
validatorAddress,
ISafe.setFallbackHandler.selector
IModule.onInstall.selector
);
}

function test_Constructor_RevertWhen_UnsafeSetGuardSelector() public {
function test_Constructor_RevertWhen_UnsafeOnUninstallSelector() public {
vm.expectRevert(
abi.encodeWithSelector(
EmailRecoveryModule.InvalidSelector.selector, ISafe.setGuard.selector
EmailRecoveryModule.InvalidSelector.selector, IModule.onUninstall.selector
)
);
new EmailRecoveryModule(
Expand All @@ -104,7 +122,7 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase {
address(emailAuthImpl),
address(emailRecoveryHandler),
validatorAddress,
ISafe.setGuard.selector
IModule.onUninstall.selector
);
}

Expand Down Expand Up @@ -135,4 +153,13 @@ contract EmailRecoveryModule_constructor_Test is EmailRecoveryModuleBase {
assertEq(validatorAddress, emailRecoveryModule.validator());
assertEq(functionSelector, emailRecoveryModule.selector());
}

function _skipIfNotSafeAccountType() private {
string memory currentAccountType = vm.envOr("ACCOUNT_TYPE", string(""));
if (Strings.equal(currentAccountType, "SAFE")) {
vm.skip(false);
} else {
vm.skip(true);
}
}
}
Loading
Loading