安全审计中Try_Catch问题

  • SmileBits
  • 更新于 2024-05-24 16:35
  • 阅读 818

目前中项目的代码中经常发现使用Try/Catch来捕获错误,防止让程序revert,而是让它能继续执行下去。在使用过程中,容易出现的问题是,开发人员认为try/catch能捕获所有的异常错误。从而可以处理所有的异常情况。实际情况是有两类错误捕获不到:

  • 1 try调用的合约地址为非合约地址,co

目前中项目的代码中经常发现使用Try/Catch来捕获错误,防止让程序revert,而是让它能继续执行下去。Solidity文档例子如下。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.1;

interface DataFeed { function getData(address token) external returns (uint value); }

contract FeedConsumer {
    DataFeed feed;
    uint errorCount;
    function rate(address token) public returns (uint value, bool success) {
        // Permanently disable the mechanism if there are
        // more than 10 errors.
        require(errorCount < 10);
        try feed.getData(token) returns (uint v) {
            return (v, true);
        } catch Error(string memory /*reason*/) {
            // This is executed in case
            // revert was called inside getData
            // and a reason string was provided.
            errorCount++;
            return (0, false);
        } catch Panic(uint /*errorCode*/) {
            // This is executed in case of a panic,
            // i.e. a serious error like division by zero
            // or overflow. The error code can be used
            // to determine the kind of error.
            errorCount++;
            return (0, false);
        } catch (bytes memory /*lowLevelData*/) {
            // This is executed in case revert() was used.
            errorCount++;
            return (0, false);
        }
    }
}

在使用过程中,容易出现的问题是,开发人员认为try/catch能捕获所有的异常错误。从而可以处理所有的异常情况。实际情况是有两类错误捕获不到:

  • 1 try调用的合约地址为非合约地址,codesize为0
  • 2 函数返回值与期望的不一致(即没有按照调用要求的实现) 下面是测试代码: TryCatchDemo.sol
    
    // SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

// External contract used for try / catch examples

contract Foo {

address public owner;

constructor(address _owner) {

require(_owner != address(0), "invalid address");

assert(_owner != 0x0000000000000000000000000000000000000001);

owner = _owner;

}

function myFunc(uint256 x) public pure returns (string memory) {

require(x != 0, "require failed");

return "my func was called";

}

function divFunc(uint256 x) public pure{

uint256 y = 1/x;

}

}

contract Boo{

function myFunc(uint256 x) public pure {

require(x != 0, "require failed");

}

}

contract Bar {

event Log(string message);

event LogBytes(bytes data);

Foo public foo;

constructor() {

// This Foo contract is used for example of try catch with external call

foo = new Foo(msg.sender);

}

// Example of try / catch with external call

// tryCatchExternalCall(0) => Log("external call failed")

// tryCatchExternalCall(1) => Log("my func was called")

function tryCatchExternalCall(uint256 _i) public {

try foo.myFunc(_i) returns (string memory result) {

emit Log(result);

} catch {

emit Log("external call failed");

}

}

// Example of try / catch with contract creation

// tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address")

// tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("")

// tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created")

function tryCatchNewContract(address _owner) public {

try new Foo(_owner) returns (Foo foo) {

// you can use variable foo here

emit Log("Foo created");

} catch Error(string memory reason) {

// catch failing revert() and require()

emit Log(reason);

}catch Panic(uint errorCode){

//如果加了catch Panic(uint errorCode)则落入 failing assert, division by zero, invalid array access, arithmetic overflow and others,

emit Log("assert error");

}

catch (bytes memory reason) {

// catch failing assert()

emit LogBytes(reason);

}

}

function tryDiv(uint256 x) public {

try foo.divFunc(x) {

// you can use variable foo here

emit Log("div ok");

} catch Error(string memory reason) {

// catch failing revert() and require()

emit Log(reason);

}

catch Panic(uint errorCode){

//如果加了catch Panic(uint errorCode)则落入 failing assert, division by zero, invalid array access, arithmetic overflow and others,

emit Log("division by zero");

}

catch (bytes memory reason) {//其他的落入到这个catch里面,

// catch failing assert()

emit LogBytes(reason);

}

}

//ReturnDecodeError

function tryCatchExternalCallReturnDecodeError(address foo_address , uint256 _i) public {

try Foo(foo_address).myFunc(_i) returns (string memory result) {

emit Log(result);

} catch {

emit Log("external call failed");

}

}

//FunctionNotExit

function tryCatchExternalCallFunctionNotExit(address foo_address , uint256 _i) public {

try Foo(foo_address).divFunc(_i) {

emit Log("ok");

} catch (bytes memory reason) {//其他的落入到这个catch里面,

// catch failing assert()

emit LogBytes(reason);

}

}

}


TryCatchDemoTest.t.sol
```javascript
// SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

// External contract used for try / catch examples

contract Foo {

address public owner;

constructor(address _owner) {

require(_owner != address(0), "invalid address");

assert(_owner != 0x0000000000000000000000000000000000000001);

owner = _owner;

}

function myFunc(uint256 x) public pure returns (string memory) {

require(x != 0, "require failed");

return "my func was called";

}

function divFunc(uint256 x) public pure{

uint256 y = 1/x;

}

}

contract Boo{

function myFunc(uint256 x) public pure {

require(x != 0, "require failed");

}

}

contract Bar {

event Log(string message);

event LogBytes(bytes data);

Foo public foo;

constructor() {

// This Foo contract is used for example of try catch with external call

foo = new Foo(msg.sender);

}

// Example of try / catch with external call

// tryCatchExternalCall(0) => Log("external call failed")

// tryCatchExternalCall(1) => Log("my func was called")

function tryCatchExternalCall(uint256 _i) public {

try foo.myFunc(_i) returns (string memory result) {

emit Log(result);

} catch {

emit Log("external call failed");

}

}

// Example of try / catch with contract creation

// tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address")

// tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("")

// tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created")

function tryCatchNewContract(address _owner) public {

try new Foo(_owner) returns (Foo foo) {

// you can use variable foo here

emit Log("Foo created");

} catch Error(string memory reason) {

// catch failing revert() and require()

emit Log(reason);

}catch Panic(uint errorCode){

//如果加了catch Panic(uint errorCode)则落入 failing assert, division by zero, invalid array access, arithmetic overflow and others,

emit Log("assert error");

}

catch (bytes memory reason) {

// catch failing assert()

emit LogBytes(reason);

}

}

function tryDiv(uint256 x) public {

try foo.divFunc(x) {

// you can use variable foo here

emit Log("div ok");

} catch Error(string memory reason) {

// catch failing revert() and require()

emit Log(reason);

}

catch Panic(uint errorCode){

//如果加了catch Panic(uint errorCode)则落入 failing assert, division by zero, invalid array access, arithmetic overflow and others,

emit Log("division by zero");

}

catch (bytes memory reason) {//其他的落入到这个catch里面,

// catch failing assert()

emit LogBytes(reason);

}

}

//ReturnDecodeError

function tryCatchExternalCallReturnDecodeError(address foo_address , uint256 _i) public {

try Foo(foo_address).myFunc(_i) returns (string memory result) {

emit Log(result);

} catch {

emit Log("external call failed");

}

}

//FunctionNotExit

function tryCatchExternalCallFunctionNotExit(address foo_address , uint256 _i) public {

try Foo(foo_address).divFunc(_i) {

emit Log("ok");

} catch (bytes memory reason) {//其他的落入到这个catch里面,

// catch failing assert()

emit LogBytes(reason);

}

}

}

类似的错误。在审计中发现具有不少的价值。例如最近审计的一个项目,该问题获得了2000u的奖励: https://github.com/sherlock-audit/2024-04-teller-finance/blob/defe55469a2576735af67483acf31d623e13592d/teller-protocol-v2-audit-2024/packages/contracts/contracts/TellerV2.sol#L953

点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
SmileBits
SmileBits
智能合约安全审计