Back to Blog

EVM互換性とセキュリティを維持するための体系的アプローチ

Code Auditing
March 27, 2023
8 min read

EVM(Ethereum Virtual Machine)互換ブロックチェーンは、イーサリアムブロックチェーンのスマートコントラクト機能、プログラミング言語(Solidity)、およびツールエコシステムと互換性を持つように設計されています。このプロセスにおいて、EVM準拠の仮想マシンの実装は重要なステップの一つです。しかし、私たちの調査の結果、異なる実装間でEVMの互換性を維持することは容易ではないことがわかりました。

この問題を解決するために、BlockSecはEVM実装内のバグやセキュリティ脆弱性を体系的に特定できる内部システムを開発しました。このシステムは有効であることが証明されています。Aurora Engineで4つのバグ、Moonbeamで4つのバグがこのシステムによって発見されました。すべて報告され、修正済みです。なお、このテスト手法は昨年、Solanaのrbpf実装における2つの重大な脆弱性(CVE-2021–46102CVE-2022–23066)の特定にも使用されました。

1. 背景

今日、低いガス代、高いスループット、優れたパフォーマンスを最適化する多くの異なるブロックチェーンが提案されています。これに伴い、新しい仮想マシンや開発言語も提案されており、それが既存のSolidity開発者にとって参入障壁となっています。こうした状況下で、Solidityで開発されたDAppsを新しいチェーンにデプロイできる、多くのEVM互換ソリューションが提案されています。AuroraやMoonbeamは、これらのEVM互換ソリューションの代表例です。しかし、これらのEVM実装の堅牢性、信頼性、正確性は未知数であり、私たちの注意を払う価値があります。このため、私たちは差分ファジング手法(differential fuzzing technique)を活用し、実装に欠陥がないかを確認することにしました。

2. 差分ファジング

基本的な考え方は、同一の入力をこれらのEVM実装(例:Aurora、Moonbeam)と最先端のイーサリアムクライアント(例:geth)に与え、出力が同じかどうかを確認することです。具体的には、イーサリアムの過去のトランザクションを収集し、コントラクトコードやトランザクションの状態をさまざまな戦略でミューテーション(突然変異)させることでテストケースを生成します。調査の結果、Aurora EngineとMoonbeamは一部のケースで仕様に準拠していないことが判明しました。幸い、報告されたすべての問題は対処済みであり、詳細を共有します。

3. 発見されたバグ

これらのバグのほとんどはプリコンパイルコントラクトにあり、その根本原因と影響はさまざまです。例えば、一部のバグはnonce値の計算に影響を与える可能性があり、他のバグはガス計算に影響を与え、さらにはDoS攻撃につながる可能性があります。発見されたすべてのバグは、EVM仕様の指定された実行ロジックに反しており、特定のケースで予期しない動作を引き起こす可能性があります。発見されたバグの詳細な説明は以下の通りです。

3.1 不適切な検証

暗号ロジックは複雑です。そのため、EVMバイトコードで対応するロジックを実装すると、かなりのガス消費量になる可能性があります。代わりに、ネイティブコードで開発されたプリコンパイルコントラクトを使用することで、パフォーマンスを向上させることができます。しかし、私たちは不適切な検証によるバグをプリコンパイルコントラクト内にいくつか発見しました。

Aurora Engine: ecPairing

このバグはプリコンパイルコントラクト ecPairing で発見されました。

ecPairing の入力は、2つの楕円曲線上の複数の点で構成されています。仕様によると、点 (0, 0) は両方の曲線上にあり、有効な入力であるはずです。

しかし、Aurora Engine(バージョン2.7.0)は、入力に点 (0,0) が含まれているとリバート(revert)してしまいます。

関連するPRはこちらです。

Moonbeam: ecMul

このバグはプリコンパイルコントラクト ecMul にあります。Aurora Engineとは異なり、Moonbeamは入力が有効であってもトランザクションをリバートします。仕様によると、プリコンパイルコントラクト ecMul は、入力長が64未満の場合、ゼロでパディングする必要があります。しかし、Moonbeamはパディング操作を行わずにリバートしてしまいます。

また、プリコンパイルコントラクト ecAddmodexp にも同じ問題があることが判明しました。

Moonbeam: ecRecover

このバグは、イーサリアムアドレスの復元に使用されるプリコンパイルコントラクト ecRecover にあります。

仕様によると、input[32..63]v はU256識別子を表し、27または28であることが期待されています。それ以外の場合、ecRecover は何も返さない(ただし、トランザクション全体がリバートされるべきではない)ことになっています。

しかし、Moonbeamはここで2つの間違いを犯しています。

  • input[32..63] をU256型に変換してから値をチェックするのではなく、input[63] のみをチェックしている。
  • 識別子が0または1の場合に入力が有効とみなされる(27または28であるべき)。

Moonbeam: ecPairing

このバグはプリコンパイルコントラクト ecPairing にあります。Moonbeamは入力が無効な場合にトランザクションをリバートしません。仕様によると、プリコンパイルコントラクト ecPairing の入力は192の倍数である必要があります。そうでない場合、トランザクションはリバートされるべきです。

しかし、Moonbeamは上記の要件を満たさない場合でもトランザクションをリバートしません。

3.2 不正確なガス計算

各プリコンパイルコントラクトには、ガス消費量を判断するためのアルゴリズムがあります。不正確なガス計算はDoS攻撃につながる可能性があります。

しかし、私たちはAurora EngineとMoonbeamの両方で、ガス計算が不正確であるというバグを2つ発見しました。

Aurora Engine: modexp

このバグはプリコンパイルコントラクト modexp にあります。ガス消費量を計算するアルゴリズムは EIP-2565 によって定義されています。ガス消費量は、反復回数に関連しています。

反復回数を計算するアルゴリズムは以下の通りです。

def calculate_iteration_count(exponent_length, exponent):
   iteration_count = 0
   if exponent_length <= 32 and exponent == 0: iteration_count = 0
   elif exponent_length <= 32: iteration_count = exponent.bit_length() - 1
   elif exponent_length > 32: iteration_count = (8 * (exponent_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1)
   return max(iteration_count, 1)

上記のアルゴリズムによれば、反復回数は少なくとも1回です。しかし、Aurora Engineは max(iteration_count, 1) ではなく、iteration_count を直接返します。この場合、返される値(つまり iteration_count)が0になる可能性があり、特定のケースで予期されるよりもかなり少ないガス代を請求することになります。

関連するIssueリンクはこちらです。

Moonbeam: modexp

このバグもプリコンパイルコントラクト modexpcalculate_iteration_count 関数にあります。しかし、こちらはMoonbeamで発見されました。

exponent_length が32より大きい場合、iteration_count は以下のアルゴリズムで計算されます。

(8 * (exponent_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1)

これは exponent & (2**256 - 1) を使用していることに注意してください。これは exponent下位32バイトを取得するものであり、Moonbeamの実装はこのアルゴリズムに従っています。

しかし、仕様によると、ガス計算の式は exponent上位32バイトを使用する必要があります。

3.3 Nonceインクリメントのバグ

外部所有アカウント(EOA)のnonceは、そのアドレスによって署名された成功したトランザクションの数を示します。しかし、Aurora Engine上で無効なトランザクションを送信することでnonceが増加してしまうことに気づきました。

EIP-1559 によると、トランザクションを実行する前に、EVMは署名者が転送されるネイティブトークン(例:ETH)と必要なガスをカバーする十分な残高を持っていることを確認する必要があります。そうでない場合、トランザクションは破棄され、署名者のnonceは増加すべきではありません。

Aurora Engineはこの状況でトランザクションを破棄しますが、それでも署名者のnonceを増加させてしまっています。

関連するPRはこちらです。

3.4 不適切なオペコード実装

このバグは、特定のオペコード(つまり PUSH)の実装に関するものです。PUSHオペコードに続くバイトが不完全な場合、右寄せされるべきです。例えば、バイトコード 0x64ffff は次のようにデコードされます。

PUSH5 0xffff

オペランドは右寄せされるべきであるため、値 0xffff000000 がスタックにプッシュされるはずです。しかし、Aurora EngineとMoonbeamの両方のEVM実装は、代わりに 0xffff をプッシュしており、これは誤りです。

関連するPRはこちらです。

4. 私たちのサービス

BlockSecでは、異なるブロックチェーン実装全体でEVMの互換性とセキュリティを維持することの重要性を理解しています。そのため、EVM実装の脆弱性を容易に特定できる、バグ検出への体系的なアプローチを開発しました。

私たちの内部システムは、EVM実装におけるバグやセキュリティ脆弱性の特定に極めて効果的であることが証明されています。Aurora Engineで4つ、Moonbeamで4つのバグを正常に報告し、修正しました。さらに、私たちのテスト手法は昨年、Solanaのrbpf実装における2つの重大な脆弱性(CVE-2021–46102、CVE-2022–23066)の特定にも使用されており、私たちのアプローチの有効性が際立っています。

私たちの体系的なバグ検出アプローチを導入することで、クライアントは自身のEVM実装が安全で信頼できるものであると確信できます。私たちはEVMの互換性とセキュリティのためのトップクラスのソリューションを提供することに尽力しており、クライアントがユーザーやステークホルダーとの信頼を築く一助となれるよう努めています。

BlockSecについて

BlockSecチームはブロックチェーンエコシステムのセキュリティに焦点を当てており、主要なDeFiプロジェクトと協力してそのプロダクトのセキュリティを守っています。チームは、学界と産業界双方のトップクラスのセキュリティ研究者および経験豊富な専門家によって設立されました。彼らは権威ある会議で複数のブロックチェーンセキュリティ論文を発表し、DeFiアプリケーションのゼロデイ攻撃を報告し、影響の大きいセキュリティインシデントの詳細な分析レポートを公開しています。

公式サイト | Twitter | Medium

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit