Back to Blog

[バタフライ効果] バグ修正が引き起こした複合セキュリティインシデント

Code Auditing
October 10, 2021
8 min read

BlockSecチーム(@BlockSecTeam)より

先週、Compoundプロトコルでは、多数のCOMPトークンが「誤って」ユーザーに送信されるバグが発生しました。このバグ(本ブログのバグ2)の原因は、以前発見された別のバグ(本ブログのバグ1)の修正が不適切だったことにあります。

本ブログでは、最初のバグの根本原因と、その修正が2番目のバグを引き起こした理由を詳しく説明します。

背景

CompoundプロトコルはCompoundホワイトペーパーに基づいています。cTokenコントラクトを通じて、ブロックチェーン上のアカウントは、イーサまたはERC-20トークンをプロトコルに供給してcTokenを受け取るか、他の資産を担保としてプロトコルから資産を借り入れます。Compound cTokenコントラクトは、これらの残高を追跡し、借り手への金利をアルゴリズムで設定します

ユーザーをインセンティブ化するため、Compoundに流動性を提供するユーザー(資本を供給する)は利息を受け取ることができます。具体的には、ユーザーはCompoundに資産(イーサまたは他のERC20トークンなど)を供給し、対応するcTokenを受け取ります。ユーザーがCompoundに借入がない場合、cTokenがCompoundに返却されると、元本資産(イーサまたはERC20トークン)と利息がユーザーに返還されます。例えば、ユーザーが1000イーサを持っている場合、cEth.mint(1000)を通じてCompoundに資産を預け入れることでcTokenを取得できます。

cTokenは、Compoundにロックされた元本資産を表します。ユーザーはさらに、cTokenを担保として他の資産を借り入れることができます。例えば、ユーザーはceth.mint(1000)を通じて1000イーサを預け入れ、その後、取得したcTokenを使用して、75イーサ相当のxDaiを借り入れることができます(過剰担保 - この数値は担保率によって異なります)。cDai.borrow(x)を通じて行われます。

コアロジックは Comptroller コントラクトで実装されています。これは、ユーザーのステータス、例えばユーザーがCompoundにいくらトークンを預け入れたか、いくらトークンを借り入れたか、そしてユーザーがさらにトークンを借り入れられるかどうかを管理します。このプロセスで呼び出される関数には、getHypotheticalAccountLiquidityInternal()borrowAllowed()mintAllowed() などがあります。

Compoundには COMPというガバナンストークンもあります。COMPトークンは提案に投票するために使用できます。さらに、COMPトークンは取引所で取引できます。現在、COMPの価格は約300ドルです。

バグ1

2021年9月31日、Compound DAOに新しい提案(提案62)があり、Comptrollerのバグを修正することを目的としていました。

このバグは、各ブロックでユーザーに配布されるCOMPトークンの数を示す CompSpeed に関連していました。

mint 関数のフロー

以下では、mint 関数を使用してこのバグの原因を説明します。mint 関数の呼び出しチェーンは、mintmintInternalmintFresh です。

mintFresh 関数では、mintAllowed を呼び出し、その後cTokenのユーザー残高を更新します。

mintAllowed 関数では、まずupdateCompSupplyIndex を呼び出し、次に distributeSupplierComp を呼び出して、1) 市場の compSupplyState を更新し、2) COMPトークンをユーザーに配布します。

updateCompSupplyIndex

updateCompSupplyIndex 関数は、各市場のステータス、特に compSupplyState[cToken] を更新します。

CompMarketState 構造体には、この更新のブロック番号 (block) と、ユーザー(cTokenを保有するユーザー)に配布されるべきCOMPトークンの数に影響するボーナスインデックス (index) が記録されています。

各トークンのボーナスインデックス(index)とは何ですか? これは、累積された値です(次の式に示す通り)。

これは、ユーザーに配布されるべきCOMPの数(ユーザーが保有する各cTokenごと)を示しています。

distributeSupplierComp

もう一つの関数 distributeSupplierComp は、ユーザー(サプライヤー)に配布されるべきCOMPトークンの数を compAccrued[supplier] に記録する責任があります。

具体的には、updateCompSupplyIndex 関数でグローバルボーナスインデックスを compSupplyState で更新します。その後、distributeSupplierComp 関数では、supplyIndex が現在のボーナスインデックスを記録し、supplierIndex がユーザー(サプライヤー)の最後のボーナスインデックスを示します。 (supplyIndex - supplierIndex) * ユーザーのcToken残高 という差分値が、ユーザーに配布されるべきCOMPトークンの数を示します。

バグ1の原因

市場の supplySpeedcompSpeeds[address[cToken]])を調整するための setCompSpeed という別の関数があります。

これは、市場の CompSpeed をゼロに設定すると、その市場ではCOMPトークンがユーザーに配布されないことを意味するためです。したがって、まずCOMPの配布を無効にしてから再度有効にしたい場合は、次の手順に従うことができます。

  • ステップI: CompSpeed[cToken] をゼロに設定して、COMPトークンの配布を無効にします。
  • ステップII: setCompSpeed 関数を呼び出して、CompSpeed[cToken] をゼロ以外の値に設定します。

ステップI: ステップIでCOMPトークンの配布が無効にされた市場(supplySpeed == 0)では、ブロックはゼロではありません。なぜなら、updateCompSupplyIndex (else if (deltaBlocks > 0)) でブロックは継続的に更新されるためです。

ステップII: ステップIIの操作を実行すると、setCompSpeedInternal 関数は else if (compSpeed != 0) ステートメント(1083行目)を通過します。次に、1088行目から1093行目では、if (compSupplyState[address(cToken)].index == 0 && compSupplyState[address(cToken)].block == 0) というチェックがあり、これは新しい市場indexblock を初期化します。しかし、既存の市場(新しい市場ではない)でCOMPトークンの配布を再有効化しているため、1090行目と1091行目のステートメントは実行されず、indexblock は初期化されません(compSupplyState[address(cToken)].block はゼロではないため)。

要約すると、現在無効になっている市場では、index はゼロですが、block はゼロではありません。これは、CompSpeed[cToken] をゼロ以外の値に設定するために setCompSpeed を呼び出して無効になった市場を再有効化すると、index 値が CompInitialIndex (1e36) に再初期化されない(1090行目と1091行目は実行されない)ことを意味します。

バグ1の影響

次に、COMPトークンの配布を担当する distributeSupplierComp 関数をさらに詳しく見ていきます。

supplierIndexcompInitialIndex です。しかし、バグのために supplyIndex はゼロのままです。これにより、 Double memory deltaIndex = sub_(supplyIndex=0, supplierIndex=1e36) のアンダーフローが発生します。

バグ2:バグ1の修正によって導入されたもの

バグを修正するために、プロジェクトオーナーはコードロジックを変更しました。具体的には、新しい市場を初期化する際に index を直ちに compInitialIndex に初期化します。

グローバルボーナスインデックス (index) が compInitialIndex に初期化されたため、ユーザーボーナスインデックスもこの値に初期化されるべきです。 distributeSupplierComp 関数を見てみましょう。

1234行目のif条件は、supplierIndex == 0 の場合でも満たされません。なぜなら、supplyIndexcompInitialIndex (1e36) と等しい(それより大きくない)からです。これにより、 supplierIndexcompInitialIndex に正しく初期化されず(値は0のまま)、 deltaIndex (supplyIndex - supplierIndex) はゼロではなく compInitialIndex になります。ユーザーのcToken残高がゼロでない場合、 supplierTokens は大きな値になります。

要約すると、バグ1の修正前にユーザーが偶然 mint 操作を実行した場合、そのユーザーはcTokenを保有しており、COMPトークンは配布されているため supplierIndex はゼロになります。その後、バグ1の修正(バグ2を導入)が行われた後、ユーザーが再度 mint 関数を呼び出すと、大量のCOMPトークン(1e36 * ctoken.balanceOf(user))を受け取ることができます。

実例

影響を受けた市場を以下に示します。

0xF5DCe57282A584D2746FaF1593d3121Fcac444dC: cSAI
0x12392F67bdf24faE0AF363c24aC620a2f67DAd86: cTUSD
0x95b4eF2869eBD94BEb4eEE400a99824BF5DC325b: cMKR
0x4B0181102A0112A2ef11AbEE5563bb4a3176c9d7: cSUSHI
0xe65cdB6479BaC1e22340E4E755fAE7E509EcD06c: cAAVE
0x80a2AE356fc9ef4305676f7a3E2Ed04e12C33946: cYFI

ユーザー(0xa7b95d2a2d10028cc4450e453151181cbcac74fc)は、このトランザクション(0x6416ed016c39ffa23694a70d8a386c613f005be18aa0048ded8094f6165e7308)で 4,466.542459954989867175 COMPトークンを受け取りました。

トランザクションのさらなるデバッグにより、バグ2により deltaIndex が1e36になり、ユーザーがその時に偶然cTokenを保有していたことが判明しました。

バグ2の修正

バグ2の修正は簡単です。 distributeSupplierComp 関数内のif条件を変更しました。

学び

  • これは別のバグの修正によって引き起こされたバグです。影響力の大きいプロジェクトのコード変更を徹底的にレビューする方法は、依然として未解決の問題です。
  • DAOは中央集権化のリスクを排除できます。しかし、セキュリティインシデントへの対応プロセスを遅くする可能性もあります。
  • 影響力の大きいDeFiプロジェクトは、従来のプログラムにおける優れたセキュリティプラクティスを採用できます。例えば、継続的なテストプロセスを備えた効率的なファジングシステムを導入することなどです。

BlockSecについて

BlockSecは、2021年に世界的に著名なセキュリティ専門家グループによって設立された、先駆的なブロックチェーンセキュリティ企業です。同社は、新興のWeb3世界におけるセキュリティとユーザビリティの向上に尽力しており、その大量採用を促進しています。この目的のため、BlockSecはスマートコントラクトとEVMチェーンのセキュリティ監査サービス、セキュリティ開発と脅威のプロアクティブなブロックのためのPhalconプラットフォーム、資金追跡と調査のためのMetaSleuthプラットフォーム、そしてWeb3ビルダーが仮想通貨の世界を効率的にサーフィンするためのMetaSuites拡張機能を提供しています。

現在までに、同社はMetaMask、Uniswap Foundation、Compound、Forta、PancakeSwapなど300社以上の著名なクライアントにサービスを提供しており、Matrix Partners、Vitalbridge Capital、Fenbushi Capitalなどの著名な投資家から2回の資金調達ラウンドで数千万米ドルを獲得しています。

公式ウェブサイト: https://blocksec.com/

公式Twitterアカウント: https://twitter.com/BlockSecTeam

Sign up for the latest updates
The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis
Security Insights

The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis

This BlockSec deep-dive analyzes the KelpDAO $290M rsETH cross-chain bridge exploit (April 18, 2026), attributed to the Lazarus Group, tracing a causal chain across three layers: how a single-point DVN dependency enabled the attack, how DeFi composability cascaded the damage through Aave V3 lending markets to freeze WETH liquidity exceeding $6.7B across Ethereum, Arbitrum, Base, Mantle, and Linea, and how the crisis forced decentralized governance to exercise centralized emergency powers. The article examines three parameters that shaped the cascade's severity (LTV, pool depth, and cross-chain deployment count) and provides an exclusive technical breakdown of Arbitrum Security Council's forced state transition, an atomic contract upgrade that moved 30,766 ETH without the holder's signature.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026

This BlockSec weekly security report covers four DeFi attack incidents detected between April 6 and April 12, 2026, across Linea, BNB Chain, Arbitrum, Optimism, Avalanche, and Base, with total estimated losses of approximately $928.6K. Notable incidents include a $517K approval-related exploit where a user mistakenly approved a permissionless SquidMulticall contract enabling arbitrary external calls, a $193K business logic flaw in the HB token's reward-settlement logic that allowed direct AMM reserve manipulation, a $165.6K exploit in Denaria's perpetual DEX caused by a rounding asymmetry compounded with an unsafe cast, and a $53K access control issue in XBITVault caused by an initialization-dependent check that failed open. The report provides detailed vulnerability analysis and attack transaction breakdowns for each incident.

Best Security Auditor for Web3

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

BlockSec Audit