Skip to content

Commit 6c8aaca

Browse files
committed
ERC7579: prevent installing/uninstalling without proper initData
1 parent 765b288 commit 6c8aaca

File tree

3 files changed

+32
-2
lines changed

3 files changed

+32
-2
lines changed

certora/specs/Account.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ invariant absentExecutorIsNotStored(address module, uint256 index)
205205
*/
206206
// This guarantees that at most one fallback module is active for a given initData (i.e. selector)
207207
rule fallbackModule(address module, bytes initData) {
208-
assert isModuleInstalled(FALLBACK_TYPE(), module, initData) <=> getFallbackHandler(getDataSelector(initData)) == module;
208+
assert isModuleInstalled(FALLBACK_TYPE(), module, initData) <=> (initData.length >= 4 && getFallbackHandler(getDataSelector(initData)) == module);
209209
}
210210

211211
rule moduleManagementRule(env e, method f, calldataarg args, uint256 moduleTypeId, address module, bytes additionalContext)

contracts/account/extensions/draft-AccountERC7579.sol

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
6868
/// @dev The account's {fallback} was called with a selector that doesn't have an installed handler.
6969
error ERC7579MissingFallbackHandler(bytes4 selector);
7070

71+
/// @dev The provided initData/deInitData for a fallback module is too short to extract a selector.
72+
error ERC7579CannotDecodeFallbackData();
73+
74+
/// @dev The provided signature is not long enough to be parsed as a module signature.
75+
error ERC7579InvalidModuleSignature();
76+
7177
/// @dev Modifier that checks if the caller is an installed module of the given type.
7278
modifier onlyModule(uint256 moduleTypeId, bytes calldata additionalContext) {
7379
_checkModule(moduleTypeId, msg.sender, additionalContext);
@@ -384,7 +390,8 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
384390
function _extractSignatureValidator(
385391
bytes calldata signature
386392
) internal pure virtual returns (address module, bytes calldata innerSignature) {
387-
return (address(bytes20(signature[0:20])), signature[20:]);
393+
require(signature.length > 19, ERC7579InvalidModuleSignature());
394+
return (address(bytes20(signature)), signature[20:]);
388395
}
389396

390397
/**
@@ -399,6 +406,7 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
399406
function _decodeFallbackData(
400407
bytes memory data
401408
) internal pure virtual returns (bytes4 selector, bytes memory remaining) {
409+
require(data.length > 3, ERC7579CannotDecodeFallbackData());
402410
return (bytes4(data), data.slice(4));
403411
}
404412

test/account/extensions/AccountERC7579.behavior.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,17 @@ function shouldBehaveLikeAccountERC7579({ withHooks = false } = {}) {
167167
});
168168
}
169169

170+
it('should revert when installing a fallback module with an initData that is not long enough to encode a function selector', async function () {
171+
const instance = this.modules[MODULE_TYPE_FALLBACK];
172+
await expect(
173+
this.mockFromEntrypoint.installModule(MODULE_TYPE_FALLBACK, instance, '0x'),
174+
).to.be.revertedWithCustomError(this.mock, 'ERC7579CannotDecodeFallbackData');
175+
176+
await expect(
177+
this.mockFromEntrypoint.installModule(MODULE_TYPE_FALLBACK, instance, '0x123456'),
178+
).to.be.revertedWithCustomError(this.mock, 'ERC7579CannotDecodeFallbackData');
179+
});
180+
170181
withHooks &&
171182
describe('with hook', function () {
172183
beforeEach(async function () {
@@ -240,6 +251,17 @@ function shouldBehaveLikeAccountERC7579({ withHooks = false } = {}) {
240251
});
241252
}
242253

254+
it('should revert when uninstalling a fallback module with an initData that is not long enough to encode a function selector', async function () {
255+
const instance = this.modules[MODULE_TYPE_FALLBACK];
256+
await expect(
257+
this.mockFromEntrypoint.uninstallModule(MODULE_TYPE_FALLBACK, instance, '0x'),
258+
).to.be.revertedWithCustomError(this.mock, 'ERC7579CannotDecodeFallbackData');
259+
260+
await expect(
261+
this.mockFromEntrypoint.uninstallModule(MODULE_TYPE_FALLBACK, instance, '0x123456'),
262+
).to.be.revertedWithCustomError(this.mock, 'ERC7579CannotDecodeFallbackData');
263+
});
264+
243265
it('should revert uninstalling a module of type MODULE_TYPE_FALLBACK if a different module was installed for the provided selector', async function () {
244266
const instance = this.modules[MODULE_TYPE_FALLBACK];
245267
const anotherInstance = await ethers.deployContract('$ERC7579ModuleMock', [MODULE_TYPE_FALLBACK]);

0 commit comments

Comments
 (0)