解説

インサイド .NET Framework

第8回 アセンブリのロードとセキュリティ

インフォテリア株式会社
吉松 史彰
2002/08/22

Page1 Page2 Page3

JITコンパイル時のプログラムのチェック

 エントリ・ポイントが無事に見つかったので、CLRはこのILコードをJITコンパイルする。このときにまたセキュリティ・チェック・ポイントが存在する。例えば次のコードを見てほしい。

.assembly MainClass {}
.class private auto ansi beforefieldinit MainClass
extends [mscorlib]System.Object {
  .method private hidebysig static void  Main() cil managed {
    .entrypoint
    .maxstack  2
    ldstr   "Hello"
    ldstr   " "
    ldstr   "World"
    call    string [mscorlib]System.String::Concat(string, string, string)
    call    void [mscorlib]System.Console::WriteLine(string)
  }
}
ILアセンブラで記述したサンプル・コード

 このコードは、C#の次のコードをコンパイルした結果できるのと「ほぼ」同じILアセンブラのコードだ。

class MainClass {
  static void Main() {
    System.Console.WriteLine("Hello" + " " + "World");
  }
}
上記のILアセンブラのコードとほぼ同じ内容のC#のコード

 上記のILアセンブラのコードが、MainClass.ilというファイルに保存されているとすると、次のコマンドで正しくコンパイルできる。

% ilasm MainClass.il

 しかし、これで作成されたMainClass.exeを実行すると、次のような例外が発生する。

上記のILアセンブラのコードから生成した実行ファイル(MainClass.exe)の実行画面

 この例外の原因は2つある。1つはメソッドの先頭付近にある「.maxstack」の指定だ。これはメソッドのヘッダ部分に埋め込まれる情報で、このメソッドがスタックのスロットをいくつ使うかを示している。ここでは2つしか使わないことになっている。にもかかわらず、コードではスタックに文字列をロードする「ldstr」という命令が3回実行され、スタックのスロットを3つ使うようになっている。CLRはJITコンパイルの過程でこのバグを検出し、プログラムの実行を阻止する。

 もう1つの原因は、このコードがメソッドの終了後にどこに行くのかが分からない点だ。通常、C#コンパイラなどで正しくコンパイルした結果のILアセンブラ・コードには、最後に「ret」というコードが出力されている。これによって、メソッドは終了するとメソッドの呼び出し側に戻ることが保証される。retが指定されていないので、このコードがそのまま実行されると、メソッドの実行後にメモリ上の任意のコードが実行されてしまう危険がある。そうなると、CLRはコードの管理ができなくなる。そのため、JITコンパイル時にこのバグを発見したCLRは、やはり例外を発生させてプログラムの実行を阻止するようになっている。

 JITコンパイラは、問題なくコンパイルが完了すると、コンパイル後のx86機械語コードをメモリ上に配置し、その先頭アドレスを次のジャンプ先に指定して処理を戻す。その結果、JITコンパイルされたコードの実行が開始されることになる。JITコンパイラはコンパイル後のコードの永続化(ファイルに保存すること)は行わないことに注意してほしい。JITコンパイルされたコードは、ユーザーがそのアプリケーションを終了すると破棄される。ユーザーが同じアプリケーションを間髪入れずに再度起動したとしても、JITコンパイルはもう1度行われる。

アセンブリのロード時のチェック

 「第4回 アセンブリのロード(前編)」で作成したuser.exeが利用しているutilアセンブリは、util.dllとutil.netmoduleという2つのモジュールで1つのアセンブリを構成している。Mainメソッドではutil.netmoduleに定義されているUtilクラスを利用しているので、上記の手順でMainメソッドがJITコンパイルされるタイミングで、すでにutl.dllとutil.netmoduleファイルがメモリ上にロードされている。ここにもう1つのセキュリティ・チェック・ポイントがある。

 ほかのアセンブリをロードするとき、そのアセンブリに厳密名が付いていた場合、CLRは次のような動作をする。

  • アセンブリがグローバル・アセンブリ・キャッシュ(GAC)からロードされた場合は、そのままロードする。
  • アセンブリがそのほかの場所からロードされた場合は、StrongNameSignatureVerification関数を呼び出して署名の検証を行う。

 つまり、GACに入っているアセンブリをロードしたときは、CLRは署名の検証を行わない。アセンブリは、(gacutil.exeやエクスプローラを使って)GACにインストールされるタイミングで署名の検証を受けているので、ロード時にいちいち検証する手間をかけないのだ。一方、構成ファイルのcodeBaseからロードされた場合や、プローブでロードされた場合は、ロードされるたびに毎回署名のチェックが行われる。

 このように動作を分けているのは、電子署名による改ざん防止機能を維持しつつ、実行時のパフォーマンスを向上させるためだ。codeBaseやプローブでロードされるアセンブリは、マシン管理者の目が行き届かない場所からロードされる危険があるので、毎回チェックしなくてはならない。だが、GACに入っているアセンブリのファイルに物理的にアクセスできるのはマシン管理者(Administratorsグループのメンバー)だけだ。GACには、.NET Frameworkの根幹をなすアセンブリがたくさん登録されている。これらのアセンブリはほとんどのプログラムから利用されるため、いちいち署名を検証すると時間がもったいない。もしもアセンブリがGACにインストールされた後に改ざんされたとしたら、そのマシンはすでに乗っ取られている(Administratorの権限が奪われている)ということだから、アセンブリの電子署名のチェックなどしても無駄である。mscorwks.dllが置き換えられているかもしれないし、StrongNameSignatureVerification関数がおかしくなっているかもしれないからだ。これらの点を踏まえて、MicrosoftはGACからロードされたアセンブリのチェックは行わないようにした。

モジュールのロード時のチェック

 utilアセンブリは、マニフェストだけを持つutil.dllと、実体となるutil.netmoduleから構成されている。このとき注意しなければならないのは、アセンブリに厳密名を付けて署名をしても、実際に電子署名が施されるのはアセンブリのプライマリ・モジュールだけということだ。この場合、util.dllには署名がされているが、util.netmoduleには署名がされていない。そうなると心配なのは、せっかく署名をしても、モジュールの方が改ざんされてしまうのではないかということだ。ここにさらにセキュリティ・チェック・ポイントがある。

 Microsoftは当然この問題を考慮している。複数のモジュールからアセンブリを作る場合で、しかもそのアセンブリに厳密名が付けられる場合は、まず1つ1つのモジュールのハッシュ(アルゴリズムはSHA-1)が取られ、それがマニフェストの中に書き込まれる。例えば、util.dllのマニフェストには次のようなエントリがあった。

ildasm.exeで表示したutil.dllのマニフェスト内のFileエントリ

 “HashValue”という属性がこのモジュールのハッシュ値を表す。これが書き込まれた状態で、次にプライマリ・モジュールに電子署名が施されるので、マニフェスト内のこのハッシュ値も署名の対象になっている。

 アセンブリをロードした結果、プライマリ・モジュールとは別のモジュールをロードすることになると、CLRはモジュールをハッシュして、値をマニフェストに書いてある値と比べる。同じなら改ざんされていないのでロードされるし、値が異なればモジュールが改ざんされたことになるので、System.IO.FileLoadExceptionが発生して処理は中断される。CLRはモジュールの改ざん防止機能もきちんと持っているのである。

 これ以降、実行されているコードの中でほかのメソッド、型、モジュール、アセンブリが参照され、CLRがそれをロードするたびに、上記のチェックが行われて、プログラムの動作が継続していくことになる。メソッドが参照されるとJITコンパイルが行われて、コードがチェックされる。参照されている型がアセンブリ内のほかのモジュールに入っていればそのモジュールがロードされ、チェックされる。参照されている型がほかのアセンブリに入っていればそのアセンブリがロードされ、チェックされる。

まとめ

 .NET Frameworkでは、プログラムが不正な動作を行わないようにするための工夫が随所に設けられている。プログラムが不正な動作を行うのは、バグかコードの改ざん(ファイルまたはメモリ上)のどちらかだ。CLRはバグを含むコードをできるだけ実行しないようにするために、JITコンパイラで数々のチェックを行っている。また、改ざんされたプログラムを実行しないように、電子署名とハッシュのシステムを使っている。このような仕組みはプログラマーが知っていなくても構わないかもしれない。結局のところ、プログラマーが知っていようといまいと、この機能をバイパスすることはCLR上ではできないからだ。 だが、このような仕組みを知っていれば、「.NETウイルス」の情報に右往左往したり、むやみにセキュリティについて心配したりすることがなくなるだろう。.NET Frameworkを開発プラットフォームとして選択するかどうかの1つの基準にもなるに違いない。

 今回は、CLRが持つセキュリティ・システムのうちの1つの側面を解説した。次回は、コードの実行中に作用するCLRのセキュリティ・システムのもう1つの側面である「コード・アクセス・セキュリティ」について解説する。End of Article


 INDEX
  解説 インサイド .NET Framework
  第8回 アセンブリのロードとセキュリティ
    1.Windowsローダーの動作
    2.署名の検証とプログラムの開始
  3.いくつものセキュリティ・チェック
 
インデックス・ページヘ  「解説:インサイド .NET Framework 」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間