Node.jsアプリに潜む!? libp2p GossipsubのCPU DoS脆弱性 (CVE-2026-49866) を徹底解説
はじめに
Web3やP2P技術に関心のある日本のフロントエンドエンジニアの皆さん、こんにちは!Node.js環境で分散アプリケーションを開発する際、基盤となるライブラリのセキュリティは非常に重要です。今回、`@libp2p/gossipsub`に重大なCPU DoS脆弱性(CVE-2026-49866)が報告されました。この記事では、この脆弱性の詳細、攻撃メカニズム、そして対策について技術的に深く掘り下げて解説します。あなたのアプリケーションが影響を受けないよう、ぜひ最後までお読みください。
脆弱性の概要
この脆弱性は、`libp2p`のPubSub実装である`@libp2p/gossipsub`ライブラリにおいて、特定のコントロールメッセージ(`IHAVE`および`IWANT`)の処理方法に起因するものです。攻撃者は、過度に大きなメッセージID配列を含むこれらのメッセージを送信することで、ターゲットノードのCPUリソースを著しく消費させ、結果としてNode.jsのイベントループを長時間ブロックさせることができます。
深刻度は「高 (High)」と評価されており、サービス拒否(DoS)攻撃によってアプリケーションの可用性が失われる可能性があります。
技術的な詳細: なぜイベントループがブロックされるのか?
`Gossipsub`プロトコルでは、ノード間でメッセージの存在を知らせる`IHAVE`メッセージや、不足しているメッセージを要求する`IWANT`メッセージがやり取りされます。問題は、これらのメッセージに含まれるメッセージIDの配列サイズに、デコード時に上限が設定されていないことにあります。
デフォルトの`decodeRpcLimits`には`maxIhaveMessageIDs: Infinity`や`maxIwantMessageIDs: Infinity`が設定されており、無限大のID数を受け入れてしまいます。加えて、`libp2p`の通信層で使用されるLP (Length-Prefixed) フレームの最大サイズは4MBです。この4MBのフレームには、約180,000個ものメッセージIDを詰め込むことが可能です。
`Gossipsub`は、受信したこれらのメッセージIDを、実際の処理を行う前に**同期的に**反復処理します。180,000個ものIDを同期的に処理すると、Node.jsのイベントループが約200msもの間ブロックされます。ご存知の通り、Node.jsはシングルスレッドでイベントループに依存しているため、この同期的なブロックは他のすべての処理を停止させてしまいます。
攻撃経路とその影響
この脆弱性には、主に2つの攻撃経路が存在します。
攻撃者は約10個の「Sybilピア」(偽装された多数のピア)を立ち上げ、それぞれがターゲットノードに接続します。各Sybilピアは、ハートビートごとに約180,000個のランダムなメッセージIDを含む4MBの`IHAVE`メッセージを1つ送信します。
ターゲットノードは、各ピアから受信した180,000個のIDを、そのピアに対するレート制限が適用される前に同期的に処理してしまいます。これにより、10個のピアからの攻撃で、1000msのハートビート間隔に対し、合計で約1500ms(10ピア × 150ms)ものイベントループブロックが発生します。これは、ノードが正常に機能できない状態を意味します。
`IWANT`メッセージの処理には、`IHAVE`のようなピアごとのレート制限が**一切ありません**。このため、攻撃者は単一のピアから、約180,000個のメッセージIDを含む4MBの`IWANT`メッセージを継続的にストリーミングできます。
データセンターレベルの帯域幅(1Gbps)では、約32msごとに4MBフレームが到着し、それを処理するのに約135msかかります。これにより、イベントループの80%以上が継続的に占有され、事実上、ノードの機能を完全に麻痺させることが可能です。
影響を受けるシステム
デフォルト設定で`@libp2p/gossipsub`を使用し、外部からのインバウンド接続を受け入れるすべてのNode.jsアプリケーションがこの脆弱性の影響を受けます。具体的には以下のようなプロジェクトが該当する可能性があります。
逆に、`opts.decodeRpcLimits`を明示的に有限な値で設定しているノードは影響を受けません。
推奨される対策
この脆弱性への最も効果的な対策は、`Gossipsub`のRPCデコード制限を明示的に設定することです。具体的には、`decodeRpc.ts`ファイル内の`defaultDecodeRpcLimits`に有限の値を設定する必要があります。
開発者は、`gossipsub()`を初期化する際に、`decodeRpcLimits`オプションを通じてこれらの制限を上書きすることが推奨されます。例えば、`maxIhaveMessageIDs`と`maxIwantMessageIDs`を`GossipsubMaxIHaveLength`と同じ`5,000`に設定することで、メッセージIDの反復処理コストを攻撃者の入力ではなく、内部の応答制限に合わせることができます。
以下は、推奨される`decodeRpcLimits`のデフォルト値の例です。`gossipsub`をインスタンス化する際に、以下のように`decodeRpcLimits`を渡します。
```typescript import { gossipsub } from '@libp2p/gossipsub' const customDecodeRpcLimits = { maxSubscriptions: 128, maxMessages: 256, maxIhaveMessageIDs: 5_000, // 推奨される上限 maxIwantMessageIDs: 5_000, // 推奨される上限 maxIdontwantMessageIDs: 5_000, maxControlMessages: 128, maxPeerInfos: 16 }; const libp2p = await createLibp2p({ // ... 他の設定 services: { pubsub: gossipsub({ decodeRpcLimits: customDecodeRpcLimits }) } }); ```
**重要**: `libp2p`のバージョンを最新に保ち、修正が適用されたリリースにアップデートすることも極めて重要です。
まとめ
`libp2p`の`@libp2p/gossipsub`におけるCPU DoS脆弱性(CVE-2026-49866)は、Node.jsベースの分散アプリケーションに深刻な影響を与える可能性があります。特にイベントループのブロックは、アプリケーションの応答性を著しく低下させ、サービス停止へと繋がります。
適切な`decodeRpcLimits`を設定することで、この種の攻撃からノードを保護できます。Web3やP2P分野に携わるフロントエンドエンジニアの皆さんには、使用しているライブラリのセキュリティ情報を常にチェックし、適切な対策を講じることを強くお勧めします。