Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Social Recovery Module Update #23

Merged
merged 38 commits into from
Jul 1, 2024
Merged

Social Recovery Module Update #23

merged 38 commits into from
Jul 1, 2024

Conversation

remedcu
Copy link
Contributor

@remedcu remedcu commented Jun 27, 2024

This PR introduces some breaking changes to the Social Recovery Module. This PR does the following:

  • Social Recovery Module now inherits the GuardianStorage. (Non-backward compatible)
  • New function to invalidate nonce (Reason: Invalidate nonce 5afe/CandideWalletContracts#40)
  • Associated tests for the inheritance as mentioned earlier & new functions have been added & updated.
  • The Social Recovery Module and the associated smart contracts have been Formally Verified using Certora.
  • CI Workflow added for checking Certora & Test (Requires a valid Certora Key to run the CI, else it will skip. It is also recommended to use "Ruleset" in Github to ensure these checks are passed for all PR to main branch for added security.)
  • Audit Report for the Social Recovery Module by Ackee.
  • Small changes to README, formatting changes in Smart Contracts, etc.

remedcu and others added 30 commits May 3, 2024 10:07
This PR sets up the Safe Harness along with a dummy rule to test the
functionality of the same.

Instead of using `link` as we had for the `safe-locking` setup, we are
using a function called `requireSocialRecoveryModuleEnabled(...)` which
requires that the `SocialRecoveryModule` is enabled for the Safe.

Fixes #2
This PR syncs from Candide's repo to get 100% code coverage (implemented
by the Candide Team).

---------

Co-authored-by: Andrew Wahid <andrew.wahid1@gmail.com>
This PR adds a few rules which mainly check two things regarding
guardians in a Social Recovery Module:
- Adding a guardian
- Removing a guardian

for a Safe Smart Account using the Social Recovery Module.

In addition to the above, there are some slights changes in:
- The `conf` file is updated with more flags for iteration.
- Using Harness for both the Social Recovery Module and Guardian Storage
(Optimistically, because we only have created a new function for
Guardian Storage, and not yet anything for the Social Recovery Module).

Note: As a workaround for the linked list-based invariant, a `require`
(with comments) is added to avoid problems until #15 is implemented.

Fixes #4
Fixes #18

This PR adds a single rule which checks that under ideal conditions, the
`confirmRecovery(...)` can always be called by a guardian to either
initiate/execute the recovery process.
Closes #5

This PR adds a rule which checks that the finalization of the recovery
process is not possible for a safe account that has disabled the
recovery module.

The prover found a counter-example which allows calling the finalize
function with zero new owners and threshold from a safe having exactly
one owner. Recovery initiation with zero new owners is not possible,
thus this counter-example is not considered (but still we check the
owner structure is not changed for the safe account after the finalize
function call).
This PR prepares the repo for audit. Once this PR is merged, a new
branch called `audit` will be created.

Changes:
- Formatting changes were made using `prettier`.
- Import updated to use named import.
- Using `getRecoveryHash` instead of
`keccak256(encodeRecoveryData(...))`
Fixes #13 

Changes in PR:

- Add rule: `cancelRecovery`
- Add rule: `cancelRecoveryDoesNotAffectOtherWallet`

---------

Co-authored-by: Shebin John <admin@remedcu.com>
Fixes #15 

Changes in PR:

- Create conf and spec file to formally verify Guardian storage

---------

Co-authored-by: Shebin John <admin@remedcu.com>
Co-authored-by: Nicholas Rodrigues Lordello <n@lordello.net>
Changes in PR:
- Minor fixes in comments
This PR implements #25 building on top of
#20 and merges to
that branch. (If the #20 PR gets merged first I'll rebase to main).

I also fixed a typo: `countZeroIffListEmpty` -> `countZeroIfListEmpty`
Fixes #10 

This PR adds a rule that verifies that the guardian can only initiate
recovery for the ones where it is marked as guardian.

This rule only checks for the initiation and not the recovery, thus
`execute` is passed as `false`.

Suggested from the Certora team, we made the `smt_groundQuantifiers` to
be `false` as the `newOwners` array even with the `require` was not
creating address within the range (`2^160`).
This PR changes the design of how the guardians are stored.

Prior to this change, the guardians were stored in a separate contract
`GuardianStorage`. The `GuardianStorage` address was then passed as a
constructor parameter to `SocialRecoveryModule` which could then make
changes to the list on request by the user. So, the recovery
functionality was achieved by having two contracts and thus, two
storages.

As a side effect, it was initially brought to light that any other
module can also modify the list of Guardians in `GuardianStorage` and
was not write-restricted only to `SocialRecoveryModule`. Any other
enabled module on a Safe could change this list.
See:
https://github.com/5afe/CandideWalletContracts/blob/870f82d3c4d286b3fd9241c6479c9472d7d73fc6/contracts/modules/social_recovery/storage/GuardianStorage.sol#L35

The initial argument for accepting this behavior was that any malicious
module can:
1- Change ownership of the account
2- Steal all account's funds
3- Execute any arbitrary function of the `SocialRecoveryModule`
impersonating the owner
Thus, it did not violate any security assumptions.

Also, allowing the guardian list to be edited by any module allows
upgradability of `SocialRecoveryModule` without having to migrate the
list of guardians.

But, later auditing partner also reported the same behavior as an issue
with the argument:

- There should be a distinction between malicious modules (or any
modules that can perform arbitrary operations) and modules that are
limited to do these things by their implementation. These "well
behaving" modules will effectively gain more privileges than they
actually need.
- It opens a new attack vector and violates least privilege principle.

After internal discussion, we concluded that allowing any module to
modify the guardians list should not be permitted and this PR reflects
the same.

In this PR, `SocialRecoveryModule` inherits `GuardianStorage` so that
there is only 1 contract involved in recovery functionality. By making
this change and removing `onlyModule` modifier, and other changes in the
code (removing `_wallet` as a parameter in some functions) now allows
only wallet i.e. `msg.sender` to change the guardians.

This also simplifies the design of the recovery functionality and
enforces to use storage space of a single contract i.e.
`SocialRecoveryModule`.

Other changes in PR include:
- Fixes to files related to formal verification
- Removing duplicated code which is now not needed
- Remove redundant parameter `_wallet` in some functions

---------

Co-authored-by: Shebin John <admin@remedcu.com>
Co-authored-by: Nicholas Rodrigues Lordello <n@lordello.net>
Fixes #6 

This PR adds a single rule which checks if the `confirmRecovery(...)`
call is successful, then the caller must be a guardian.

---------

Co-authored-by: Mikhail <16622558+mmv08@users.noreply.github.com>
This pull request adds a formal verification rule that verifies that the
delay period has to be always passed before finalizing the recovery.

I also extracted a requirement for a pending recovery process to a
helper function.

I tested the rule by commenting out
[this](https://github.com/5afe/CandideWalletContracts/blob/870f82d3c4d286b3fd9241c6479c9472d7d73fc6/contracts/modules/social_recovery/SocialRecoveryModule.sol#L238)
line and it was caught:

https://prover.certora.com/output/6575/3e23871afade4f1aaaadddf2a7dcd0c9/?anonymousKey=6a0ba85ca6c02acb503d1620918ab642bd00d55e
Fixes #7 

This PR merges the `canFinalizeRecoveryOnlyAfterDelayPeriod` rule which
checks that a call to the `finalizeRecovery` can only succeed after the
delay period with an additional check to ensure that if reverted, what
are the conditions possible.
This PR adds an extra function called `invalidateNonce(...)` based on
the recommendation from the audit team to invalidate a particular
recovery process. It could be used in two ways:
- Assuming the owner lost access to the Safe, the owner requests a
guardian for recovery initiation. Once the recovery initiation started,
the owner realized that the access was not actually lost, thus to
invalidate the current initiation (not execution, for that owner can use
`cancelRecovery(...)`), the owner can actually call the
`invalidateNonce(...)`.
- As a best practice to invalidate any `confirmedHashes[...][...]` from
a guardian which is soon to be removed.

Also added:
- a test to check if execution fails in the case of an invalidated
nonce.
- run test in CI.
- FV to check if invalidating a nonce cancels an execution of recovery.

---------

Co-authored-by: Nicholas Rodrigues Lordello <n@lordello.net>
Related to #26 

This PR adds the audit report by Ackee Blockchain for the `0.0.1`
version of the Social Recovery Module.

P.S. The `docs` folder was `.gitignore`-ed, so kept the audit folder
separate.
Fixes #9 

This PR adds one rule to check that actions done by one safe should not
affect another safe smart account.

Here we also require that the `otherSafeContract` be different from
`safeContract` instead of implications as the rule specifically looks
for that case.
akshay-ap and others added 7 commits June 19, 2024 08:31
Fixes #11 

Changes in PR:
- Adds a rule that verifies that recovery is always possible given
certain conditions are met. These conditions are mentioned in the spec
comments. These additional conditions on top of the 2 conditions given
in the issue are required because proves reports counter example without
them.

- This rule is different from `finalizeRecovery`. The new rule here
verifies that recovery should always work when certain conditions are
met.

---------

Co-authored-by: Nicholas Rodrigues Lordello <n@lordello.net>
Co-authored-by: Mikhail <16622558+mmv08@users.noreply.github.com>
…ry (#41)

This PR implements
#17 by adding the
following rules:
- the rule that verifies that the amount of approvals counted by the
`multiConfirmRecovery` function equals to the length of supplied
signature array
- duplicate signatures should always revert
- if the signature check reverts, then the function should revert
- the function doesn't add any shadow approvals not included in the
signatures array

I created a separate specs file and configuration to summarize a couple
of functions. Otherwise, the run would timeout

Failed runs where I introduced bugs:
- removed the call to `validateGuardianSignature` -
https://prover.certora.com/output/6575/73ebaf5c425447129adda18cc8a33d7c?anonymousKey=56669628037dafe0f4d909daea90212c828559cc
- added a shadow approval
https://prover.certora.com/output/6575/75b1e75c0fcc4c7fa7d9758762a42873/?anonymousKey=d2180d11c689ac54b75f076119ac18d1c7eb5205
and
https://prover.certora.com/output/6575/b9cb509223b94526976698728d715b01/?anonymousKey=1bf9bb9f412488d3d4785c1a81affc7169635d18
- removed the duplicated signer check
https://prover.certora.com/output/6575/41cd0e1911864c8e94629963d46f35f6/?anonymousKey=48962fbfca64772215f0f57f72f3f38f93302431

---------

Co-authored-by: Nicholas Rodrigues Lordello <n@lordello.net>
This PR updates the summary with `DISPATCHER(false)` in
`RecoveryConfirmationSignatureValidity.spec` as for most cases, it works
as intended (i.e. find the correct implementation from the available
contracts) and a summary is not required (to include possible reverting
paths as well).

Also, we found one case which should be checked (`isModuleEnabled(...)`)
but was ignored because we used a summary before.
This PR updates the summary to use `DISPATCHER(true)` for the
`execTransactionFromModule`.

From Certora Team:

> DISPATCHER(false) and DISPATCHER(true) both first try any known
Contract. The difference should be only if a different contract is
called which is not explicitly linked into the scene. With
DISPATCHER(true) the prover just assumes that this will never happens.
It only tries those input values for which you only call known
contracts. With DISPATCHER(false), the prover will also consider the
case you call an unknown contract and in that case assumes the worst,
i.e. all ghost variables are havoced and the return value is arbitrary.
So you get a lot of spurious counterexamples with DISPATCHER(false), but
with DISPATCHER(true) you are technically unsound, as you don’t verify
anything if unknown contracts could be called.

So, In our case, as we already summarized call to
`execTransactionFromModule(...)` if `callee` is `safeContract` before.
And as the summary goes for the non-reverting path, I think it is safe
to use `DISPATCHER(true)` here.

P.S. For the `RecoveryConfirmationSignatureValidity.spec`, as we didn't
require some method specified, that was removed, along with an extra
harness function.
Fixes #36 

This PR introduces a few rules which check the state change and the
resulting function call for the same.

A new harness function is introduced for easier guardians list check
with FV.
This PR updates a few things for #47 

- Formatting `README` and adding details to run FV.
- Ordering `GuardianStorage.spec` contents.
- Updating the `conf` file of FV.
- Upgrading certora version to 7.6.3

No new FV rule/invariant is introduced.
This PR updates the README based on the suggestions mentioned in
#49 (comment)
@nlordell
Copy link
Contributor

One small improvement we can make before "un-drafting" the PR would be to not make the FV GitHub action fail when the key is missing, but instead not skip it.

@remedcu
Copy link
Contributor Author

remedcu commented Jun 27, 2024

@nlordell made the suggested change in: 5afe#52

Will move it to ready once it is merged and this PR description is updated.

This PR adds a check to see if the `CERTORA_KEY` is set or not to run
the CI. If the key is not set, the Certora check will show as "Skipped"
instead of Success in the GitHub Actions.

This ensures that the repo can be upstreamed to Candide without any
immediate failure to the CI. It also ensures that it doesn't show the
impression of FV passing without verification.

P.S. I have disabled the ruleset which required all the FV tasks to be
successful so this could be included.
@remedcu remedcu marked this pull request as ready for review July 1, 2024 09:04
@andrewwahid andrewwahid merged commit d0083e0 into candidelabs:main Jul 1, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants