Skip to content

Commit 45558b8

Browse files
Amxxernestognw
andauthored
ERC7579: prevent installing/uninstalling without proper initData (#5974)
Co-authored-by: ernestognw <[email protected]>
1 parent f7da70c commit 45558b8

File tree

4 files changed

+31
-2
lines changed

4 files changed

+31
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- `SignerERC7702` is renamed as `SignerEIP7702`. Imports and inheritance must be updated to that new name and path. Behavior is unmodified.
1212
- `ERC721Holder`, `ERC1155Holder`, `ReentrancyGuard` and `ReentrancyGuardTransient` are flagged as stateless and are no longer transpiled. Developers using their upgradeable variants from `@openzeppelin/contracts-upgradeable` must update their imports to use the equivalent version available in `@openzeppelin/contracts`.
1313
- Update minimum pragma to 0.8.24 in `Votes`, `VotesExtended`, `ERC20Votes`, `Strings`, `ERC1155URIStorage`, `MessageHashUtils`, `ERC721URIStorage`, `ERC721Votes`, `ERC721Wrapper`, `ERC721Burnable`, `ERC721Consecutive`, `ERC721Enumerable`, `ERC721Pausable`, `ERC721Royalty`, `ERC721Wrapper`, `EIP712`, `ERC4626` and `ERC7739`. ([#5726](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5726))
14+
- `AccountERC7579`: Installing and uninstalling fallback modules now require the corresponding `initData` and `deInitData` arguments to be at least 4 bytes long (matching the selector to which the fallback module is registered). It now reverts with `ERC7579CannotDecodeFallbackData` instead of treating the missing bytes as `0x00`.
1415

1516
### Deprecation
1617

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: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ 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+
7174
/// @dev Modifier that checks if the caller is an installed module of the given type.
7275
modifier onlyModule(uint256 moduleTypeId, bytes calldata additionalContext) {
7376
_checkModule(moduleTypeId, msg.sender, additionalContext);
@@ -380,11 +383,13 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
380383
* https://github.com/erc7579/erc7579-implementation/blob/16138d1afd4e9711f6c1425133538837bd7787b5/src/MSAAdvanced.sol#L296[ERC7579 reference implementation].
381384
*
382385
* This is not standardized in ERC-7579 (or in any follow-up ERC). Some accounts may want to override these internal functions.
386+
*
387+
* NOTE: This function expects the signature to be at least 20 bytes long. Panics with {Panic-ARRAY_OUT_OF_BOUNDS} (0x32) otherwise.
383388
*/
384389
function _extractSignatureValidator(
385390
bytes calldata signature
386391
) internal pure virtual returns (address module, bytes calldata innerSignature) {
387-
return (address(bytes20(signature[0:20])), signature[20:]);
392+
return (address(bytes20(signature)), signature[20:]);
388393
}
389394

390395
/**
@@ -399,6 +404,7 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
399404
function _decodeFallbackData(
400405
bytes memory data
401406
) internal pure virtual returns (bytes4 selector, bytes memory remaining) {
407+
require(data.length > 3, ERC7579CannotDecodeFallbackData());
402408
return (bytes4(data), data.slice(4));
403409
}
404410

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)