動的保証(全3回の1)
静的解析は取り組むのが難しいトピックになり得ます。それは、日々の開発の現実から切り離されているように見える理論と証明の世界の氷山の一角です。 しかし、コンパイラのユーザーである私たちは、実装の詳細をすべて理解しなくても型システムの恩恵を受けています。
それに比べると、動的解析は楽しく、身近に感じられる相方です。 独自の静的解析を実装する開発者はほとんどいません。 ほとんどのプロの開発者はユニットテストを書きます。これは、プログラムの一部を意味のある方法で実行する小さな動的解析です。
動的解析は概念的には理解しやすいものです。具体的な入力でプログラムを実行し、何が起こるかを観察することで、そのプログラムについて学びます。
-
長所: 実際のプログラムを現実世界で実行したものなので、各実行結果を信頼できます。偽陽性はありません1。
-
短所: 一度に観察できるのは1回の実行だけなので、データポイントの集合から繰り返しサンプリングすることで確信を積み上げていきます。しかし、完全な集合はたいてい巨大であり、私たちのサンプルはごくわずかです。そのため、一般的な結論を導くことはできません。
- 動的解析は、1つ以上のバグの存在を証明できます。しかし、いかなる種類のバグの不在も証明することはできません。
プログラムを実行すると何が起こるのでしょうか?
ハードウェアとソフトウェアのコンポーネントのオーケストラが、複雑な方法でタスクを実行し、相互作用します。 1万フィート上空から眺めると、おおよそ次のようになります。
ローダーがプログラムのインスタンスをメモリにコピーし、そのための隔離された環境をセットアップして、「プロセスを生成」します。 プロセスが実行中で、ディスクへの書き込みやネットワークデータの読み取りを行いたい場合、オペレーティングシステム(OS)の協力を引き出します。 そのOSは、物理ディスクドライブやネットワークインターフェイスカードのようなハードウェアへのアクセスを管理します。 比較的小さな状態機械である*中央処理装置(CPU)*が、あなたのプログラム、何百もの他のプログラム、そしてOS自体の実行を高速に切り替えることで、一連のイベント全体を駆動します。
動的解析は、*テスト対象プログラム(Program Under Test: PUT)*に便乗する小さなプログラムです。 実行中、あるいは実行の前後にPUTへ「フック」し、「ライブイベント」を記録します。 たとえば、デバッガーは実行中の特定の時点における変数の現在値を読み取れます。 ユニットテストは、特定のパラメーターで実行された特定の関数の戻り値を確認できます。
Rustで暗号を少し書いてみましょう!
現実的には、読者のうち決して少なくない割合の人が、この第2章を越えて読み進めないかもしれません。 現実の優先事項は変わりますし、新しい言語と新しいスキルセットを学び、それを最後までやり遂げるのは大変なタスクです。
だからこそ、あなたには今すぐ興味深いRustプログラムを書いてもらいます。 実際に動くもの、実行できるものを作りましょう。 コマンドラインツールのエンドユーザーとしても、セキュリティ上重要なライブラリを検証するテスターとしても使えるものです。
これから、小さいながらもモジュール化されたプログラムを書きます。 それには2つの部分があります。
-
単一暗号の暗号ライブラリ: 有名だが時代遅れのストリーム暗号であるRC4を、ゼロから実装した、組み込み向けにも適した実装です。扱いの難しいRustコードを書くための独立した短期集中講座だと考えてください。
-
コマンドラインインターフェイス: あなたの暗号ライブラリを使って、コンピューター上のファイルを暗号化および復号する方法です。引数解析とファイルI/Oを実行できるようになることは、どんな新しい言語でも、実用的なプロジェクトへの扉を開きます。
さて、暗号は正しく実装するのが非常に難しいことで有名です。 そしてRustコンパイラは、どれほど強力であっても、特定のアルゴリズムの実装の正しさを静的に推論することはできません。ストリーム暗号であれ、それ以外であれ同じです。 ここで動的解析の出番です。
-
既知の正しいRC4実装との入力出力の等価性を示すユニットテストを書きます。
-
動的解析がどこで失敗するのかを理解するために、ライブラリへ単純なバックドアを挿入します。
ストリーム暗号とは何でしょうか?
この用語を初めて見る場合、または簡単に復習したい場合は、先に進む前に付録の「基礎: ストリーム暗号」セクションを読んでください。 次のセクションの暗号コードを理解するために必要な背景を簡潔に説明しています。
モジュール化されたプロジェクトのセットアップ
第1章の終わりでセットアップした開発環境にログインし、ここから先は手を動かしながら進めてください。 以下を流し読みするだけでなく、実際にやって学びましょう!
まず、Rustツールチェーンが正しくインストールされていることを確認します。 次のコマンドを実行すると何が起こるでしょうか?
rustup doc --std
WebブラウザーでRust標準ライブラリのドキュメントが開くはずです。
このコマンドは覚えておくと便利です。
もし飛行機の中でエアギャップされた安全な施設でコーディングしたことがあるなら、オフラインでアクセスできるドキュメントが必要になるかもしれません。
次に、Rustのパッケージマネージャーであるcargoを使って、「ワークスペース」2を作成します。
ワークスペースは、独立したモジュール(Rustの用語ではクレートと呼ばれます)で構成されるプログラムを整理するための便利な方法です。
-
各クレートは、それ自体が独立した「プロジェクト」です。IDEが作成するもののようなものです。
-
ワークスペースでは、2つ以上のクレートが単一のビルドディレクトリを共有できます。これにより、共有依存関係のコンパイル時間を節約できます。
-
クレートは、ワークスペース内の同等の仲間(同じワークスペース内にあるが別のサブディレクトリにある他のクレート)の公開APIを呼び出せます。
この章で書くコードはかなり短いものになります(200行未満)。 しかし、より大きなプロジェクトでは、ワークスペースがモジュール性を助けます。 モジュール化されたコード構成は、複雑さを抑えるのに役立ちます(これについては第3章で詳しく説明します)。
まず、暗号ライブラリとそのコマンドラインインターフェイスの両方を収める最上位ディレクトリを作成します。
crypto_toolと呼ぶことにしましょう。
mkdir crypto_tool
次に、cargoを使って2つのクレートのスケルトンを生成します。
-
rc4という名前のライブラリ(共有オブジェクト)クレート。 -
rcliという名前のバイナリ(実行可能)クレート(“RC4 CLI“を疑わしく短縮したもの)。
rcliバイナリは、rc4ライブラリのAPIに依存します。
既存の独立した暗号ライブラリを使用する現実世界のツールと同じです。
両方のクレートの定型コードを生成するには、次のようにします。
cargo new crypto_tool/rc4 --lib
cargo new crypto_tool/rcli
--libフラグは、cargoにライブラリクレートを特に作成するよう伝える点に注意してください。
フラグが指定されていない場合、mainメソッドを持つ実行可能バイナリがデフォルトです(ただし、明示したい場合は--binを使うこともできます)。
バイナリとライブラリの違いは何ですか?
バイナリは、直接実行できるスタンドアロンプログラムです。 以下の
treeコマンドは、対応するバイナリプログラムを探して実行するようシェルに指示します。ライブラリには再利用可能なコードが含まれており、通常はバイナリや他のライブラリから呼び出せる API です。
treeがコンソールに出力を表示するとき、C の標準ライブラリ内の API であるprintfを呼び出します。面白い事実があります。Linux の ELF や Windows の PE のようなファイル形式では、ライブラリとバイナリの違いはファイルヘッダー(ローダーが理解するメタデータ)内のわずか 1 バイトだけです。 CPU から見れば、どちらも単なるプログラムです!
現時点では、cargo は 2 つのクレート(rc4 と rcli)が関連していることを知りません。
今のところ、それらはたまたま隣接するディレクトリに存在しているだけです。
crypto_tool ディレクトリに新しい Cargo.toml ファイルを作成して、cargo に状況を把握させておきましょう。
touch Cargo.toml
この新しく作成したファイルを好みのエディターで開き、rc4 と rcli が同じワークスペースの一部であることを cargo に知らせるために、次の内容を入力します。
[workspace]
members = [
"rc4",
"rcli"
]
Linux コマンドの tree を実行すると、次のようなファイルとディレクトリの構成が表示されるはずです。
.
└── crypto_tool
├── Cargo.toml
├── rc4
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── rcli
├── Cargo.toml
└── src
└── main.rs
5 directories, 5 files
.rs は Rust ソースファイルの拡張子です。
2 つの .rs ファイル(main.rs と lib.rs)が、これからコードを書く場所です。
Cargo.toml ファイルはプロジェクトマニフェスト3であり、Rust のビルドシステムの設定です。
他の 2 つは cargo new を実行したときに自動的に作成されたことに注目してください。
少し時間を取って、それらの内容を確認してください。
rcli は rc4 ライブラリに依存するため、cargo はコンパイル時にライブラリコードを見つける方法を必要とします。
その Cargo.toml ファイルの [dependencies] タグの下にエントリを追加する必要があります。
rcli/Cargo.toml を開き、以下のように最後の行を追記します。
[package]
name = "rcli"
version = "0.1.0"
edition = "2021"
# キーとその定義の詳細については https://doc.rust-lang.org/cargo/reference/manifest.html を参照してください
[dependencies]
rc4 = { path = "../rc4" }
ワークスペースの準備が整っていることを確認するには、crypto_tool ディレクトリから cargo build を実行します。
以下のような出力が表示され、rc4 と rcli の両方が正常にコンパイルされたことが示されるはずです。
Compiling rcli v0.1.0 (/home/tb/proj/high-assurance-rust/code_snippets/chp2/crypto_tool/rcli)
Compiling rc4 v0.1.0 (/home/tb/proj/high-assurance-rust/code_snippets/chp2/crypto_tool/rc4)
Finished dev [unoptimized + debuginfo] target(s) in 0.43s
定型的な準備が片付いたので、組み込み環境に適した RC4 ライブラリを書き始める準備ができました!
次のセクションの詳細をすべて理解する必要がありますか?
いいえ。 次のセクションでは、Rust の構文と暗号技術の概念の両方に触れます。 先に進むために、細部まで完全に理解する必要はありません。
Rust の見慣れない構文は、特に第 3 章以降、読み進めるにつれて身についていきます。
暗号技術はこの本の主題ではありません。この章で開発しているサンプルプログラムの文脈として、大まかな内容を把握すれば十分です。
- 必要に応じて、対応する付録セクションを確認することを忘れないでください。
-
一般的に言えば、動的解析には偽陽性はありません。ただし、テスト固有の例外は存在します。たとえば、クラッシュを引き起こす入力を見つけるために単一の関数をファジング(ストレステスト)しているとします。クラッシュを見つけるかもしれませんが、実際にはプログラム全体が、クラッシュを引き起こす入力をテスト対象関数に渡す前にサニタイズ(正規化または拒否)している可能性があります。この場合、そのクラッシュはより大きなプログラムの文脈では実際には再現できないかもしれません。 ↩