Issue H-1: Detaching HSG when there's non-unregistered owners who no longer own the hat would give them control over the multi-sig
Source: #4
The protocol has acknowledged this issue.
bughuntoor
After a user no longer owns a hat, although they are no longer treated as a valid signer, they remain an owner
within the Safe, until removeSigner
is called
function detachHSG() public {
_checkUnlocked();
_checkOwner();
ISafe s = safe; // save SLOAD
// first remove as guard, then as module
s.execRemoveHSGAsGuard();
s.execDisableHSGAsOnlyModule();
emit Detached();
}
When HSG is detached, it does not check if there's any Safe owners who no longer wear the necessary hat. For this reason, if there's such owners, they'll remain rights within the multi-sig. Depending on their number, they might be able to overturn the multisig, or at least disallow them to reach quorum.
However, this attack can further be weaponized if one of the signer hats has admin rights over another signer hat. If the said admin hat wants to overturn the multisig and gain full access of it upon detaching, they can simply front-run the detachHSG
call and do a loop of 1) transferring the lower hat to a new address 2) claiming it as a signer. Then, when the detachHSG
executes, all of these addresses that the attacker had looped through would be owners of the safe and in most cases that should be enough to fully overturn the multi-sig and claim full custody of it.
detachHSG
does not check if there are Safe owners who no longer wear the necessary hat.
- DAO plans to detach from HSG
- There exists a user who has admin hat over a signer hat which has a set max supply of 1.
- DAO calls detach from HSG
- The admin hat owner front-runs the tx and does a loop of transferring the hat and adding it as a signer. This gives a lot of wallets
owner
rights within the Safe, which would otherwise be worthless if HSG remains active - The DAO gets detached and all of the wallets the admin hat owner had looped through now are owners within the Safe
- This would usually give full custody to the attacker, or at the very least guarantee the DAO is not able to execute anything on their own.
Attacker can gain full custody over a Safe upon HSG detachment
Upon detaching HSG, loop through all Safe owners and in case a wallet does not wear the necessary hat, unregister them as a signer.
Source: #3
bughuntoor
In order to make sure that a delegatecall does not change Safe's state, HSG's checkTransaction
stores the current threshold, owners list and fallback handler. Then, after the call is executed, checkAfterExecution
is supposed to verify that these variables have not been changed.
if (operation == Enum.Operation.DelegateCall) {
// case: DELEGATECALL
// We disallow delegatecalls to unapproved targets
if (!enabledDelegatecallTargets[to]) revert DelegatecallTargetNotEnabled();
// Otherwise record the existing owners and threshold for post-flight checks to ensure that Safe state has not
// been altered
_existingOwnersHash = keccak256(abi.encode(owners));
_existingThreshold = threshold;
_existingFallbackHandler = safe.getSafeFallbackHandler();
However, since the checkTransaction
can be re-entered by a new call, these restrictions can easily be bypassed. If the delegatecall changes the owners and the threshold, the executing signer can then just provide a new transaction to be executed with the new owners being just him and threshold set to 1. This will then override the above stored variables. Because of this the checkAfterExecution
check will also succeed.
Possible reentrancy within checkTransaction
- Signers sign a tx which would alter the owners list and set the threshold to 1.
- Within that
delegatecall
, the only remaining owner signs a new transaction and executes it. It doesn't realistically mater what the tx is. checkTransaction
is entered._existingOwnersHash
and_existingThreshold
are overwritten to their new values.- The
checkAfterExecution
on both the inner and the outer call check against the altered values, hence they both succeed. - In the end, the intended restrictions are bypassed and the ownerlist and threshold are both overwritten.
- The user who has remained the only owner has full access over the multi-sig until the other owners re-claim their hats.
Users can bypassed intended restrictions not to be able to overwrite the owner list and threshold variables.
If checkTransaction
is entered, and the transient variables have already been assigned values, revert if the values differ from the current ones.