Wobbly Chrome Cuckoo
Medium
In rare cases claimSignersFor()
can inappropriately set the Safe threshold and cause DoS of Safe transactions
claimSignersFor()
lacks a success check for adding a new owner to the safe, while assuming that all addOwnerWithThreshold()
calls succeeded for the purposes of updating the Safe threshold. Therefore, if adding an owner fails silently (possible in rare cases), then the threshold will be set incorrectly and all Safe transactions will revert due to the threshold check in checkTransaction()
. This will DoS Safe transactions until the threshold is updated to the correct value. The DoS could last for more than one week if the HSG owner is a timelock contract, which is common with governance protocols.
The root cause is that the SafeManagerLib.sol
doesn't check return bools. (Note that the Safe design allows the execute()
call in execTransactionFromModule()
to fail silently.) Therefore, claimSignersFor()
assumes that all of the owners specified were added successfully to the safe by agnostically incrementing the local newNumOwners
variable. Furthermore, it also assumes that all threshold updates succeed.
claimSignersFor()
calls execSafeTransactionFromHSG()
to add owners, and this function doesn't check the return bool of the execTransactionFromModule()
function it calls. The same issue occurs with execChangeThreshold()
a few lines below the execSafeTransactionFromHSG()
call.
These silent failures can result in an improper threshold set in the Safe.
One example of a situation in which one of the calls to add a new owner in claimSignersFor()
will silently fail:
- An address which is disallowed as a Safe owner is given a signer hat.
claimSignersFor()
is called to add multiple signers, including the disallowed signer.- Adding the disallowed signer to the Safe will silently fail.
No response
One scenario where the threshold is improperly increased:
- Silent failure occurs as described when adding owners to the Safe via
claimSignersFor()
, and sincenewNumOwners
is incremented more than it should be, the call to_getNewThreshold()
inclaimSignersFor()
returns a value that's too high. claimSignersFor()
changes the Safe threshold. One of two possibilities occurs, but either way the resulting Safe threshold is incorrect:claimSignersFor()
attempts to change the threshold to improperly high value which is disallowed by the Safe, and this fails silently similarly to how adding an already existing Safe owner fails silently.claimSignersFor()
successfully changes the threshold to a value that's higher than intended, but is still allowed by the Safe.
- Signers sign a transaction and execute it on the Safe. Due to the transaction check in HSG that requires the threshold on the Safe to be identical to the intended threshold, the transaction will fail.
- Suppose the HSG owner is a timelock contract (very common with governance). The Safe functionality will be DoSed for the duration of the timelock, until the timelock contract can call other functions such as
setThresholdConfig()
as a workaround to fix the threshold. Note that timelock periods are often longer than 1 week.
DoS of Safe functionality for longer than one week.
No response
Check return bools to make sure that Safe module transactions were executed successfully, and take appropriate actions if they weren't.