hats-finance / Safe-0x2909fdefd24a1ced675cb1444918fa766d76bdac

A collection of modules that can be used with the Safe contract
GNU Lesser General Public License v3.0
0 stars 1 forks source link

Incorrect Implementation of Fallback Function For Signature Verification Failure #25

Open hats-bug-reporter[bot] opened 1 week ago

hats-bug-reporter[bot] commented 1 week ago

Github username: -- Twitter username: @_AresAudits Submission hash (on-chain): 0x8b5989637fbdeedc5f05fc96212dca593101fdce12bb3e29caf45918aaebe940 Severity: low

Description: Description\ The fallback function in FCLP256Verifier.sol returns an empty string "" when the signature is invalid. This results in a returndatasize of 0, which causes the verifySignatureAllowMalleability function in P256.sol to fail the check eq(returndatasize(), 32). Consequently, the signature verification process fails to correctly identify invalid signatures, leading to potential security vulnerabilities.

Attack Scenario\

  1. Setup: A smart contract uses the P256 library to verify signatures for authorizing transactions.
  2. Exploit: A User submits a transaction with an invalid signature. The fallback function in FCLP256Verifier.sol returns an empty string "", resulting in a returndatasize of 0. The verifySignatureAllowMalleability function in P256.sol fails the check eq(returndatasize(), 32) and returns false.
  3. Impact: The inconsistency in handling return data sizes can lead to confusion and potential issues in other parts of the system that rely on consistent return data sizes.
  4. Why This Issue Should Be Fixed: The verifySignatureAllowMalleability function explicitly checks that the return data should be exactly 32 bytes long. This is crucial for ensuring the integrity and correctness of the signature verification process. If the return data size is inconsistent, it can lead to incorrect handling of invalid signatures

Attachments

  1. Proof of Concept (PoC) File

https://github.com/hats-finance/Safe-0x2909fdefd24a1ced675cb1444918fa766d76bdac/blob/9a18245f546bf2a8ed9bdc2b04aae44f949ec7a0/modules/passkey/contracts/verifiers/FCLP256Verifier.sol#L18C9-L20C10

https://github.com/hats-finance/Safe-0x2909fdefd24a1ced675cb1444918fa766d76bdac/blob/9a18245f546bf2a8ed9bdc2b04aae44f949ec7a0/modules/passkey/contracts/verifiers/FCLP256Verifier.sol#L37C8-L39C10

  1. Revised Code File (Optional)
    
    // SPDX-License-Identifier: LGPL-3.0-only
    /* solhint-disable no-complex-fallback */
    /* solhint-disable payable-fallback */
    pragma solidity 0.8.24;

import {IP256Verifier} from "../interfaces/IP256Verifier.sol"; import {FCL_ecdsa} from "../vendor/FCL/FCL_ecdsa.sol";

/**

To fix this vulnerability, modify the fallback function in FCLP256Verifier.sol to return abi.encodePacked(0) instead of an empty string when the signature is invalid. This ensures that the returndatasize is always 32 bytes, making it easier to handle the return data consistently in the verifySignatureAllowMalleability function in P256.sol. By implementing this change, the verifySignatureAllowMalleability function will correctly handle the return data from the fallback function, ensuring that invalid signatures are properly detected and the verification process is consistent

Files:

0xEricTee commented 1 week ago

The checks for the return data size and return data value are done in P256.sol Line 124 & Line 126 respectively:

            success := and(
                and(
                    // Return data is exactly 32-bytes long
-->                    eq(returndatasize(), 32),
                    // Return data is exactly the value 0x00..01
-->                    eq(mload(0), 1)
                ),
                // Call does not revert
                staticcall(gas(), verifier, input, 160, 0, 32)
            )

With that being said, I believe this issue is invalid.

NishantKoyalwar commented 1 week ago

Hey @0xEricTee thank u for the response ,yep the line 124-126 checks the returndatasize but the issue here is the fallback function in the FCLP256Verifier.sol does not return bytes32 value for invalid signature instead it returns “”

0xEricTee commented 1 week ago

@NishantKoyalwar Although the return byte sizes are different for valid and invalid signatures, however the code correctly handle all the cases: When the signature is valid, the staticcall must succeed, the return bytes size must be 32 bytes and the return value must be 1, other cases will be evaluated as invalid signatures.

NishantKoyalwar commented 1 week ago

Yep @0xEricTee the code correctly handles for the valid signature m but The inconsistency in handling return data sizes can lead to confusion and potential issues in other parts of the system that rely on consistent return data sizes.therefore submitted this issue as low severity.

nlordell commented 1 week ago

I agree with @0xEricTee that this is invalid.

One additional consideration is the IP256Verifier interface is specified to behave exactly like the EIP-7212 precompile, which only returns 0x00...01 IIF the signature is valid. The current implementation corresponds to how the precompile is actually implemented on multiple chains (e.g. OP and Polygon).

I also think that the interface specified by EIP-7212 is a bit "unclean" (as it could be specified to return 0 padded to 32 bytes in the case an invalid signature, but it isn't). Since the implementation here is intended to follow that of the spec, I will mark this as invalid.

NishantKoyalwar commented 1 week ago

hey @nlordell @0xEricTee ,

Thank you for your feedback, Implementing the fix will ensure consistency in the code and consistent return data sizes, which help in correctly identifying invalid signatures. Below are links to industry-wide implementations of EIP-7212 that follow this approach:

Please consider rechecking the issue. If you still think this isn't valid, I will agree with your final decision on this matter.

nlordell commented 1 week ago

As mentioned, the FCLP256Verifier is intended to behave exactly as the EIP-7212/RIP-7212 precompile works at an interface level - including returning the empty string on invalid signatures:

$ curl -s -X POST https://polygon-pokt.nodies.app -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id
":42,"method":"eth_call","params":[{"to":"0x0000000000000000000000000000000000000100","data":"0xbb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca6050232ba3a8be6b94d
5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e184cd60b855d442f5b3c7b11eb6c4e0ae7525fe710fab9aa7c77a67f79e6fadd762927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c
732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e"},"latest"]}' | jq
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": "0x0000000000000000000000000000000000000000000000000000000000000001"
}
$ curl -s -X POST https://polygon-pokt.nodies.app -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id
":42,"method":"eth_call","params":[{"to":"0x0000000000000000000000000000000000000100","data":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000"},"latest"]}' | jq
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": "0x"
}

I understand that other implementations chose to diverge from the P-256 precompile specification, which is up to them - but the one here is explicitly documented to follow it.

NishantKoyalwar commented 1 week ago

Thank you for clarification. I agree with your decision to follow the P-256 precompile specification