Skip to content

checkNSignatures/checkSignatures(): current success/revert semantics allow false‑positive validation when called on a contract without the function #1027

@mmv08

Description

@mmv08

Summary

checkNSignatures(address executor, bytes32 dataHash, bytes signatures, uint256 requiredSignatures) is documented to revert if any signature is invalid and to return normally otherwise.
However, if the target address is not a Safe (or is a proxy pointing elsewhere) and the contract has a permissive fallback() that does not revert, an external staticcall/call:

(bool ok, ) = safe.staticcall(
    abi.encodeWithSelector(
        ISafe.checkNSignatures.selector,
        executor,
        dataHash,
        signatures,
        requiredSignatures
    )
);

returns ok == true with empty return data — indistinguishable from a genuine, fully‑validated call.
Libraries and integrations therefore cannot tell whether any signature check actually occurred.

Minimal Reproduction

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;

contract NotSafe {
    // Accept any call, do nothing and return success
    fallback() external payable {}
}

interface ISafe {
    function checkNSignatures(
        address executor,
        bytes32 dataHash,
        bytes calldata signatures,
        uint256 requiredSignatures
    ) external view;
}

contract SignatureChecker {
    function check(
        address maybeSafe,
        address executor,
        bytes32 dataHash,
        bytes calldata signatures,
        uint256 requiredSignatures
    ) external view returns (bool ok) {
        // *Always* returns true when maybeSafe is NotSafe
        (ok, ) = maybeSafe.staticcall(
            abi.encodeWithSelector(
                ISafe.checkNSignatures.selector,
                executor,
                dataHash,
                signatures,
                requiredSignatures
            )
        );
    }
}
  1. Deploy NotSafe.
  2. Call SignatureChecker.check, pointing maybeSafe to the NotSafe address with arbitrary data.
  3. The call returns true even though no signature check occurred.

Expected Behaviour

checkNSignatures SHOULD provide an explicit return value, e.g.

function checkNSignatures(
    address executor,
    bytes32 dataHash,
    bytes calldata signatures,
    uint256 requiredSignatures
) external view returns (bool success);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions