// SPDX-License-Identifier: MIT pragma solidity 0.8.28; /// The only thing this needs from a Kernel account. interface IKernelRootValidator { function rootValidator() external view returns (bytes21); } /// Is this module still the ROOT validator of the account asking? /// /// Two holes in Kernel need the same answer, and neither is reachable from /// calldata inspection alone. /// /// 1. DE-REGISTERING A ROOT FROM UNDER ITS OWN ACCOUNT. `uninstallModule(1, /// root, …)` is guarded — `_uninstallValidation` reverts /// `RootValidatorCannotBeRemoved` — but every root validator here also /// reports `isModuleType(4) == true` so the owner keeps a direct-call exit, /// and Kernel's TYPE-4 branch has no such guard: it force-calls /// `module.onUninstall(...)` and nothing looks at `rootValidator`. That /// clears the owner slot, which is the only thing standing between /// `onInstall` and being re-run with somebody else's address in it — the /// re-install guard is `AlreadyInitialized`, and `AlreadyInitialized` is a /// non-zero slot. Two calls, no signature, root owner replaced. /// /// So `onUninstall` asks this before it deletes anything. Kernel routes the /// type-4 uninstall through `ModuleLib.uninstallModule`, which uses /// `ExcessivelySafeCall` and DISCARDS the result — so the revert does not /// surface, and does not need to. Refusing to clear the slot is the whole /// defence; the attacker's second call then hits `AlreadyInitialized`. /// /// 2. A VALIDATOR THAT USED TO BE ROOT STILL ANSWERS ERC-1271. /// `changeRootValidator` re-points `rootValidator` and never clears /// `validationConfig[oldRoot]`, and `Kernel.isValidSignature` accepts any /// `vId` whose config has a non-zero hook. So every validator that was ever /// root would keep signing for the account forever, invisible to /// `rootValidator()`, to `ownerOf` and to `RootOwner` — which is the /// EIP-3009 drain with an extra step. Asking here makes the answer follow /// the account's current root rather than its history. /// /// Read, never written: one STATICCALL, so it cannot re-enter. library RootBinding { /// Kernel encodes a validator-type ValidationId as `0x01 ‖ address` /// (VALIDATION_TYPE_VALIDATOR). `ValidatorLib` builds it with /// `shl(88, validator)`, so the type is the high byte. bytes1 internal constant VALIDATION_TYPE_VALIDATOR = 0x01; error StillRootValidator(address account); /// True only when `account` is a contract that answers `rootValidator()` /// with `0x01 ‖ self`. /// /// FAILS CLOSED, and the direction matters per caller. A codeless or /// non-Kernel `account` returns false, which means (1) permits the /// uninstall — there is no account whose root this could be — and (2) /// refuses the signature. Both are the safe answer for their side. function isRootOf(address account, address self) internal view returns (bool) { (bool ok, bytes memory ret) = account.staticcall(abi.encodeCall(IKernelRootValidator.rootValidator, ())); if (!ok || ret.length != 32) return false; bytes32 word; assembly { word := mload(add(ret, 32)) } return bytes21(word) == bytes21(abi.encodePacked(VALIDATION_TYPE_VALIDATOR, self)); } /// For `onUninstall`: a root can never be de-registered out from under its /// own account. function requireNotRootOf(address account, address self) internal view { if (isRootOf(account, self)) revert StillRootValidator(account); } }