Open hats-bug-reporter[bot] opened 1 week ago
In test_test
, I want to describe a scenario where the user disables the module on his safe, so calls to removeWholeTree
and disconnectSafe
will fail.
Better test case:
function test_test() public {
(uint256 rootId, uint256 safeA1Id) =
palmeraSafeBuilder.setupRootOrgAndOneSafe(orgName, safeA1Name);
address rootAddr = palmeraModule.getSafeAddress(rootId);
address safeAddress = palmeraModule.getSafeAddress(safeA1Id);
vm.startPrank(rootAddr);
palmeraModule.setRole(
DataTypes.Role.SAFE_LEAD_EXEC_ON_BEHALF_ONLY,
rootAddr,
safeA1Id,
true
);
/// Disable
address prevModule = palmeraModule.getPreviewModule(safeAddress);
bytes memory data = abi.encodeCall(
ISafe.disableModule, (prevModule, address(palmeraModule))
);
bool result = palmeraModule.execTransactionOnBehalf(
orgHash,
rootAddr,
safeAddress,
safeAddress,
uint256(0),
data,
Enum.Operation.Call,
"0x"
);
// try to disconnect
vm.expectRevert();
palmeraModule.disconnectSafe(safeA1Id);
}
Non-Issue @0xmahdirostami , we have a Unit-test for that @0xmahdirostami
avoid replay attack to removeSafe: https://github.com/hats-finance/Palmera-0x5fee7541ddcd51ba9f4af606f87b2c42eea655be/blob/1ac35880b5d45154267788e2db548eaaae0beaa0/test/PalmeraGuardTest.t.sol#L50
avoid replay attack to disconnectSafe: https://github.com/hats-finance/Palmera-0x5fee7541ddcd51ba9f4af606f87b2c42eea655be/blob/1ac35880b5d45154267788e2db548eaaae0beaa0/test/PalmeraGuardTest.t.sol#L50
in the end _exitSafe is a private function called by removeWholeTree
and disconnectSafe
but verify the status fo the safe before to call _exitSafe
additonally, the Palmera Guard avoid to the Safe disconnectSafe if not have the permissions to execute that methods
can you check the logic of the Palmera Guard and verify that!!
@alfredolopez80 thank you.
if you check the test case in comments, you will see that it’s different from tests that you mentioned., it’s not replay attack, if safe disable module from its own, the disconnect safe and removeWholeTree will revert. It means that root couldn’t delete that safe.
It's not about calling remove safe or disconnect safe twice; it's about first disabling the module, so any attempt to disconnect the safe will be reverted.
@alfredolopez80 thank you.
if you check the test case in comments, you will see that it’s different from tests that you mentioned., it’s not replay attack, if safe disable module from its own, the disconnect safe and removeWholeTree will revert. It means that root couldn’t delete that safe.
It's not about calling remove safe or disconnect safe twice; it's about first disabling the module, so any attempt to disconnect the safe will be reverted.
ok, @0xmahdirostami retake it this, and let me explain the details about how removeSafe and disconnectSafe work, and how to connect with the Palmera Guard
RootSafe, SuperSafe and Child Safe can't disable module in their own (using the native methods of safe disableModule(module address)
and setGuard(zero address)
) becuase the Palmera Guard prevent if any Safe of Organization try to disable module or guard without autority, only the rootSafe have the Role and Autority to call DisconnectSafe or removeWholeTree, and eliminate de safe of the array safes[org][id's]
in this scenario and pass this if in the PalmeraGuard: https://github.com/hats-finance/Palmera-0x5fee7541ddcd51ba9f4af606f87b2c42eea655be/blob/1ac35880b5d45154267788e2db548eaaae0beaa0/src/PalmeraGuard.sol#L62, so is impossible to any superSafe or Child Safe execute in their own
any disable module or disable guard!!
The Root Safe can call remoteSafe, Disconnect Safe or remoteWholeTree if they want but of course the have the more high level in all organization, and we asume have a deep knowledge of the consecuence into their organization, but in any case not produce the error that you mentions!!
I run your test have several point in your PoC:
it working that way:
function test_test() public {
(uint256 rootId, uint256 safeA1Id) =
palmeraSafeBuilder.setupRootOrgAndOneSafe(orgName, safeA1Name);
address rootAddr = palmeraModule.getSafeAddress(rootId);
address safeAddress = palmeraModule.getSafeAddress(safeA1Id);
vm.startPrank(rootAddr);
palmeraModule.setRole(
DataTypes.Role.SAFE_LEAD_EXEC_ON_BEHALF_ONLY,
rootAddr,
safeA1Id,
true
);
palmeraHelper.setSafe(rootAddr);
bytes memory data = abi.encodeCall(
ISafe.disableModule, (Constants.SENTINEL_ADDRESS, address(palmeraModule))
);
/// try to disable module through execTransactionOnBehalf
bool result = palmeraModule.execTransactionOnBehalf(
orgHash,
rootAddr,
safeAddress,
safeAddress,
uint256(0),
data,
Enum.Operation.Call,
"0x"
);
// try to disconnect
vm.expectRevert();
palmeraModule.disconnectSafe(safeA1Id);
}
But this is works only with the express colaboration of the rootSafe, the question is the RootSafe execute this several steps to fail the correct way to disconnectSafe?? i'm not sure is a issue, Because it is like intentionally placing a wrong address in the receive address in a cross-chain transfer, that is, RootSafe, which is the only one that has the authorization levels to execute that series of steps, executes them with the intention of making the methods fail. disconnectSafe()
and removeWholeTree()
Github username: @0xmahdirostami Twitter username: 0xmahdirostami Submission hash (on-chain): 0xd77a9b46593e4a7658ab290f9fe2d76a34d5ed7ef1b4e5558a5f286e8c6d8cf2 Severity: high
Description: Description: The
_exitSafe
function currently includes a check forgetPreviewModule(caller)
and reverts if it returnsaddress(0)
. This can lead to a Denial of Service (DoS) issue because users can calldisableModule
on their own, causing subsequent calls to_exitSafe
to fail.Impact: The DoS vulnerability in
_exitSafe
affects theremoveWholeTree
anddisconnectSafe
functions. IfdisableModule
has been called by the user, it will result in a failure of_exitSafe
, thereby preventing the proper execution of these functions, leading to Protocol insolvency.Proof of Concept (PoC):
Here is the relevant code snippet in
_exitSafe
:If a user has previously called
disableModule
,getPreviewModule(caller)
will returnaddress(0)
, leading to the function reverting.Suggested Test Case:
please change the following:
To demonstrate the issue, add the following test to
PalmeraGuardTest
:Expected Log:
Mitigation:
Update the
_exitSafe
function to handle cases whereprevModule
isaddress(0)
:By checking if
prevModule
is notaddress(0)
, the function avoids reverting unnecessarily and ensures that_exitSafe
can proceed even if the module has already been disabled.