ソフトウェア保証
米国国防総省(DoD)は、ソフトウェア保証を次のように定義しています1。
… ソフトウェアが意図したとおりに機能し、ソフトウェアの一部として意図的または非意図的に設計または挿入された脆弱性が存在しないことに対する信頼の度合い。
これは簡潔な定義ですが、掘り下げるべき奥行きがあります。 政治的イデオロギーや国籍に関係なく、この定義は特定のシステムのセキュリティについて考えるための鋭いレンズです。
脆弱性:セキュリティ欠如の根本原因
これまでにプログラムを書いたことがある人なら、バグという考え方にはおそらく非常になじみがあるでしょう。これは、プログラムが誤動作する原因となる誤りです。 プログラムに存在するバグの一部は脆弱性である場合があります。つまり、攻撃者に悪用される可能性があるということです。 違いを明確にするために、2つのシナリオを対比してみましょう。
-
有効な認証情報でログインできない場合は?
- 認証が壊れています。
- 正規ユーザーを含め、誰もデータにアクセスできません。
- それはバグであり、ソフトウェアが正しく動作していません。
-
無効な認証情報でログインできる場合は?
- 認証が壊れています。
- 攻撃者を含め、誰でもデータにアクセスできます。
- それは、機密データの閲覧や変更に悪用される可能性のある脆弱性です。
DoDの定義は、バグがないことを「ソフトウェアが意図したとおりに機能する」という表現に包含していますが、セキュリティに関連する目標は、「脆弱性が存在しない」ソフトウェアへ近づくことです。 本書はその両方を扱います。 私たちは、堅牢でセキュアなシステムを構築したいのです。
率直に言えば、脆弱性が完全に存在しないソフトウェアや、絶対的にセキュアなソフトウェアはありません。 口語的に言えば、セキュアなシステムとは、攻撃のコストがあらゆる資産の価値を大きく上回るシステムです。 資産とは、ハードウェア、ソフトウェア、機密情報など、保護すべきシステムのあらゆる部分を指します。 攻撃を不可能にすることはできません。ただ、非現実的にすることはできます。
セキュリティ実務者として、私たちはソフトウェアエンジニアがバグを最小化しようと努めるのと同じように、脆弱性を兵器化最小化しようと努めます。
脆弱性が少なければ、現実的な攻撃も少なくなります。
しかし、後の章で取り上げる形式検証でさえ限界があります。
ハードウェアとの相互作用やソフトウェアコンポーネント間の相互作用はあまりにも複雑であり、任意のシステムが考え得るすべての脅威モデルに耐えられると誰かが完全に確信することはできません。
だからこそ保証
ここで「信頼の度合い」が重要になります。 一連のツールとプロセスを適用することで、その一部は本章で試しますが、ソフトウェアのセキュリティに対する信頼を築くことができます。 大まかに言えば、これらは次の3つのカテゴリのいずれかに分類されます。
-
静的保証 - 開発中および/またはテスト中に、コードを実行せずに行うチェック。
-
動的保証 - テスト中に、プログラムを実行して行うチェック。
-
運用保証 - ソフトウェアが本番環境で実行されているときに取られる対策。
この文脈で「本番環境」とは何を意味するのでしょうか?
情報システムが顧客にサービスを提供する環境です。 あなたが行うすべてのセキュリティ上の判断は、この環境の現実に基づいているべきです。
Webアプリケーションの場合、バックエンドコンポーネントについては「クラウド」(さまざまな地理的場所にプロビジョニングされた仮想マシン)を意味するかもしれません。そしてフロントエンドコンポーネントについては「クライアント」(アプリやブラウザを実行するスマートフォンのような、エンドユーザーが所有するハードウェア)を意味するかもしれません。
組み込みシステムの場合、本番環境は多種多様な冒険的な場所になり得ます。 自動車のケースでは、センサーとステアリングの両方に接続された、車のダッシュボード内の小さなコンピューター上です。
各カテゴリの理由と方法を理解するための概念的基盤を構築し、本書の大部分をその知識を実践的に適用することに費やします。 私たちはコードを書き、ツールを使うことに焦点を当てているため、高レベルのソフトウェアエンジニアリング2方法論は扱いません。 これには次のものが含まれます。
-
システム開発ライフサイクル(SDLC3): 任意の情報システムを計画、作成、テスト、デプロイするための一般的なプロセス。
-
Microsoft Security Development Lifecycle(SDL4): ソフトウェアセキュリティ脆弱性の発生可能性と保守コストを低減するためのフレームワーク。
このような方法論は価値のある設計図であり、本章の概念をそれらに対応付けることもできますが、ここでは議論しません。プロジェクトレベルのベストプラクティスが存在し、組織のリーダーシップとコミュニケーションするための共通言語を提供できることだけ知っておいてください。
DoDの定義では、一般にバックドアと呼ばれる、「意図的に…設計または挿入された」脆弱性という考え方にも言及しています。 私たちが書くコードは、Rustの標準ライブラリ5以外には、ごく少数の十分に信頼された依存関係しか持たないため、バックドアの検出については気にしません。 ただし、本章ではこのトピックをより直感的に感じられるように、単純なバックドアを目にすることになります。
Rustは、システムプログラミングの制約下で前例のないレベルの保証を提供するため、有望なセキュリティ技術です。 Rustコンパイラは、俗に「メモリ破壊バグ」と呼ばれる悪質な種類の脆弱性が存在しないことを証明できると同時に、同じ保証を提供できない言語のベアメタル性能に匹敵します。 次章ではメモリの仕組みと安全性を深掘りしますが、本章に入るにあたり、改めて強調しておく価値があります。
初めての高保証Rustプログラム
理論は基盤ですが、成長には実践的な経験が必要です。 さっそく始めましょう。 本章の後半では、Rustによる高保証システムプログラミングを初めて体験します。 200行未満のコード(テストを含む)で、次のことを行います。
-
ほぼあらゆる組み込み環境で実行可能な小さな暗号ライブラリを実装する。
-
公式にリリースされたテストベクターを使用して実装を検証する。
-
動的テストが失敗する箇所を理解するために、素朴なバックドアを挿入する。
-
コマンドラインフロントエンドを追加し、ライブラリを使ってローカルファイルを暗号化できるようにする。
行数はごくわずかですが、私たちのツールはモジュール化されたシステムになります。 信頼できるコンポーネントで構成されます。
サードパーティライブラリのメモリ安全性検証
Rustプロジェクトでは、オプション属性
#![forbid(unsafe_code)]を有効にできます。 これにより、単一のバイナリまたはライブラリの境界内で、unsafeの使用がコンパイル時エラーになります。サードパーティの
#![forbid(unsafe_code)]依存関係をソースからビルドすると、外部エンティティから調達したコードがメモリ安全であることを、コンパイラが自動的に検証できます。 コンパイラ自体にバグがない限り。
しかし現実世界のチームは、自明でないシステム内の実行可能コードの1バイト単位すべてを検証できると期待することはできません。 その検証がメモリ安全性のためであれ、何らかの別の性質のためであれ同じです。 素早く出荷するために、私たちは次のようにします。
-
フロントエンドを構築するために、Rustの標準ライブラリと広く使われているサードパーティライブラリに依存します。
-
フロントエンドを構築するために、
libc、つまりC標準ライブラリ(動的メモリアロケータ、POSIX APIなど)に推移的に依存します。 -
エンドユーザーにインタラクティブな機能を提供するために、成熟したオペレーティングシステムに推移的に依存します。
私たちの目標は、自分たちが書くコードから高水準の設計上の欠陥、ロジックバグ、メモリエラーを排除することです。 攻撃者にとって唯一の現実的な選択肢が、標準ライブラリ、有名なサードパーティ依存関係の最新版、あるいはOS自体の脆弱性を見つけることだけであるなら、私たちのシステムを侵害するためのコストはおそらく高いでしょう。
しかし、それは本当に高保証なのか?
いいえ、私たちは意図的に譲歩しています。破られている暗号化アルゴリズムであるRC4を使うことです。 理由は2つあります。
-
ソース行数を少なく保つため。RC4は単純なので、例として機能します。
-
この章のチャレンジに取り組む動機を与えるため。このチャレンジでは、モジュール式CLIツールを最新の暗号化バックエンドに切り替えることを求めます。
RC4はかつて、SSL/TLSやWEPのように、私たちの社会が依存するプロトコルの一部でした。 しかし1987年の登場以来6、複数の弱点が発見され、いくつかの実用的な攻撃が実証されています。
これは重要な公理の縮図です。保証は動く標的です。 セキュリティ環境が変化するにつれて、要件や講じる対策も変わらなければなりません。
動機
先に進む前に、少し立ち止まりましょう。そもそも、なぜソフトウェア保証を優先するのでしょうか?
別のDoDの声明がそれをうまく要約していると言えるでしょう。 安全でないソフトウェアの代償に関する以下の説明では、“mission critical” を「ビジネスクリティカル」に置き換えて読んでも構いません1。
結果: 敵はミッションクリティカルなデータを盗んだり改ざんしたりする可能性があり、ミッションクリティカルなプラットフォームの機能を破損させたり拒否したりする可能性がある
サイバースペースへようこそ。 このフロンティアを安全にしましょう!
学習成果
- 静的解析と動的解析のトレードオフを理解する
- 運用上のデプロイ対策の役割を理解する
- 最初の興味深いRustプログラムを書く: 小さな暗号化ツール
- 静的リンクされた実行可能ファイルのビルド方法を学ぶ(ほぼすべてのLinuxクライアントで動作)
-
DoDソフトウェア保証イニシアチブ. Mitchell Komaroff, Kristin Baldwin(2005年、パブリックドメイン) ↩ ↩2
-
ソフトウェアエンジニアリング知識体系ガイド. Pierre Bourque, Richard E. Fairley(2014年) ↩
-
システム開発ライフサイクル. Wikipedia(2022年アクセス)。 ↩
-
Microsoftセキュリティ開発ライフサイクル (SDL). Microsoft(2021年) ↩
-
Rustの標準ライブラリは、どんな大規模なソフトウェアと同様に、脆弱性がないことが保証されているわけではありません。以前に発見された2つの脆弱性には、
unsafeコードにおけるメモリ安全性エラー7と、Time-of-check-to-time-of-use(TOCTTOU)競合状態8があります。しかしstdは公式のRustチームによって保守されている広く使われているコンポーネントなので、一般的にはサードパーティパッケージよりも信頼できます。特にバックドアに関してはそうです。 ↩ -
偶然にも、RC4の発明者であるRon Rivestは、第7章で実装するデータ構造であるスケープゴート木の共同発明者でもあります。スケープゴート木はRC4ほどの人気を得ることはありませんでしたが、確かに時の試練に耐えてきました。 ↩
-
CVE-2018-1000657の分析: RustのVecDeque::reserve()におけるOOB書き込み. GeorgiaTech SSLab(2022年アクセス)。 ↩
-
標準ライブラリのセキュリティアドバイザリ (CVE-2022-21658). The Rust Team(2022年アクセス)。 ↩