Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

静的ツールと動的ツール

ミッションクリティカルなアプリケーションの作者として、私たちは自分たちのコードにバグが少なく、脆弱性はたとえ存在するとしてもさらに少ない、と確信できなければなりません。 信頼性にとってセキュリティは必要ですが、それ自体だけでは十分ではありません。 私たちにはその両方が必要です。 しかし私たちは、不完全な要件群を相手に、時間やリソースの制約下でソフトウェアを書いています。 自尊心はさておき、私たちは自分たちの確信をどのように正当化できるのでしょうか。

その答えは、定量的に、です。 プロセスとツールを通じて、客観的な証拠の断片を十分に蓄積することによってです。 学術界では、これを検証と呼びます。 しかし、研究カンファレンスで称賛されるプロトタイプが、現実世界で大規模に利用できるほど成熟していることはめったにありません。 そのため産業界では、検証技術の実用的なサブセットを採用し、それをテストというラベルの下に置いています。

テストの目標は、ソフトウェアというコインの両面の一貫性を検証することです。

  • 仕様: ソフトウェア製品に対するビジネス上重要な要件。通常は平易な英語で表現されますが、「ビジネスロジック」はツール固有の形式(アサーション、事前/事後条件、型状態など)でエンコードできます。

    • 平易な英語の例: 当社の Web アプリケーションは、ネットワークベースの攻撃者からユーザーのデータを保護しなければならない。
  • 実装: ソフトウェア製品の設計と動作を、実際のコードとして表現したもの。

    • 例: その Web アプリケーションは、業界標準の Transport Layer Security (TLS) ライブラリである OpenSSL1 を使用して、ネットワーク経由で送信されるデータを暗号化し(機密性)、転送中にデータが破損していないことを検証し(完全性)、データの送信者/受信者の身元を検証します(認証)。

仕様と実装が整合していることを示せるなら、私たちは確信を得られます。 ここで、私たちのプログラムを解析するツールが登場します。 すべての解析ツールは、小さな証拠の断片、つまり仕様と実装のごく小さな一致を出力します。 それらは、コードレビューやセキュリティ評価のような手作業のプロセスの代替ではありません。熟練した人間の「頭脳」には及ばないからです。 ツールはむしろ補助であり、コストを削減し、スケールを容易にします。

人間と違って、ツールは容赦なく勤勉です。疲れたり、気が散ったりすることがありません。 また、たとえ偽陽性の場合(存在しないバグを報告する場合)であっても、完全に一貫しています。 メモリ安全性違反のような特定のセキュリティ問題を検出するうえで、一貫したツールは保証を達成する最善の方法です。

Rust は完璧には程遠いものです。 しかし、信じられないほど現代的なコンパイラを備えているため、商業的に実用可能なプログラミング言語と、特定のランタイム信頼性プロパティを検証する洗練されたツールとの境界を曖昧にしています。 型システムがその二つを橋渡しし、ツールチェーンがその恩恵を民主化します。 私たちは、確信を抱かせる次のような問いに、実証可能な形で答えることができます。

  • 私のプログラムは実行時にメモリエラーに遭遇することがあり得るのか。

    • Rust のコンパイラは、私たちのコードを初めてビルドするときにその答えを提供します。
    • これは静的解析です。プログラムは一度も実行されていません。
    • その答えは、プログラムが到達し得る現実的な状態の大半2に適用されます。
    • これは*安全性プロパティ3*です。プログラムが悪い状態に到達し得ないことをチェックします。
  • この特定の入力が与えられた場合、私のプログラムは正しい結果を生成するのか。

    • Rust の公式パッケージマネージャーである cargo は、ユニットテストの実行時に答えます。
    • これは動的解析です。プログラムの一部が具体的な入力で実行されました。
    • その答えは、あなたがテストした状態と、それらに意味的に近い状態にのみ適用されます。
    • これはライブネスプロパティです。プログラムが良い状態に到達することをチェックします。

メモリ安全性に関する問いはコンパイル時に静的に答えられ、入出力の正しさに関する問いは実行時に動的に答えられることに注目してください。 静的/動的という二分法は、解析ツール設計の核心にあります。

静的解析と動的解析は、対立しながらも補完し合う力という点で、一種の「陰と陽」4です。 その二元論的な性質にふさわしく、それらは異なるアルゴリズムと技術に支えられた、対照的な長所と短所の集合を提供します。

ユニットテストとは何か。

プログラムのサブセットに対する、手書きだが自動実行可能なチェックです。 通常は個々の関数を呼び出し、戻り値または副作用を検証することで実装されます。

ユニットテストは誤り得るものであり、多くの場合不完全ですが、より高い意味的洞察があるため、自動化アプローチの大多数よりも優れています。 動的解析に入るときに、ユニットテストを一通り見ていきます。

実務エンジニアのための全体像

コンピューターサイエンスの領域に入り込みすぎる前に、実務エンジニアの視点から解決空間を分解してみましょう。 私たちは、テストプロセスを迅速化するか、スケールさせる必要があるために、ツールに関心を持っていると仮定します。 大まかに言って、今日の静的ツールと動的ツールをどのように分類できるでしょうか。

一つのアプローチ5は、X 軸に静的 vs. 動的、Y 軸に既知のバグ vs. 未知のバグを置いた四象限です。

番号順に、左から右、上から下へと象限をたどっていきましょう。 地平線より上では、未知のバグを見つけられます。 これは、既存のデータを必要とせず、まったく新しいバグを発見することを意味します。

  1. 静的、未知のバグ (S, U) - 既存のソースコードまたはビルド成果物を取り込み、解析を使ってバグを見つけたり、プロパティを証明したりします。プログラム自体は実行しません。

    • ツールの例: rustc(本書全体)。
  2. 動的、未知のバグ (D, U) - 具体的なテストケースを生成し、これらの生成された入力でプログラムを実行します。実行時の振る舞いからフィードバックを収集する可能性もあります。

    • ツールの例: libFuzzer(第12章)。

地平線より下では、シグネチャデータがすでに利用可能な、事前に発見されたバグだけを検出できます。 つまり、既知のバグです。

  1. 静的、既知のバグ (S, K) - ソフトウェアシステムの構成を解析し、依存関係に既知の脆弱なバージョンや既知のバグのある設定がないかをチェックします。

    • ツールの例: cargo-audit(第3章)。
  2. 動的、既知のバグ (D, K) - 稼働中の資産またはサービスに問い合わせ、それらのバージョンや設定をフィンガープリントします。

    • ツールクラスの例: ネットワーク脆弱性スキャナー(本書では扱いません)。 どのアプローチであっても、報告されたバグの一部は、アプリケーションやサービスというより広い文脈において、悪用可能な脆弱性である可能性があります。 したがって、一般的なソフトウェアセキュリティのワークフローは次のようになります。
  3. ツールから結果を収集する。

  4. それらのレビューをトリアージする。

  5. 優先度の高い脆弱性の修正を開発する。

  6. パッチ適用済みの製品をテストする。

  7. 修正を本番環境にデプロイする。

このサイクルをより速く進められるほど、より良くなります。 少なくとも、機能追加のペースに追随できる必要があります。 理想的には、セキュリティテストは新機能のロールアウトを支援し、既存の攻撃対象領域をプロアクティブに堅牢化できます。

では、未知のバグを見つけるツールがあるなら、既知のバグをチェックするツールはそもそもなぜ必要なのでしょうか? 第1象限と第2象限のツールを使って、存在するすべてのバグを見つけるだけではだめなのでしょうか?

残念ながら、答えはノーです。 多くのバグクラスは、どのような種類のツールや分析を使っても自動的には検出できません。 その理由については、後のセクションで制限について説明するときに取り上げます。 ほとんどのバグは熟練した人間によって発見され、第3象限と第4象限のツールが検出できるシグネチャに変換される必要があります。 繰り返しますが、ツールはスケールのための補助であり、知性の代替ではありません。

まとめ

ソフトウェア保証には「信頼の水準」が伴います。 テストは、ビジネスに関連する仕様と、システムまたは製品の特定の実装が一致しているという信頼を強化します。

静的解析は、プログラムを実行せずにそのプログラムについて推論するもので、あるバグクラスが存在しないことを証明するのに適している傾向があります。 しかし、そのクラスは限られています。 多くの場合、偽陰性はありません(解析は何も見逃しません)。

動的解析は、プログラムを実行するもので、少なくとも1つのバグを見つけるのに適している傾向があります。 しかし、それはどのような種類のバグでもあり得ます。 多くの場合、偽陽性はありません(解析結果は真です)。

どちらのアプローチでも、既知のバグ(例: 既存のCVE)と未知のバグ(例: ゼロデイ)を見つけることができます。 頭が冴えているうちに、まず静的解析に取り組みましょう。 これは、この2つのうち理論的により複雑な方です。


  1. OpenSSL。The OpenSSL Project(2021)。

  2. 「ほとんど」は、その解析が推論できる範囲外のドメインを除外しています。これは、電力やタイミングに関するハードウェアサイドチャネル(ハードウェアと物理世界との相互作用に関する何かが脆弱性を生み出す場合)のようにモデル化が難しいためかもしれません。あるいは、解析を行うコード内のバグやその設計上の欠点によって、推論可能なドメインに制限が生じるためかもしれません。 完全に万全なものはありません。絶対的なセキュリティは存在しません。

  3. ここでいう「安全性プロパティ」は、私たちの問いが対象としている仕様の種類を分類するために使用される一般的な用語です。メモリ安全性は、安全性プロパティの具体例の1つにすぎません。

  4. 陰陽。Wikipedia(2022年アクセス)。

  5. CSE545 Week 12: 6 Reasons to Love Fuzzing。David Brumley(2020)。