サービス拒否のテスト (Testing for Denial of Service (DoS))

説明

サービス拒否 (DoS) 脆弱性は、スマートコントラクトまたはシステムが計算リソースを枯渇させたり、重要な操作をブロックすることで、ユーザーが利用できなくなる場合に発生します。この種の脆弱性は、非効率的な関数設計、過剰なガス使用、未処理の障害状態に起因する可能性があります。DoS 攻撃は、パフォーマンスの低下、操作の停止、ユーザーエクスペリエンスの低下につながる可能性があります。DoS 脆弱性を防ぐには、開発者はガス効率の高い操作を慎重に設計し、エラーを適切に処理し、リソース枯渇のリスクを緩和する必要があります。

例: ユーザー入力による無制限ループ

pragma solidity ^0.8.0;

contract GasDoSVulnerable {
    mapping(address => uint256) public balances;

    // Allows a user to send tokens to many recipients in one transaction.
    function bulkTransfer(address[] memory recipients, uint256[] memory amounts) public {
        require(recipients.length == amounts.length, "Invalid input");

        for (uint256 i = 0; i < recipients.length; i++) {
            require(amounts[i] > 0, "Amount must be greater than zero");
            balances[recipients[i]] += amounts[i];
        }
    }
}

影響

  • ガス制限の枯渇: 動的な入力サイズや最適化されていないロジックでのループに依存する関数は、過剰なガス消費につながる可能性があります。これは、トランザクションがブロックガス制限を超え、ガス不足エラーが発生し、完了に失敗する可能性があります。

  • トランザクションの失敗: ガス使用量やエラー状態を適切に処理しないと、コントラクトが予期せず失敗し、正当な操作を妨げ、ユーザーへのサービスを拒否する可能性があります。

  • リソース枯渇: 大規模なデータクエリや過度のループ反復など、リソース消費が適切に管理されていないと、システムを過負荷にし、計算リソースを枯渇して DoS を発生したり、コントラクトがそれ以上のやり取りに利用できなくなる可能性があります。

  • トランザクションコストの増加: ガスの使用効率が悪いと、トランザクション手数料が上昇し、ユーザーがコントラクトとやり取りすることを妨げたり、ネットワークの輻輳を引き起こすことにつながる可能性があります。

対策

効率的なループと関数の設計 (Efficient Loop and Function Design):

  • ループのある関数を最適化し、ガス消費量を削減して DoS 攻撃を防ぎます。ループは固定または最小限の入力サイズで動作するようにします。ループに大規模な動的データ配列を使用することは避けます。

  • burn() など、重要な関数が障害を適切に処理し、コントラクトが回復不能な状態にならないようにします。

  • ガス消費量を慎重に管理することでグリーフィング攻撃から保護し、操作が効率的に行われ、ガス制限にヒットしないようにします。

フォールバックメカニズム (Fallback Mechanisms):

  • 十分なガスでの try/catch ブロックを実装し、予期しない障害がコントラクトを不整合状態や応答不能状態にすることを防ぎます。

  • サイレントに失敗したり、コントラクトが停止状態になるなど、DoS 脆弱性を引き起こすことなくエラーをキャッチして処理するフォールバックメカニズムを設計します。

レート制限とリソース管理 (Rate Limiting and Resource Management):

  • DoS 攻撃につながる可能性のあるブロッキングメカニズムを避けます。たとえば、特に外部システムや大規模なデータセットを扱う場合、過剰なクエリや操作が効率的に処理されるようにします。

  • 大量のトランザクションにはレート制限やバッチ処理を使用し、システムの過負荷や過剰なリソース消費を防ぎます。

  • 外部関数呼び出しに対して効率的なエラー処理を実装し、未チェックの戻り値や外部とのやり取りの失敗によりコントラクトが失敗したり応答不能にならないようにします。

エラー処理 (Error Handling):

  • アサーションが DoS につながらないようにします。条件を慎重にチェックし、障害が適切に処理されるようにして、システム全体の可用性に影響を及ぼすリバートの発生を避けます。

  • コントラクトのロジックで考えられるすべての障害シナリオを考慮し、適切なフォールバックソリューションを提供することで、予期しないリバートによる DoS から保護します。

  • ERC165Checker.sol の supportsERC165InterfaceUnchecked() などの関数が大規模なデータクエリを効率的に処理し、リソース枯渇のリスクを最小限に抑えるようにします。


テスト 1: 無制限ループを検出する

目的

コントラクトコードでの無制限のループの使用は、過剰なガス使用に脆弱であるため、識別して緩和します。

脆弱なコード:

pragma solidity ^0.8.0;

contract UnboundedLoopExample {
    uint[] public data;

    function processAll() public {
        for (uint i = 0; i < data.length; i++) {
            // This operation scales with the size of the array
            data[i] = data[i] + 1;
        }
    }
}

なぜ脆弱なのか

ループは、無限に大きくなる可能性のある、data 配列全体を反復処理します。

  • 大規模な配列では、ガス消費量がブロックガス制限を超え、トランザクションが失敗する可能性があります。

  • 攻撃者は配列を埋めることでこれを悪用し、正当なユーザーがこの関数を使用できなくなる可能性があります。

チェック方法

  • コードレビュー: サイズ制約のない動的配列やマッピングに対して動作する for ループや while ループを探します。

  • 動的入力テスト: 大規模なデータセットで関数をテストし、ガス制限付近での動作をシミュレートします。

  • ドキュメントのレビュー: コントラクトがデータ増加と関数使用に関する制約を指定していることを確認します。

修正されたコード:

pragma solidity ^0.8.0;

contract BoundedLoopExample {
    uint[] public data;

    function processBatch(uint start, uint end) public {
        require(end <= data.length, "End index out of bounds");
        for (uint i = start; i < end; i++) {
            data[i] = data[i] + 1;
        }
    }
}

テスト 2: 非効率なネストされたループを特定する

目的

  • ガス消費量を指数関数的に増加する非効率なネストされたループを検出して対処します。

脆弱なコード:

pragma solidity ^0.8.0;

contract NestedLoopExample {
    uint[][] public matrix;

    function processMatrix() public {
        for (uint i = 0; i < matrix.length; i++) {
            for (uint j = 0; j < matrix[i].length; j++) {
                matrix[i][j] = matrix[i][j] * 2;
            }
        }
    }
}

なぜ脆弱なのか

ネストされたループは操作の複雑さを増し、入力サイズの増加に伴いガスコストの高騰につながります。

  • 大規模なデータセットはガス制限内で関数を使用できなくなり、DoS 状態を発生する可能性があります。

チェック方法

  • コードレビュー: コード内のネストされたループを調べ、入力サイズに対するガス消費量を評価します。

  • ガスプロファイリング: eth-gas-reporter などのツールを使用して、テスト時のガス使用量を解析します。

  • 動的テスト: 大きな matrix サイズでのシナリオをシミュレートし、ストレス下でのコントラクトの動作を観察します。

pragma solidity ^0.8.0;

contract OptimizedNestedLoopExample {
    uint[][] public matrix;

    function processMatrixBatch(uint startRow, uint endRow) public {
        require(endRow <= matrix.length, "End row exceeds matrix size");
        for (uint i = startRow; i < endRow; i++) {
            for (uint j = 0; j < matrix[i].length; j++) {
                matrix[i][j] = matrix[i][j] * 2;
            }
        }
    }
}

Last updated