
tough のバージョン 0.20.0 より前の問題(複数の CVE)
2025年3月、AWS LabsのToughライブラリ(TUF(The Update Framework)のRustクライアント実装)において複数のセキュリティ脆弱性が公開されました。これらの問題はCVE-2025-2885、CVE-2025-2886、CVE-2025-2887、CVE-2025-2888として追跡されており、Toughバージョン0.20.0で修正されました。セキュリティエンジニアはこのドキュメントを使用して、各脆弱性の技術的詳細、コードベース内の根本原因、およびそれらを解決するパッチを理解できます。Tough < 0.20.0のすべてのユーザーは、v0.20.0以降にアップグレードすることを強くお勧めします。
Toughは更新中にRoot metadataのバージョンを順番に検証できませんでした。リポジトリを制御する攻撃者(または中間者攻撃が可能な攻撃者)は、予期しないバージョン番号のルートメタデータファイルを供給し、クライアントに誤ったバージョンを取得・信頼させることができました。要するに、Toughは新しいルートメタデータのバージョンが、以前に信頼されたバージョンのちょうど1つ後であることを保証していませんでした。これはTUFの継続的な信頼の連鎖という要件に違反します。これによりロールバック攻撃またはミックスアンドマッチ攻撃が発生する可能性があり、古い(しかし正しく署名された)ルートが最新であるかのように受け入れられ、廃止された署名鍵や失効した信頼が再導入される可能性があります。
tough/src/lib.rs内のToughのルートメタデータ更新ロジック。脆弱な実装は、新しいルートのバージョンが古いルートのバージョン以上であることだけをチェックし、次の順序のバージョンであることを要求していませんでした。この欠落により、クライアントが中間のルートバージョンを順番どおりにダウンロードしなければならないというTUF仕様の規則(バージョンスキップの禁止)に違反します。脆弱な実装: バージョン0.19.x以前では、新しいroot.jsonをダウンロードした後、Toughは署名を検証しましたが、バージョンの連続性を厳密に強制していませんでした。Toughは新しいルートバージョンが信頼済みバージョン以上の任意の値であることを許可していました。たとえば、現在信頼されているルートがバージョンNの場合、ToughはバージョンN+1をスキップして、N+2以上のバージョンを主張する新しいルートを(署名が有効である限り)受け入れていました。以下のコードスニペットは、修正前のチェックを示しています。```rust
// (Prior to fix) Allow new root version to be >= old version – too permissive
ensure!(root.signed.version <= new_root.signed.version, error::OlderMetadataSnafu {
role: RoleType::Root,
current_version: root.signed.version,
new_version: new_root.signed.version
});
攻撃者は、<b>より新しいバージョンのルートメタデータでありながら、実際には古い鍵セット</b>を提示することでこれを悪用する可能性があります。Toughはそれを更新だと思って受け入れ、[古い鍵で署名されたコンテンツを信頼](https://github.com/advisories/GHSA-5vmp-m5v2-hx47#:~:text=The%20tough%20client%20will%20trust,with%20a%20previous%20root%20role)してしまうでしょう。
<b>修正済みの実装:</b> パッチ適用済みコード(Tough 0.20.0)では、新しいルートのバージョンが古いバージョンより正確に1つだけ大きいことを明示的に要求します([コミット 0eeb60a](https://github.com/awslabs/tough/commit/0eeb60aefe27f00b65730634b788a1aafb8bf3c6) を参照)。また、新しいバージョンがより高いことを保証し(等しい場合や減少を防ぐ)、+1 を超えるジャンプを防ぎます。```rust
// (Fixed in v0.20.0) Enforce sequential root version update (new_version == old_version + 1)
ensure!(
root.signed.version < new_root.signed.version &&
root.signed.version.get() + 1 == new_root.signed.version.get(),
error::OlderMetadataSnafu {
role: RoleType::Root,
current_version: root.signed.version,
new_version: new_root.signed.version
}
);
この変更により、クライアントはルートメタデータを順番どおり(N、N+1、N+2、...)にダウンロードして適用し、スキップしなくなります。ルートメタデータファイルのバージョンが正確に old_version+1 ではない場合、Tough はそれを潜在的な攻撃として扱い、更新を中止するようになりました。
Tough は、TUF で定義されている「終端(terminating)」委任ターゲットロールを誤って処理していました。TUF リポジトリでは、委任を 終端(terminating) としてマークできます。つまり、ターゲットのルックアップがその委任に到達し、そこでターゲットが見つからなかった場合、検索は停止する(他の優先度の低い委任に続行しない)必要があります。(こちら を参照)論理エラーのため、Tough はこの場合検索を終了できず、停止すべき状況でも後続の委任を検索し続けていました。これにより、優先度の低い委任を制御する攻撃者が、本来制御すべきでないターゲットのコンテンツを配信でき、意図された信頼境界を迂回する可能性がありました。
tough/src/editor/targets.rs のターゲット検索アルゴリズム(および関連する委任解決コード)。脆弱なコードは、委任の terminating フラグを適切に処理していませんでした。Tough は、ターゲットを探して委任を反復処理する際、現在の委任が終端とマークされていて一致しなかった場合でも、TUF 仕様に反して次の委任に進んでいました。脆弱な実装: Tough <0.20.0 では、find_target() ロジックは、ターゲットが見つかるかすべての委任ロールを探索し尽くすまで、単純にすべての可能な委任ロールを再帰的またはループで探索していました。終端ロールに遭遇しても、フラグを設定したりループを抜けたりしませんでした。旧動作の擬似コード:```rust
for role in delegation_chain {
if role.has_target(target) {
return target_metadata;
}
// Missing: if role is terminating and target not found, should break.
// Tough erroneously continues to next delegation.
}
つまり、上位の終端委任後に無視されるべき優先度の低い委任先が、依然として参照され、悪意のあるターゲットファイルを提供する可能性があるということです。
<b>修正された実装:</b> パッチ適用版では、終端を追跡し、検索を適切に停止するメカニズムが導入されています。終端となる委任が検出され、それにターゲットが含まれていない場合、Tough は [検索ループから即座に抜け出します](https://github.com/awslabs/tough/commit/598111f88105a707ee68b0fa06c52da7176ea96a#:~:text=%2F%2F%20we%20encountered%20a%20terminating,so%20we%20stop%20iterating%20immediately)。更新されたコードでは、終端となるロールに達するとブールフラグ(例: `terminated`)が設定され、コールスタックの上位に伝播されます。例:```rust
// If a terminating delegation was reached (and we didn't find the target there), stop searching further
if role.terminating && !permissive {
// Mark that we encountered a terminating delegation
*terminated = true;
break;
}
さらに、再帰的な find_target 呼び出しには terminated フラグが追加され、終了が通知されると、上位レベルのループでは他の委任が考慮されないようになりました。Tough の RepositoryEditor::delegate_role と関連構造体も更新され、terminating 属性を保存し、検索ロジックに渡すようになりました。
break がないため、許可されていないターゲットデータが考慮される可能性がありました。Tough の スナップショットメタデータにおけるロールバック攻撃の検出ロジックは不完全でした。具体的には、Snapshot ロールを更新する際、Tough は 以前に確認されたすべてのターゲットメタデータ (委任されたターゲットを含む) が新しいスナップショットに引き続き存在し、バージョンが逆戻りしていないことを検証する必要があります (こちら を参照)。Tough はトップレベルの targets.json についてはこれを強制しましたが、委任されたターゲットメタデータファイルについては実行できませんでした。このギャップにより、攻撃者がリポジトリのスナップショットメタデータ内の委任されたターゲットファイルを検出されずに削除または巻き戻す可能性があり、クライアントが古い (または存在しない) 委任されたターゲットファイルを最新のものとして受け入れる 原因となりました。
tough/src/lib.rs のスナップショット更新検証 (新しい Snapshot メタデータをロード/適用する関数)。脆弱なコードは、スナップショット内のメインの targets.json ロールの連続性のみをチェックし、スナップショットメタデータにリストされている委任ロールを反復処理して同様のチェックを実行しませんでした。