code4rena-2023-10-brahma-m04

[M-04] Module transactions will always fail because incompatible with Safe 1.5.0

보고서

Summary

Safe 월렛 1.5 버전 이상에서는 가드 컨트랙트의 checkModuleTransaction 함수가 반드시 bytes32를 리턴해야 한다. 하지만 이 인터페이스를 지키지 않아 1.5 버전 이상에서 ExecutorPlugin.executeTransaction의 호출이 항상 실패한다.

Keyword

gnosis safe, wallet, version, integration, interface, return value

Vulnerability

SafeModerator 와 SafeModeratorOverridable 컨트랙트는 Safe 월렛의 가드로 등록된다. Safe 월렛 1.5 버전부터는 가드에 checkModuleTransaction 함수를 추가해야 한다. 이 함수는 모듈로부터 트랜잭션 요청 시 추가적인 확인을 하는 함수로, 이 프로토콜에서는 빈 함수로 구현되었다.

    /**
     * @notice Inherited from IGuard, function is called after executing a Safe transaction during execTransactionViaModule
     * @dev No op-check. Provides compatibility with Safe 1.5 guard over module
     */
    /* solhint-disable no-empty-blocks */
    function checkModuleTransaction(
        address, /* to */
        uint256, /* value */
        bytes memory, /* data */
        Enum.Operation, /* operation */
        address /* module */
@>  ) external override {}

하지만 이를 잘못 구현하였다. 정상적인 checkModuleTransaction 함수bytes32 moduleTxHash를 리턴해야 한다. 하지만 구현에서는 아무것도 리턴하지 않는다.

checkModuleTransaction 함수가 아무 작업도 하지 않지만, 인터페이스가 맞지 않으므로 checkModuleTransaction 함수 콜은 실패할 것이다. 다음은 checkModuleTransaction 함수를 호출하는 Safe.execTransactionFromModule 코드이다. 역시 checkModuleTransaction의 리턴값을 기대하는 것을 볼 수 있다.

    function execTransactionFromModule(
        address to,
        uint256 value,
        bytes memory data,
        Enum.Operation operation
    ) public virtual returns (bool success) {
        // Only whitelisted modules are allowed.
        require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104");
        // Execute transaction without further confirmations.
        address guard = getGuard();
 
        bytes32 guardHash;
        if (guard != address(0)) {
@>          guardHash = Guard(guard).checkModuleTransaction(to, value, data, operation, msg.sender);
        }
        ...
    }

checkModuleTransaction 함수는 Safe.execTransactionFromModule 로부터 호출되며, Safe.execTransactionFromModuleExecutorPlugin.executeTransaction 로부터 호출된다. 따라서 Safe 월렛 1.5 버전 이상에서는 ExecutorPlugin.executeTransaction의 호출이 항상 실패한다.

Impact

Safe 월렛 1.5 버전 이상에서는 ExecutorPlugin.executeTransaction의 호출이 항상 실패한다.

Mitigation

    function checkModuleTransaction(
        address, /* to */
        uint256, /* value */
        bytes memory, /* data */
        Enum.Operation, /* operation */
        address /* module */
-   ) external override {}
+   ) external override returns (bytes32){
+      return bytes32(0); // or return real moduleTxHash
+}

tags: bughunting, brahma, smart contract, solidity, gnosis safe, wallet, version, integration, solidity interface, severity medium