GithubHelp home page GithubHelp logo

2024-04-coinbase-mitigation-findings's Introduction

Coinbase Smart Wallet Mitigation Review

Unless otherwise discussed, this repo will be made public after audit completion, sponsor review, judging, and issue mitigation window.

Contributors to this repo: prior to report publication, please review the Agreements & Disclosures issue.


Audit findings are submitted to this repo

Sponsors have three critical tasks in the audit process:

  1. Respond to issues.
  2. Weigh in on severity.
  3. Share your mitigation of findings.

Let's walk through each of these.

High and Medium Risk Issues

Wardens submit issues without seeing each other's submissions, so keep in mind that there will always be findings that are duplicates. For all issues labeled 3 (High Risk) or 2 (Medium Risk), these have been pre-sorted for you so that there is only one primary issue open per unique finding. All duplicates have been labeled duplicate, linked to a primary issue, and closed.

Judges have the ultimate discretion in determining validity and severity of issues, as well as whether/how issues are considered duplicates. However, sponsor input is a significant criterion.

Respond to issues

For each High or Medium risk finding that appears in the dropdown at the top of the chrome extension, please label as one of these:

  • sponsor confirmed, meaning: "Yes, this is a problem and we intend to fix it."
  • sponsor disputed, meaning either: "We cannot duplicate this issue" or "We disagree that this is an issue at all."
  • sponsor acknowledged, meaning: "Yes, technically the issue is correct, but we are not going to resolve it for xyz reasons."

Add any necessary comments explaining your rationale for your evaluation of the issue.

Note that when the repo is public, after all issues are mitigated, wardens will read these comments; they may also be included in your C4 audit report.

Weigh in on severity

If you believe a finding is technically correct but disagree with the listed severity, select the disagree with severity option, along with a comment indicating your reasoning for the judge to review. You may also add questions for the judge in the comments. (Note: even if you disagree with severity, please still choose one of the sponsor confirmed or sponsor acknowledged options as well.)

For a detailed breakdown of severity criteria and how to estimate risk, please refer to the judging criteria in our documentation.

QA reports, Gas reports, and Analyses

All warden submissions in these three categories are submitted as bulk listings of issues and recommendations:

  • QA reports include all low severity and non-critical findings from an individual warden.
  • Gas reports include all gas optimization recommendations from an individual warden.
  • Analyses contain high-level advice and review of the code: the "forest" to individual findings' "trees.”

For QA reports, Gas reports, and Analyses, sponsors are not required to weigh in on severity or risk level. We ask that sponsors:

  • Leave a comment for the judge on any reports you consider to be particularly high quality. (These reports will be awarded on a curve.)
  • For QA and Gas reports only: add the sponsor disputed label to any reports that you think should be completely disregarded by the judge, i.e. the report contains no valid findings at all.

Once labelling is complete

When you have finished labelling findings, drop the C4 team a note in your private Discord backroom channel and let us know you've completed the sponsor review process. At this point, we will pass the repo over to the judge to review your feedback while you work on mitigations.

Share your mitigation of findings

Note: this section does not need to be completed in order to finalize judging. You can continue work on mitigations while the judge finalizes their decisions and even beyond that. Ultimately we won't publish the final audit report until you give us the OK.

For each finding you have confirmed, you will want to mitigate the issue before the contest report is made public.

If you are planning a Code4rena mitigation review:

  1. In your own Github repo, create a branch based off of the commit you used for your Code4rena audit, then
  2. Create a separate Pull Request for each High or Medium risk C4 audit finding (e.g. one PR for finding H-01, another for H-02, etc.)
  3. Link the PR to the issue that it resolves within your contest findings repo.

Most C4 mitigation reviews focus exclusively on reviewing mitigations of High and Medium risk findings. Therefore, QA and Gas mitigations should be done in a separate branch. If you want your mitigation review to include QA or Gas-related PRs, please reach out to C4 staff and let’s chat!

If several findings are inextricably related (e.g. two potential exploits of the same underlying issue, etc.), you may create a single PR for the related findings.

If you aren’t planning a mitigation review

  1. Within a repo in your own GitHub organization, create a pull request for each finding.
  2. Link the PR to the issue that it resolves within your contest findings repo.

This will allow for complete transparency in showing the work of mitigating the issues found in the contest. If the issue in question has duplicates, please link to your PR from the open/primary issue.

2024-04-coinbase-mitigation-findings's People

Contributors

c4-bot-1 avatar c4-bot-10 avatar c4-bot-2 avatar c4-bot-4 avatar c4-bot-5 avatar c4-bot-6 avatar c4-bot-7 avatar c4-bot-8 avatar c4-bot-9 avatar code4rena-id[bot] avatar knownfactc4 avatar

Watchers

 avatar

2024-04-coinbase-mitigation-findings's Issues

ADD-01 MitigationConfirmed

Lines of code

Vulnerability details

Issue Report

ADD-01: Gas Fixes 1

Details

Issue#195

Issue#137

The following gas optimizations were identified for integration to the codebase to save gas:

[G-02] Optimization Proposal for Withdraw Function in MagicSpend Contract

[G-04] Check withdrawAmount - maxCost for greater than 0 if 0 it can save 1 Gsreset (~2900 Gas) and 1 Gcoldsload (Saves ~5000 Gas)

[G-06] Switch the order of if statement to fail early saves 1 function call half of the times where function have SLOAD (Saves ~2100 Gas Half of the times)

Mitigation

PR#18

  • G-02: Validations are now reordered in the withdraw function to check signature fisrt and fail early before the _validateRequest gas intensive call which saves more gas.

Loc:

function withdraw(WithdrawRequest memory withdrawRequest) external {
        if (block.timestamp > withdrawRequest.expiry) {
            revert Expired();
        }

        if (!isValidWithdrawSignature(msg.sender, withdrawRequest)) {
            revert InvalidSignature();
        }
        
        _validateRequest(msg.sender, withdrawRequest);

        // reserve funds for gas, will credit user with difference in post op
        _withdraw(withdrawRequest.asset, msg.sender, withdrawRequest.amount);
    }
  • G-04: Additional check was added to function in light of G-04 that checks if withdrawAmt is greater than maxCost before updating mapping. This indeed saves gas.

Loc:

if (withdrawAmount > maxCost) {
            _withdrawable[userOp.sender] += withdrawAmount - maxCost;
        }
  • G-06: if statements reorganized which saves gas when calling this function.

Loc:

function withdraw(WithdrawRequest memory withdrawRequest) external {
        _validateRequest(msg.sender, withdrawRequest);

        if (block.timestamp > withdrawRequest.expiry) {
            revert Expired();
        }

        if (!isValidWithdrawSignature(msg.sender, withdrawRequest)) {
            revert InvalidSignature();
        }

        // reserve funds for gas, will credit user with difference in post op
        _withdraw(withdrawRequest.asset, msg.sender, withdrawRequest.amount);
    }

Conclusion

G-02, G-04, G-06 optimizations implemented successfully.

QA-01 MitigationConfirmed

Lines of code

Vulnerability details

https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/SmartWallet/MultiOwnable.sol#L102

Issue Report

QA-01: All Smart Wallet funds will be lost if users remove all owners

Details

Issue#181

An oversight was found in removeOwnerAtIndex where all owners could be removed including the last owner. This presents a significant risk that can potentially lead to loss of funds if all owners loose access.

Mitigation

PR#43

  • The modified version of removeOwnerAtIndex now includes a check to ensure that the operation does not proceed if attempting to remove the last owner.

Loc:

function removeOwnerAtIndex(uint256 index, bytes calldata owner) external virtual onlyOwner {
        MultiOwnableStorage storage $ = _getMultiOwnableStorage();
        if ($.nextOwnerIndex - $.removedOwnersCount == 1) {
            revert LastOwner();
        }

        _removeOwnerAtIndex(index, owner);
    }
  • A function was added to be able to remove last owner to allow for revocation of last owner if need be.

Loc:

function removeLastOwner(uint256 index, bytes calldata owner) external virtual onlyOwner {
        MultiOwnableStorage storage $ = _getMultiOwnableStorage();
        uint256 ownersRemaining = $.nextOwnerIndex - $.removedOwnersCount;
        if (ownersRemaining > 1) {
            revert NotLastOwner(ownersRemaining);
        }

        _removeOwnerAtIndex(index, owner);
    }

Suggestion

The number 1 is used to check if there's only one owner left. While understandable, using magic numbers directly in code can be considered poor practice. Define a constant at the beginning of your contract to give context to this value.

uint256 private constant MIN_OWNERS = 1;

Conclusion

This fix succesfully mitigates the issue#181

ADD-02 MitigationConfirmed

Lines of code

Vulnerability details

Issue Report

ADD-02: Gas Fixes 2

Details

Issue#195

Issue#38

Gas optimizations:

[Issue#38] unchecked loop increments no valid in solidity > v0.8.22

[G-01] Update storage variable once outside of the loop instead of updating it every time in loop it saves 1 SSTORE, 1 SLOAD per iteration(Saves ~2200 Gas per iteration)

[G-03] Call _getMultiOwnableStorage() one time to fetch storage pointer and avoid extra internal function call

[G-09] Do not assign a variable with its default value

Mitigation

PR#45

  • Issue#38: loop index increment correctly optimized by removing unchecked block.

Loc:

function executeBatch(Call[] calldata calls) public payable virtual onlyEntryPointOrOwner {
        for (uint256 i; i < calls.length; i++) {
            _call(calls[i].target, calls[i].value, calls[i].data);
        }
    }
  • G-01: nextOwnerIndex storage variable is now cached in _initializeOwners function contributing to gas savings.

Loc:

function _initializeOwners(bytes[] memory owners) internal virtual {
        uint256 nextOwnerIndex_ = _getMultiOwnableStorage().nextOwnerIndex;
        for (uint256 i; i < owners.length; i++) {
            if (owners[i].length != 32 && owners[i].length != 64) {
                revert InvalidOwnerBytesLength(owners[i]);
            }

            if (owners[i].length == 32 && uint256(bytes32(owners[i])) > type(uint160).max) {
                revert InvalidEthereumAddressOwner(owners[i]);
            }

            _addOwnerAtIndex(owners[i], nextOwnerIndex_++);
        }
        _getMultiOwnableStorage().nextOwnerIndex = nextOwnerIndex_;
    }
  • G-03: cached storage pointer in _initializeOwners saves gas.

Loc:

function _initializeOwners(bytes[] memory owners) internal virtual {
        MultiOwnableStorage storage $ = _getMultiOwnableStorage();
        uint256 nextOwnerIndex_ = $.nextOwnerIndex;
        for (uint256 i; i < owners.length; i++) {
            if (owners[i].length != 32 && owners[i].length != 64) {
                revert InvalidOwnerBytesLength(owners[i]);
            }

            if (owners[i].length == 32 && uint256(bytes32(owners[i])) > type(uint160).max) {
                revert InvalidEthereumAddressOwner(owners[i]);
            }

            _addOwnerAtIndex(owners[i], nextOwnerIndex_++);
        }
        $.nextOwnerIndex = nextOwnerIndex_;
    }
  • G-09: No-op assignment of default values have been removed which saves some gas.

Loc:

(name, version) = _domainNameAndVersion();
        chainId = block.chainid;
        verifyingContract = address(this);
        salt = salt; // `bytes32(0)`.
        extensions = extensions; // `new uint256[](0)`.

Conclusion

Gas optimizations identified have been applied successfully.

ADD-02 MitigationConfirmed

Lines of code

Vulnerability details

Comments

  1. Simple change to iterator incrementation method in CoinbaseSmartWallet::executeBatch.
  2. Replaces MultiOwnable::_addOwner with _addOwnerAtIndex
  3. Cache nextOwnerIndex in MutliOwnable::_initializeOwners rather than read from storage in each loop iteration.
  4. Cache _getMultiOwnableStorage() in MultiOwnable::_initializeOwners to avoid having to call it twice.

Conclusion

LGTM

H-01 MitigationConfirmed

Lines of code

Vulnerability details

C4 Issue

H-01 : Users making specific chain account ownership upgrades will likely cause issues when later using cross-chain replay-able ownership upgrades

Comments

The protocol wallet uses cross chain methods to manage wallet owners.

Vulnerability details

The root cause is in the one of the methods used to manage users cross chain.

The method in question is : removeOwnerAtIndex that can be replayed cross-chain and despite the same index parameter it may point to a different owner.

If the owner (on the same index) doesn't match cross chain this result in a high risk position of a user/s losing ownership of their wallet.

Mitigation

The issue is successfully remediated by applying the PR

The patch consist in reparametrization of the removeOwnerAtIndex method by adding a second parameter of owner bytes that is now explicitly checked to be the desired owner match on the same index.

This line effectively checks that the desired owner is correctly targeted.

Tests are patched accordingly to match the new parametrization.

Notes

Any wallet owner can decide the fate of other wallet owners.

Conclusions

Successful Mitigation

Inability to Remove Compromised Owners Due to Data Loss and Lack of Backup Records

Lines of code

https://github.com/coinbase/smart-wallet/blob/95c8ab2777fa69243a56a5f4d1ce8ce629d707f3/src/MultiOwnable.sol#L109

Vulnerability details

Impact

The vulnerability arises specifically because the MultiOwnable contract's security model does not account for the real-world possibility of data loss. It assumes that owners will always have access to and the ability to reproduce the exact bytes representing their public keys.

An owner who has lost control over their private key will remain listed. This could leave the door open for attackers if the private key is compromised, as the legitimate participants would have no means to revoke the compromised owner's access.

see code below:

function removeOwnerAtIndex(uint256 index, bytes calldata owner_) public virtual onlyOwner {
        bytes memory owner = ownerAtIndex(index);
        if (owner.length == 0) revert NoOwnerAtIndex(index);
        if (keccak256(owner) != keccak256(owner_)) revert WrongOwnerAtIndex(index, owner_);

        delete _getMultiOwnableStorage().isOwner[owner];
        delete _getMultiOwnableStorage().ownerAtIndex[index];

        emit RemoveOwner(index, owner);
    }

In the case where data is not managed efficiently through use of backups and publicly accessible accurate records it might be impossible to remove owner hence compromising the function utility and ownership all together.

Poc Scenario

  • Owner A and Owner B are added using their Ethereum addresses.
  • Owner C is added using their public key, represented by (x, y) coordinates encoded as bytes, to utilize the passkey feature for additional security measures.
  • Owners A and B are registered with their Ethereum addresses.
  • Owner C generates a new Ethereum key pair specifically for this contract, uses the public key derived from this pair for registration, and is added with the public key bytes.
  • Owner C loses the device containing their private key. This loss includes the inability to access the Ethereum account and, critically, the inability to regenerate the exact public key bytes used for their registration as an owner in the MultiOwnable contract.
  • Due to the loss of the private key, Owner C can no longer participate in the governance of the DeFi application. The remaining owners decide to remove Owner C from the contract.
  • The contract requires the exact bytes of Owner C's public key for removal. However, without access to the private key or an external, accurate record of the public key bytes, the remaining owners cannot generate the required data to invoke removeOwnerAtIndex successfully.
  • Owner C remains a registered owner, despite no longer having control over their associated private key. This situation presents a serious security risk and operational challenge, as Owner C can be potentially compromised and can't be removed.

Severity Assesment: Medium to High

The severity depends on actions taken by compromised owner C and threat response. It becomes a high severity bug if malicious owner does the following actions:

  1. Add unauthorized or malicious addresses as new owners

  2. Remove legitimate owners from the contract gaining full control

Mitigation Strategies

  • Introduce a mechanism that allows for owner removal under specific conditions without requiring the exact bytes, such as a time-based lock that allows an owner to be marked for removal if certain conditions are met.

  • Introduce a dead man's switch mechanism where owners can periodically confirm their active control over their keys. Failure to do so could trigger a recovery process, allowing for their eventual removal.

  • Require owners to store backup copies of their public key bytes in a secure yet accessible manner. This could involve off-chain storage solutions that are still secure and private.

  • Have a multi-signature administrative override for critical situations like owner removal.

Assessed type

Access Control

ADD-01 MitigationConfirmed

Lines of code

Vulnerability details

Comments

  1. Reorders logic flow to do signature validation checks before before calling _validateRequest.

Conclusion

LGTM

M-01 MitigationConfirmed

Comments

MagicSpend is a contract that allows on-chain accounts to present valid Withdraw Requests and receive funds.

The withdraw signature are obtained off-chain by providing fiat money or other tokens that the user can exchange for this prepaid ETH amount.

This withdraw request signature can be presented to the contract in two ways as :

  1. a direct account/SWC call to MagicSpend#withdraw method. In this way the user pays gas fees to get the already prepaid ETH amount

  2. MagicSpend contract can be also used as a erc-4337 EntryPoint v0.6 compliant paymaster. As a paymaster it allows the user/SWC account to get ETH without the need to already have the necessary native gas to spend for the transaction.

Vulnerability details

The guard that is supposed to protect the MagicSpend when used as a paymaster from accounts withdrawing when there is not enough native token available is not working correctly.

It can happen that the validatePaymasterUserOp says it can pay for a valid withdraw request but next fail on postOp where the payment to the account is finally made.

When the bundler adds UserOperation for MagicSend by successfully simulate the necessary transaction off-chain a revert of insufficient native token balance can still happen on-chain and can damage the reputation of MagicSend as a paymaster.

This revert can be a consequence of allowing in the same transaction to have other ways to deplete the MagicSend contract funds which are impossible to predict on time when the bundler bundles chosen UserOperation.

Mitigation

The MagicSend contract can deplete its native funds in the same transaction with valid withdraw request signatures send trough the EntryPoint as a paymaster and/or the call to withdraw method. Making the first mitigation PR16 as an end solution unfeasible.

The second mitigation PR17 tries to fix this problem by providing the owner of MagicSpend a tool to probabilistically limit the possible amount to withdraw within the same transactions. The MagicSpend owner must carefully monitor transactions to set an appropriate value for the maxWithdrawDenominator which regulates the amount of native funds moved out of the contract.

By using this kind of budgeting it lowers the probability of having too little funds available when withdraws happen in the same transaction but as mentioned in the fix comment : this doesn't entirely solve the issue, and the efficacy depends the value chosen and usage.

Suggestions

Here are 4 options I can provide to hopefully ease this issue: (the first two are the most promising)

  1. The following idea has two goals. To minimize non reverting postOp operations and to facilitate the work imposed on the MagicSpend owner.

I would suggest a merge of both mentioned PRs and a reiteration of the denominator idea in a way that allows the owner to chose how much budget a direct method withdrawing has and leaving the rest of the balance to be used as a in paymaster mode only.

This could be achieved by having a more precise budgeting without using a denominator calculation but a fixed budget value so when the account choose to direct withdraw we can have something like this:

+   public maxDirectWithdrawBudget;
+   public currentDirectWithdrawBudget;

    function withdraw(WithdrawRequest memory withdrawRequest) external {
        _validateRequest(msg.sender, withdrawRequest);


        if (!isValidWithdrawSignature(msg.sender, withdrawRequest)) {
            revert InvalidSignature();
        }


        if (block.timestamp > withdrawRequest.expiry) {
            revert Expired();
        }

+       if (withdrawRequest.amount > currentDirectWithdrawBudget) {
+           revert OufOfWithdrawBudget();
+       }
+
+       currentDirectWithdrawBudget -= withdrawRequest.amount;
+
+       if (currentDirectWithdrawBudget <= LOW_MAXWITHDRAW_BUDGET) {
+           emit LowOnMaxWithdrawBudget(block.timestamp, maxDirectWithdrawBudget);
+       }

        // reserve funds for gas, will credit user with difference in post op
        _withdraw(withdrawRequest.asset, msg.sender, withdrawRequest.amount);
    }

When first set the currentDirectWithdrawBudget is equal to maxDirectWithdrawBudget.

Now inside validatePaymasterUserOp we can take in account this maxDirectWithdrawBudget which is solely used as a concrete budget for direct withdrawing and reconsider PR16 when checking if we have enough token balance.

The MagicSpend owner gets a bonus of a easier work of monitoring for low token balance. And the bundler should now work fine as a isolated process of funds depletion.

  1. Another interesting idea/option is to add an un/locking type of functionality as an extra step for user/account wanting to directly withdraw:

I saw this 1, 2 as a reference implementation for the next idea

For example: a account decides to use the direct withdrawing functionality and first calls unlockDirectWithdrawing method which "sets" this account to direct withdraw mode only and for the same account to be used with a paymaster it (the SWC) must first call lockDirectWithdrawing.

An important aspect of this option is that withdrawing cannot be called in the same block as unlockDirectWithdrawing, but for withdrawing to work unlock must be called a block prior.

In paymaster mode we can check that an account is not locked as withdrawing only by only checking a mapping (!not by using block.number! which is banned) and that the contract has enough funds by reconsider PR16 then we can proceed sending the funds.

Aa a note: default account/SWC status is paymaster mode (or locked withdrawals) so accounts don't need to speed gas on calling lockDirectWithdrawing.

  1. Instead of using signatures use a token balance mapping for accounts/SWC but in this case there must be more work done when an allowed balance expires. Plus in this mode the contract should get/have necessary funds deposited in the (almost) same time a account balance is created off-chain. But the signature solution is obviously a more elegant one to pursue.

  2. There is another option to just remove the direct withdraw method but I don't think is the right idea of MagicSpend contract

Notes

I marked this issue as mitigated becouse the provided solutions was good enough. But if a extra call step is not a problem I would chose the second suggested option of locking direct withdraws as a possible alternative.

Conclusions

Partial Mitigation (MagicSpend owner must pay extra attention to control funds depletion)

QA-01 MitigationConfirmed

Lines of code

Vulnerability details

Comments

The protocol wallet owners have cross chain methods to manage owners.

Vulnerability details

The root cause is in the one of the owner managing methods that can remove all wallet owners leaving wallet funds locked inside and also locking any other interaction with the wallet.

The method in question is : removeOwnerAtIndex that can remove all assigned wallet owners.

Mitigation

The issue is successfully remediated by applying the PR

The patch adds this check that prevents the removeOwnerAtIndex to remove all owners.

The variable nextOwnerIndex and removedOwnersCount used in the previous check are correctly accounted for.

Suggestions

Consider adding this newly added method named removeLastOwner to the list of cross chain callable methods for managing wallet owners.

Notes

The same PR also:

  • adds a method that will explicitly "renounce" all wallet ownership where there is only one owner left.
  • restricts adding of new owner address (trough addOwnerAddress) to be only called externally.

Conclusions

Successful Mitigation

H-01 MitigationConfirmed

Lines of code

Vulnerability details

Comments

The SmartWallet implementation allows certain functions to be callable using cross chain replayable singatures. However the implementation of MultiOwnable::removeOwnerAtIndex was susceptible to unintended behaviours when the ownership storage layout was different across different chains. This led to the possibility of different owners being removed than the one originally intended by the signer.

Mitigation

To mitigate this the removeOwnerAtIndex field now takes a bytes calldata owner_ argument to ensure only the owner the user intends on removing is removed when this function is called. The function will revert if the owner at the given index is not the owner_ passed to the function.

Conclusion

The mitigation added succeeds in removing the possibility that a single cross chain replayable signature leads to different owners being removed on different chains.

However there are two issues worth noting:

Scenario A: Unintended Non-removal of Owner
If the owner a user wishes to remove is present on all chains but at different indexes, their removeOwnerAtIndex call would fail to remove that owner on some chains. It's necessary that the UI when signing the transactions makes it clear to the user which chains this function call will be valid on or risk them unexpectedly leaving an unwanted owner on some chains.

Scenario B: Reverts Causing Mismatched Replayable Nonces
In the event the call reverts on any given chain (when the owner_ is not present at index) this will result in the account's cross chain replayable nonces being out of sync across the different chains. This means the user will have to do something to consume the current nonce on the chain that is lagging behind before any future cross chain replayable signatures will succeed on that chain.

Ultimately, opting to not use owner indexing in MultiOwnable would go a long way to removing these potential edge cases and reduce complexity in the MultiOwnable contract. It's up to the team to consider whether the gas saved by using ownerIndex in SignatureWrapper (rather than the full owner bytes) make this an acceptable tradeoff.

M-01 Unmitigated

Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/MagicSpend/MagicSpend.sol#L130-L135

Vulnerability details

Issue Report

M-01: Balance check during MagicSpend validation cannot ensure that MagicSpend has enough balance to cover the requested fund.

Details

Issue#110

An issue was identified in MagicSpend contract where during the validation phase of multiple UserOperations, the contract's balance check does not account for the cumulative effect of multiple withdrawals being validated and executed in sequence. This can lead to a situation where the contract believes it has enough balance to cover all requests during validation but runs out of funds during the execution phase, due to not accounting for the total aggregate withdrawal amount requested by all operations.

Mitigation

  1. PR#16
  2. PR#17

In the first PR the core idea was to use a variable to keep track of the total amount being withdrawn during the validation phase of each UserOperation within a transaction bundle. This would help ensure that the sum of all withdrawals did not exceed the contract's available balance. By validating each withdrawal request against the tracked total withdrawal amount and the contract's balance, the PR aimed to preemptively reject withdrawal requests that would lead to an insufficient balance, thus preventing the entire transaction bundle from failing. This however turns out to be inefficient in the case of paymaster as reputional damage will have already been done by the multiple reverts introduced by this mitigation.

The second PR introduced a new strategy to address the issue by setting a maxWithdrawDenominator, which represents a more probabilistic approach to managing the risk of transaction reverts due to insufficient contract balance when multiple withdrawals occur within the same transaction bundle. By probabilistically limiting withdrawal amounts, the strategy reduces the likelihood of transaction reverts in bundles due to insufficient balance, which could negatively impact the Paymaster's reputation.

The issue#110 is not entirely resolved but the introduction of maxWithdrawDenominator allows for limiting the size of each withdrawal relative to the contract's balance. This approach is designed to probabilistically reduce the likelihood of reverts due to balance insufficiency when multiple withdrawals are processed in the same transaction.

Loc

Suggestion

Implement mechanisms for dynamically adjusting the maxWithdrawDenominator based on recent transaction history or other relevant metrics. This could help adapt to changing usage patterns without manual intervention.

Conclusion

Unmitigated/Partially Mitigated: The solution presented decreases the likelihood of transaction failures due to insufficient balance when multiple withdrawals are executed in a single transaction batch, but it does not eliminate the possibility entirely.

Assessed type

Invalid Validation

M-01 MitigationConfirmed

Lines of code

Vulnerability details

Comments

In the original MagicSpend implementation there were insufficient checks that the MagicSpend contract had enough ETH to cover all the withdrawal requests in a UserOps bundle.

Mitigation

To mitigate this _validateRequest (called inside of validatePaymasterUserOp) allows requests to only request a fraction of the contracts ether balance meaning maxWithdrawDenominator amount of requests could be processed in a UserOps bundle without the contract unexpectedly running out of funds to cover withdrawals.

Conclusion

The mitigations made should fix this issue, providing maxWithdrawDenominator is set to a high enough value to cover the maximum possible user operations that can put into a single UserOps bundle.

However as the withdraw function also calls _validateRequest it is necessary that the contract has maxWithdrawDenominator times more than the maximum withdraw amount that can be signed for else their otherwise valid WithdrawRequest's will revert.

Agreements & Disclosures

Agreements

If you are a C4 Certified Contributor by commenting or interacting with this repo prior to public release of the contest report, you agree that you have read the Certified Warden docs and agree to be bound by:

To signal your agreement to these terms, add a 👍 emoji to this issue.

Code4rena staff reserves the right to disqualify anyone from this role and similar future opportunities who is unable to participate within the above guidelines.

Disclosures

Sponsors may elect to add team members and contractors to assist in sponsor review and triage. All sponsor representatives added to the repo should comment on this issue to identify themselves.

To ensure contest integrity, the following potential conflicts of interest should also be disclosed with a comment in this issue:

  1. any sponsor staff or sponsor contractors who are also participating as wardens
  2. any wardens hired to assist with sponsor review (and thus presenting sponsor viewpoint on findings)
  3. any wardens who have a relationship with a judge that would typically fall in the category of potential conflict of interest (family, employer, business partner, etc)
  4. any other case where someone might reasonably infer a possible conflict of interest.

ADD-02 MitigationConfirmed

Lines of code

Vulnerability details

C4 Issue

QA 38 : unchecked loop increments no valid in solidity > v0.8.22

G01 : Update storage variable once outside of the loop instead of updating it every time in loop it saves 1 SSTORE, 1SLOAD per iteration(Saves ~2200 Gas per iteration)

G03 : Call _getMultiOwnableStorage() one time to fetch storage pointer and avoid extra internal function call

G09 : Do not assign a variable with its default value

Mitigation

Gas optimization are correctly implemented as suggested.

From the difference in gas snapshot there is a slightly decrease of gas usage for the patched version which is to be expected.

Conclusions

Successful Mitigation

H-01 MitigationConfirmed

Lines of code

Vulnerability details

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L102

Issue Report

H-01: Users making specific chain account ownership upgrades will likely cause issues when later using cross-chain replay-able ownership upgrades

Details

Issue#114

A warden raised a concern during contest where a user can mistakenely or maliciously remove unintended user. The removeOwnerAtIndex function removes an owner based on their index in the ownerAtIndex mapping. If the owner indexes are not consistently synchronized across chains, a transaction to remove an owner on one chain could indeed remove a different owner if replayed on another chain, given that the indexes might not map to the same addresses.

Mitigation

PR#42

  • An explicit check was added to check if owner byte passed by sender matches the intended owner to be improved. This succesfully mitigates this mistake from happening when calling this function

  • Tests were added that verify new changes

Suggestion

Consider providing a method to update or confirm owner data without removing the owner entirely.

Conclusion

The reported issue#114 has been mitigated succesfully.

ADD-01 MitigationConfirmed

Lines of code

Vulnerability details

C4 Issue

G06 : Switch the order of if statement to fail early saves 1 function call half of the times where function have SLOAD (Saves ~2100 Gas Half of the times)

G02 : Optimization Proposal for Withdraw Function in MagicSpend Contract

Mitigation

Gas optimization are correctly implemented as suggested.
Main point being the different order of operation/checks consumes less gas.

Changes are included into PR16

From the difference in gas snapshot there is a slightly decrease of gas usage for the patched version which is to be expected.

Conclusions

Successful Mitigation

QA-01 MitigationConfirmed

Lines of code

Vulnerability details

Comments

In the original MultiOwnable implementation there was no check to stop a user accidentally removing the last owner of an account, which would lead to the account being permanently inaccessible and any funds it held being locked.

Mitigation

To mitigate this, the function removeOwnerAtIndex now reverts if the index passed to it contains the last active owner. Additionally a removeLastOwner function has been added to allow users to still remove the accounts last owner if that is indeed their intention.

Conclusion

The mitigations made should indeed make it impossible for a user to mistakenly remove an account's last owner via removeOwnerAtIndex.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.