CLR 徹底解剖

APTCA アセンブリを .NET Framework 4 に移行する

Mike Rousos

Microsoft .NET Framework 4 では、共通言語ランタイム (CLR) のセキュリティ モデルがいくつかの点で大きく変更されました。このような変更点の 1 つとしてレベル 2 の透過性 (Silverlight のセキュリティ モデルとほぼ同じ機能) が導入されたため、AllowPartiallyTrustedCallers (APTCA) ライブラリの作成者は影響を受ける可能性があります。

このような影響の原因は、APTCA の基盤となるしくみが CLR 4.0 で変更されたことにあります。APTCA 属性では、完全に信頼されたライブラリを部分的に信頼された呼び出し元からアクセスできるようにする機能が保持されますが、この機能のしくみは細部が変更されているため、APTCA ライブラリのコードを変更する必要があるでしょう。

注: このコラムでは、"バージョン 2" は CLR 2.0 を表します。CLR 2.0 は、.NET Framework 2.0 ~ 3.5 SP1 のすべてのバージョンを含んでいます。

バージョン 4 より前の APTCA

バージョン 4 より前は、すべてのエントリ ポイントで暗黙のうちに完全信頼のリンクを要求することによって、署名済みのすべてのアセンブリが部分的に信頼された呼び出し元から保護されていました。つまり、いずれかの部分的に信頼されたコードから厳密名のアセンブリにアクセスしようとすると、セキュリティ例外が発生して失敗しました。このため、完全に信頼されたコードが、悪意によって (潜在的に危険な) 部分的な信頼から呼び出されないようになっていました。

署名済みの完全に信頼されたライブラリに AllowPartiallyTrustedCallers 属性を追加すると、これらの暗黙のリンク要求が削除され、部分的な信頼からそのライブラリにアクセスできるようになりました。したがって、APTCA ライブラリでは、部分的に信頼されたコードから APTCA で公開されるメソッド経由で特権が必要な操作に制御された方法でアクセスできました。この方法で安全な操作だけが部分的な信頼に公開されるようにし、潜在的に危険なすべての操作が明示的なリンク要求か、完全な要求で保護されるようにするのは、APTCA の作成者の役割でした。

バージョン 4 での APTCA

AllowPartiallyTrustedCallers 属性が変更されました。バージョン 4 では、リンク要求はまったく関係なくなりました。実際、バージョン 2 の署名済みライブラリに存在していた暗黙のリンク要求は存在しなくなりました。代わりに、バージョン 4 の完全に信頼されたすべてのアセンブリは、既定で SecurityCritical になります。一方、部分的に信頼されたアセンブリは、バージョン 4 ではすべて自動的に SecurityTransparent になります。次の透過性についての概要で説明するように、SecurityCritical コード は SecurityTransparent コードから呼び出すことはできません。

したがって、新しいバージョン 4 の透過性システムにより、以前のリンク要求と同様に、完全に信頼されたコードの保護が提供されます。
SecurityCritical 透過性レベルと SecurityTransparent 透過性レベルは自動的に設定されるため、既定では、部分的に信頼されたコードから完全に信頼されたライブラリを呼び出すことができません。

既にお気付きの方もいらっしゃるでしょうが、バージョン 4 における AllowPartiallyTrustedCallers の変更はこの変更に関連しています。バージョン 4 では、APTCA を適用すると、適用したアセンブリから自動的に SecurityCritical を設定する動作が削除されます。続いて、このアセンブリには既定で SecurityTransparent に設定されますが、APTCA アセンブリの作成者は、必要に応じてより詳細な SecurityCritical 属性や SecuritySafeCritical 属性を特定の型やメソッドに適用できます。

透過性についての速習講座

新しいバージョン 4 の透過性モデルは Silverlight のセキュリティ モデルによく似ているため、Silverlight のセキュリティ モデルに詳しい方であれば SecurityTransparent や SecurityCritical などの透過性属性の効果を理解しやすいでしょう。

SecurityTransparent、SecuritySafeCritical、および SecurityCritical という 3 つの主な透過性属性を見てみましょう。

SecurityTransparent: SecurityTransparent としてマークされたコードは、セキュリティの観点からは安全です。アクセス許可のアサート、検証不可能なコードの実行、ネイティブ コードの呼び出しなど、あらゆる危険な操作を完了することはできません。また、SecurityCritical コードを直接呼び出すこともできません。

  • 既に説明したように、部分的に信頼されたすべてのコードは、セキュリティ上の理由から強制的に SecurityTransparent になります。これは、APTCA ライブラリの既定の透過性でもあります。

SecurityCritical: 対照的に、SecurityCritical コードでは希望するすべての操作を実行できます。アサートしたり、ネイティブ コードを呼び出したりすることができます。また、マークされている透過性に関係なく、他のメソッドを呼び出すこともできます。

  • SecurityCritical に設定できるのは、完全に信頼されたコードだけです。実際、(APTCA ではない) 完全に信頼されたコードは、既定では SecurityCritical であると見なされ、部分的に信頼された呼び出し元から透過的に保護されます。

SecuritySafeCritical: SecuritySafeCritical コードはブリッジとして機能し、透過的なコードから重要なメソッドを呼び出せるようにします。SecuritySafeCritical コードは SecurityCritical コードとまったく同じ権限を持ちますが、SecuritySafeCritical コードは SecurityTransparent コードから呼び出すことができます。したがって、SecuritySafeCritical コードでは基になる SecurityCritical メソッドを安全な方法でのみ公開することが非常に重要です (悪意のある人物が部分的に信頼されたコードで SecuritySafeCritical 層を通じてメソッドを悪用しようとするのを防ぐため)。

  • SecurityCritical コードと同様に、SecuritySafeCritical コードは完全に信頼されている必要があります。

図 1 は、SecurityTransparent コード、SecuritySafeCritical コード、および SecurityCritical コードの相互作用を示しています。


図 1 SecurityTransparent コード、SecuritySafeCritical コード、および SecurityCritical コードの相互作用

上図に示したセキュリティの遷移に加え、すべての透過性レベルでは、同じレベルのコードおよび重要性の低いコードにアクセスできることに注意してください (たとえば、SecuritySafeCritical コードからは SecurityTransparent コードにアクセスできます)。AllowPartiallyTrustedCallers 属性によってアセンブリ全体が既定で SecurityTransparent になるので、アセンブリの作成者は、特権付きの操作が必要なメソッドを明確に SecurityCritical または SecuritySafeCritical としてマークする必要があります。このようにマークしない限り、APTCA を使用して作成したコードは、MethodAccessException や TypeAccessException が発生したり、APTCA ライブラリで SecurityTransparent から危険な API を呼び出そうとしていることを示すその他のエラーが発生したりして、失敗することになります。

ここでの説明は、透過性モデルの概要にすぎません。詳細な解説については、MSDN ドキュメント、または Andrew Dai による以前の「CLR 徹底解剖」コラム (msdn.microsoft.com/magazine/ee677170.aspx) を参照してください。

バージョン 2 からバージョン 4 に移行する: 適用する属性について

バージョン 2 の APTCA アセンブリをバージョン 4 に移行する際に必要な作業のほとんどは、適切な透過性属性を特定して、その属性を必要とするメソッドに適用することに関係します。以下に、それぞれの属性が適している場合を説明するガイドラインを示します。

SecurityTransparent: セキュリティ上重要な操作を実行しないコードは、SecurityTransparent にします。

他の透過性設定と異なり、SecurityTransparent の動作は APTCA アセンブリの既定値なので、明示的にマークする必要はありません。他の属性が適用されていない場合、コードは透過的と見なされます。

透過的なコードのメリットの 1 つは安全なことです (危険な操作が許可されないため)。このため、SecurityCritical コードや特に SecuritySafeCritical コードほど入念なセキュリティ レビューは必要ありません。できる限り多くのコードを SecurityTransparent にすることをお勧めします。

以下の操作は、SecurityTransparent コードでは許可されません。

  • SecurityCritical が設定されたメソッドを呼び出す
  • アクセス許可またはアクセス許可セットをアサートする
  • 検証不可能なコード
  • アンマネージ コードを呼び出す
  • SecurityCritical が設定された仮想メソッドをオーバーライドする
  • SecurityCritical が設定されたインターフェイスを実装する
  • SecurityTransparent が設定されていない型からの派生する

SecuritySafeCritical: 部分的に信頼されたコードから呼び出すことができる一方で、潜在的に危険な API を呼び出す必要があるコードは、SecuritySafeCritical としてマークします。多くの場合、アクセス許可を要求するメソッドは、部分的に信頼されたコードと特権が必要な操作の間の保護された境界を表すため、このカテゴリに分類されます。

SecuritySafeCritical コードを使用すると、部分的に信頼された呼び出し元から危険な API に直接アクセスできるようになるため、SecuritySafeCritical は非常に強力な属性であり、慎重かつ控えめに適用する必要があります。SecuritySafeCritical コードでは、明確で安全な方法だけを使用して、呼び出し元に SecurityCritical 機能を公開することが重要です。通常は、SecuritySafeCritical コードで使用する予定の特定のリソースに呼び出し元がアクセスできるようにする要求を、SecuritySafeCritical コードに含めることをお勧めします。また、SecuritySafeCritical コードでは、入力と出力の両方を検証することも重要です (無効な値が渡されないようにし、返されたすべての情報を部分的な信頼に安全に渡すことができるようにするため)。

潜在的なセキュリティ リスクがあるため、SecuritySafeCritical コードの量は最小限に抑えることをお勧めします。

SecurityCritical: 部分的に信頼された呼び出し元に公開するのが安全ではないコードは、SecurityCritical としてマークする必要があります。以前のバージョンでリンク要求によって保護されていたメソッドには、おそらくこの属性が必要でしょう。

SecurityCritical コードは、透過的な (部分的に信頼された) 呼び出し元から直接呼び出せるわけではないため、SecuritySafeCritical ほど危険ではありません。しかし、SecurityCritical コードでは高度なセキュリティを必要とする操作を多数実行できるので、セキュリティ レビューの必要性を最小限に抑えるには、SecurityCritical コードも最小限に抑えることをお勧めします。

一般的なガイダンスとしてお勧めできるのは、コードをできる限り SecurityTransparent に設定することです。他のコードは、そのコードを介して透過的なコードから SecurityCritical コードにアクセスすることが特に想定されていない限り、SecurityCritical にします (想定されている場合は SecuritySafeCritical が適切です)。

SecAnnotate.exe を使用する

透過属性の適切な適用に役立つよう、Security Annotator (セキュリティ注釈ツール、SecAnnotate.exe) という新しい .NET Framework SDK ツールが開発されました。このツールでは、ユーザーのバイナリ (またはバイナリのコレクション) を使用し、透過属性をどこに適用するかについてのガイダンスを示します。このツールは、APTCA ライブラリをバージョン 4 に移行するうえで非常に役立ちます。

SecAnnotate は、対象のバイナリを数回繰り返し処理し、CLR のルールに従って透過性属性でマークする必要のあるメソッドを検索することで機能します。2 回目以降の処理では、それまでの繰り返し処理で提示された変更が原因で必要になる属性を検索します。たとえば、次の短いコード スニペットについて考えてみましょう (これは、APTCA アセンブリに含まれているものとします)。

static void Method1()
{
      Console.WriteLine("In method 1!");
      Method2();
}

static void Method2()
{
      PermissionSet ft = new PermissionSet(PermissionState.Unrestricted);
      ft.Assert();
      DangerousAPI();
      PermissionSet.RevertAssert();
}

SecAnnotate.exe では、Method2 メソッドでアクセス許可をアサートしているため、このメソッドを透過的にできないことがすぐに認識されます。1 回目の処理の後、Method2 を SecurityCritical または SecuritySafeCritical のいずれかにする必要があることが認識されます (透過的なコードから特にこのメソッドにアクセスする必要がない限り、SecurityCritical をお勧めします)。

バイナリの 1 回目の処理では、Method1 は注目されません。しかし、2 回目の処理では、Method1 から Method2 (SecAnnotate によって 1 回目の処理で SecurityCritical にするよう提示されたメソッド) を呼び出していることが認識されます。このため、Method1 も SecurityCritical (作成者の判断によっては SecuritySafeCritical) にする必要があることになります。2 回目の処理が終わると、SecurityCritical としてマークするよう指示するガイダンスが両方のメソッドに示されます。

SecAnnotate.exe の出力を理解する

セキュリティ注釈ツールの出力は、ツールによって特定された問題と、その推奨解決策が記述された XML ファイルです。場合によっては、先に提示した推奨事項がそれ以降の繰り返し処理によって無効になることもあります。このような場合、両方の推奨事項が XML に記述されます。複数の推奨事項がある場合は、繰り返しの回数を確認して、最新のつまり適切な推奨事項を理解する必要があります。

たとえば、図 2 に示すセキュリティ注釈ツールの出力を考えてみましょう。Logging.MethodA メソッドの annotations タグの下に、safeCritical タグと critical タグという 2 つのタグがあることに注目してください。これは、SecAnnotate により、分析中にこのメソッドに対して SecurityCritical 属性と SecuritySafeCritical 属性の両方が推奨されたことを意味します。

図 2 セキュリティ注釈ツールの出力

<requiredAnnotations>
    <assembly name="Logging">
      <type name="Logging">
        <method name="MethodA()">
          <annotations>
            <safeCritical>
              <rule name="MethodsMustOverrideWithConsistentTransparency">
                <reason pass="2" sourceFile="d:\repro\aptca\logging.cs" sourceLine="67">Critical method Logging.MethodA()’ is overriding transparent or safe critical method ‘Logging.MethodA()’ in violation of method override rules.  Logging.MethodA()’ must become transparent or safe-critical in order to override a transparent or safe-critical virtual method or implement a transparent or safe-critical interface method.</reason>
              </rule>
            </safeCritical>
            <critical>
              <rule name="TransparentMethodsMustNotSatisfyLinkDemands">
                <reason pass="1" sourceFile="d:\repro\aptca\logging.cs" sourceLine="68">Security transparent method Logging.MethodA()’ satisfies a LinkDemand for ‘FileIOPermissionAttribute’ on method ‘Logging.set_LogLocation(System.String)’.  Logging.MethodA()’ should become critical or safe-critical in order to call ‘Logging.set_LogLocation(System.String)’.</reason>
              </rule>
            </critical>
          </annotations>
        </method>
      </type>
    </assembly>
  </requiredAnnotations>

critical 要素の説明では、リンク要求で保護されているコードをこのメソッドで呼び出しているので、このメソッドを SecurityCritical または SecuritySafeCritical にする必要があることが示されています。SecurityCritical の方がセキュリティが強力なので、SecAnnotate.exe ではこの属性が既定の推奨事項になります。ここでは pass 属性の値が 1 であることに注目してください。つまり、この提示は SecAnnotate.exe による 1 回目のコード処理から出力されています。

次の推奨事項 (SecuritySafeCritical の提示) では、MethodA メソッドで透過的な基本メソッドをオーバーライドしているので、MethodA メソッドを SecurityTransparent または SecuritySafeCritical にする必要があることが示されています (このメソッドには基本メソッドと同じアクセス許可が必要です)。この情報を以前の推奨事項と組み合わせることで、SecAnnotate.exe では、MethodA メソッドを SecuritySafeCritical にするよう提示します。

pass="2" は、この推奨事項が SecAnnotate.exe による 2 回目のコード処理で出力されたことを示しています。これは、1 回目の処理中は、SecAnnotate.exe では MethodA メソッドを SecurityCritical にできないことが認識されていなかったので、この SecuritySafeCritical の要件がツールで認識されなかったためです。

SecuritySafeCritical の推奨事項は 2 回目の (より新しい) 処理で出力されたので、この場合に適した注釈です。

SecAnnotate.exe のベスト プラクティス

SecurityCritical と SecuritySafeCritical の両方が適切なマークの場合、セキュリティ注釈ツールでは、まずコードの既存の属性を優先し、次に SecurityCritical を優先します (比較的リスクが小さいため)。残念ながら多くの場合、この手法では、部分的に信頼された呼び出し元に対してすべてのエントリ ポイントがブロックされるため、セキュリティは確保されてもサンドボックスでは使用できないコードが作成されました。

SecuritySafeCritical は、透過的な (部分的に信頼された) コードから直接呼び出すことを意図した API や、そのような処理を念頭にセキュリティをレビューした API に適していることを忘れないでください。セキュリティ注釈ツールでは、部分的な信頼から呼び出すことを意図した API や、そのような方法で安全に呼び出せる API を特定できないので、SecuritySafeCritical としてマークされる API はごくわずかです。セキュリティ注釈ツールを使用している場合でも、ライブラリ作成者は一部のメソッドに"手動" で SecuritySafeCritical 属性を適用する必要があります。

セキュリティ注釈ツールで繰り返し処理を連続的に実行している間に、SecuritySafeCritical 属性が戦略的に配置されずに、透過的なコードで禁止されている 1 つの処理が複数の SecurityCritical マークに "クモの巣" 状に広がることがあるので、/p コマンド ライン スイッチを指定して SecAnnotate.exe を使用することをお勧めします。/p:x (x は数値) スイッチでは、セキュリティ注釈ツールに対して、必要な処理がなくなるまで実行するのではなく x 回だけ処理を繰り返すよう指示します。お勧めできるセキュリティ注釈ツールの使用方法は、次のとおりです。

  1. 「SecAnnotate.exe /p:1 /d:<参照先アセンブリのパス> <ファイル名.dll>」というコマンドを実行します。
          a. コマンドを実行すると、必要な箇所に透過性属性が追加されますが、処理は 1 回だけ実行されます。ここで処理を中止すると、作成者は手動で属性を確認できます。
          b. 既定では、SecAnnotate.exe では GAC だけを参照して、注釈対象のアセンブリの依存関係をチェックします。他のアセンブリには、/d スイッチでそれぞれのパスを指定する必要があります。
  2. 提示された属性でライブラリのソース ファイルを更新します。ただし、使用できる属性が複数ある場合を考慮し、適切な属性を判断します。SecAnnotate では SecurityCritical が優先されますが、場合によっては、SecuritySafeCritical が適切な属性のこともあります。
  3. アセンブリをビルドし直し、/p:1 スイッチを指定せずに手順 1. を繰り返します。/p:1 スイッチを指定してプログラムを繰り返し再実行することもできますが、指定しなくてもかまいません。必要な SecuritySafeCritical 属性は、手順 2. の 1 回目の繰り返し処理で既に適用されています。

この、手作業の開発を伴う繰り返し処理を実行すると、適切に注釈が付けられ、透過的なコードが最大限使用されたアセンブリが作成されます。

SecuritySafeCritical API の特定とレビュー

先ほど述べたように、SecAnnotate.exe では、API を SecurityCritical と SecuritySafeCritical のいずれかにするよう推奨するのが一般的です。これらの属性の大きな違いは、部分的な信頼から API を安全な方法で呼び出すことができるかどうかです。基になる重要な API やネイティブ API が安全な方法で (たとえば、要求、または入力と出力の検証によって) 呼び出されるようにするため必要なすべての検証を API で実行する場合は、その API を SecuritySafeCritical にできます (API の呼び出し元を透過的にすることができるので、場合によってはこの設定が適しています)。悪意のあるコードから保護されたリソースに API 経由でアクセスする方法がある場合、その API は SecurityCritical のままにする必要があります。

部分的に信頼されたコードに公開した場合のセキュリティ上の影響について、すべての SecuritySafeCritical コードを入念にレビューすることが重要です。SecuritySafeCritical コードと SecurityCritical コードの両方を最小限に抑える必要がありますが、適切な属性を明確に判断できない場合は、SecurityCritical を適用する方が安全です。

透過性属性を適用する

透過性属性の適用は、コード内にある他の .NET 属性の適用と同じくらい簡単です。属性の使用法に関するドキュメントについては、次の型についての MSDN ドキュメントを参照してください。

  • SecurityTransparentAttribute
    この属性は、アセンブリ レベルでのみ適用できることに注意してください。その場合、アセンブリ内のすべての型とメソッドが透過的になることを表します。これは APTCA アセンブリの既定の透過性設定なので、型レベルまたはメソッド レベルでは必要ありません。
  • SecuritySafeCriticalAttribute
  • SecurityCriticalAttribute

C# では、属性の適用は次のようになります。

[SecurityCritical]
public static void Method1()
{ /* Do something potentially dangerous*/ }

[SecuritySafeCritical]
public static void Method2()
{ /* Do something potentially dangerous in a safe way that can be called from partial trust */ }

レベル 1 とレベル 2

透過性と APTCA に関する最後の注意点は、アセンブリ レベルの属性を使用すると、新しいバージョン 4 の APTCA の動作の代わりに以前のバージョン 2 の動作を使用できることです。新しいバージョンは、セキュリティがより強化され、監査しやすく、Silverlight とデスクトップ CLR の間の共通性が高いため、バージョンの動作を切り替える手法はお勧めしません。それでも、移行できるようになるまでの短期間、互換性が必要となることがあります。この場合、SecurityRules 属性を使用すると、アセンブリで以前にバージョン 2 のルールを強制的に使用できます。

SecurityRules 属性は、SecurityRuleSet 列挙型のパラメーターを受け取ります。SecurityRuleSet.Level1 は互換性の確保を指定します。SecurityRuleSet.Level2 は新しいバージョンを指定しますが、Level2 属性は既定値なので必ずしも指定しなくてもかまいません。しかし、使用している透過性ルール セットを明示的に指定して、将来、.NET Framework の既定のルール セットが変更された場合に対処できるようにしておくと便利です。

C# では、この属性の適用は次のようになります。

[assembly:SecurityRules(SecurityRuleSet.Level1)]

よくある落とし穴

APTCA ライブラリの作成者がバージョン 2 からバージョン 4 に移行する際に注意すべき一般的な問題をいくつか以下にまとめます。

  • SecAnnotate.exe では、LinkDemand を SecurityCritical 属性に置き換えることが推奨されます (この属性は FullTrust の LinkDemands に非常によく似ています)。ただし、(メソッドではなく) 型が LinkDemand で保護されていた場合、このように変更しても、SecurityCritical 属性をバージョン 4 の型に適用する場合と同じ動作にはなりません。バージョン 2 の型レベルの LinkDemand に動作が近づくため、SecurityCritical 属性をこの型のすべてのメンバーに適用することをお勧めします。
  • 部分的に信頼されたコードで十分機能すると想定されている、アクセス許可のレベルが低い LinkDemand は、適切に SecurityCritical に変換されないことがあります。低いアクセス許可に対して LinkDemand が設定されている場合 (たとえば、特定の安全なパスに対する読み取りのアクセス許可など)、LinkDemand を削除して、同じアクセス許可の完全な要求に置き換える方が適しています。このようにすると、部分的に信頼されたコードでも引き続き API を呼び出すことができます (ただし要求では、アクセス許可のレベルが十分に高い、部分的に信頼されたコードだけが呼び出しに成功するようにします)。
  • 一般に、型レベルの透過性属性は、変更した型のメンバーにも適用されます。また、最も外側の属性が他の属性に優先されます。したがって、[SecurityCritical] 属性をメソッドに適用する処理は、たとえばこのメソッドが含まれている型に [SecuritySafeCritical] 属性が適用されている場合、効果がありません。通常、[SecuritySafeCritical] 属性は型レベルで役立つ属性ではありません。さらに、後から新しいメンバーを型に導入する際にこの型が SecuritySafeCritical だと気が付かないことは十分に考えられるので、セキュリティ ホールが発生する可能性があります。

型レベルの属性は、変更した型の新しいメンバーにも適用されますが、オーバーライドされたメンバーには適用されません。型レベルの透過性属性を使用する場合は、必要に応じて、オーバーライドされたメンバーにも明確に属性を追加してください。

移行の例

図 3 は、バージョン 2 で作成した単純な (不完全な) ログ ライブラリです。

図 3 バージョン 2 の APTCA ライブラリ

using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

// This assembly is meant to be representative of a simple v2 APTCA assembly
// It has some dangerous code protected with demands/link demands
// It exposes some dangerous code in a controlled way with an assert

[assembly: AllowPartiallyTrustedCallers]
public class Logging
{
    private string logLocation = @"C:\temp\firstfoo.txt";

    public virtual string Usage()
    {
        return "This is a helpful string";
    }

    public virtual string LogLocation
    {
        get
        {
            return logLocation;
        }

        [FileIOPermissionAttribute(SecurityAction.LinkDemand, Unrestricted=true)]
        set
        {
            logLocation = value;
        }
    }

    public virtual void SetLogLocation(int index)
    {
        switch (index)
        {
            case 1: 
                LogLocation = @"C:\temp\foo.txt";
                break;
            case 2:
                LogLocation = @"D:\temp\foo.txt";
                break;
            case 3:
                LogLocation = @"D:\repro\temp\foo.txt";
                break;
            default:
                break;
        }
    }


    public virtual void DeleteLog()
    {
        FileIOPermission fp = new FileIOPermission(FileIOPermissionAccess.AllAccess, LogLocation);
        fp.Assert();
        if (File.Exists(LogLocation)) { File.Delete(LogLocation); }
        SecurityPermission.RevertAll();
    }

    // TODO : Put other APIs (creating log, writing to log, etc) here
}

public class OtherLogging : Logging
{
    public override string Usage()
    {
        LogLocation = null;
        return "This is a different useful string";
    }

    // TODO : Put other APIs (creating log, writing to log, etc) here
}

バージョン 4 に移行し、同じライブラリに変更点を説明するコメントを追加したものを図 4 に示します。

図 4 バージョン 4 の APTCA ライブラリ

using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

// This assembly is meant to be representative of a simple v2 APTCA assembly
// It has some dangerous code protected with demands/link demands
// It exposes some dangerous code in a controlled way with an assert

[assembly: AllowPartiallyTrustedCallers]
public class Logging
{
    private string logLocation = @"C:\temp\firstfoo.txt";
    
    // This API can be transparent because it does nothing dangerous.
    // Transparent APIs need no attributes because it is the default behavior of a v4
    // APTCA assembly
    public virtual string Usage()
    {
        return "This is a helpful string";
    }

    // Note that transparency attributes do not go directly on properties.
    // Instead, they go on the getters and setters (even if the getter and setter
    // get the same attributes)
    public virtual string LogLocation
    {
        get
        {
            return logLocation;
        }

        // This API is made critical because it sets sensitive data (the path to write to)
        // which partial trust code should not be able to do.
        [SecurityCritical]
        // The previous LinkDemand is removed as the SecurityCritical attribute replaces it
    //[FileIOPermissionAttribute(SecurityAction.LinkDemand, Unrestricted=true)]
        set
        {
            logLocation = value;
        }
    }

    // This API accesses a critical member (LogLocation) and, therefore, cannot be transparent
    // However, the access is done in a limited, safe way and we expect transparent code
    // should be able to call this API. Therefore, it is SecuritySafeCritical
    [SecuritySafeCritical]
    public virtual void SetLogLocation(int index)
    {
        switch (index)
        {
            case 1: 
                LogLocation = @"C:\temp\foo.txt";
                break;
            case 2:
                LogLocation = @"D:\temp\foo.txt";
                break;
            case 3:
                LogLocation = @"D:\repro\temp\foo.txt";
                break;
            default:
                break;
        }
    }

    // This API is potentially dangerous; it asserts which means it can’t be transparent
    // Because setting LogLocation is protected, however, partial trust code can safely
    // call this API. In fact, it is intended that it is safe for partial trust code
    // to call this method. Therefore, it is SecuritySafeCritical
    [SecuritySafeCritical]
    public virtual void DeleteLog()
    {
        FileIOPermission fp = new FileIOPermission(FileIOPermissionAccess.AllAccess, LogLocation);
        fp.Assert();
        if (File.Exists(LogLocation)) { File.Delete(LogLocation); }
        SecurityPermission.RevertAll();
    }

    // TODO : Put other APIs (creating log, writing to log, etc) here
}

public class OtherLogging : Logging
{
    // The logic for attributing this method is complicated and it is an example of when
    // SecAnnotate.exe can be very helpful. This API cannot be transparent because it 
    // calls a critical member (LogLocation). However, because it overrides a transparent
    // method (Usage) it cannot be critical. Therefore, the only possible annotation here
    // is SecuritySafeCritical and it is the author’s responsibility to make sure that
    // a malicious caller cannot abuse that access.
    [SecuritySafeCritical]
    public override string Usage()
    {
        LogLocation = null;
        return "This is a different useful string";
    }

    // TODO : Put other APIs (creating log, writing to log, etc) here
}

CLR セキュリティ システムと Silverlight CoreCLR セキュリティ システムを同期する

APTCA とバージョン 4 の透過性の統合作業は複雑に思えますが、最終的には、機密性の高いシステム リソースを部分的に信頼された呼び出し元から単純かつ効果的に保護できます。しかも、この変更によって、デスクトップ CLR セキュリティ システムと Silverlight CoreCLR セキュリティ システムが同調されます。

SecAnnotate.exe、FxCop 規則 (透過性を検証できるツール) などの SDK ツールを使用すると、容易に移行できるようになります。バージョン 4 の APTCA アセンブリは、これまでよりはるかに監査が容易です。SecuritySafeCritical API (およびこの API で行われる SecurityCritical の呼び出し) の詳細を参照するだけで、アセンブリのセキュリティが確保されているかどうか確実に確認できます。

多くの場合、透過的なコードはアセンブリの 80 ~ 90% に及ぶので、監査の負担が大きく軽減されます。透過性の詳細について興味をお持ちの方は、MSDN ドキュメントのさらに充実した解説を参照してください。

 

Mike Rousos は、2005 年以来、マイクロソフトの CLR チームでソフトウェア設計エンジニアとしてテストに携わってきました。主な仕事内容は、CLR セキュリティ システムの設計と実装における品質確保です。

この記事のレビューに協力してくれた技術スタッフの Andrew Dai、Cristian Eigel、および Shawn Farkas に心より感謝いたします。