CLRの除外フィルター

こんにちはハブラ 今日は、C#開発者が直接アクセスできないCLRメカニズムの1つ、例外フィルターについて説明します。

私の友人のC#プログラマーの調査では、彼らが(もちろん)このメカニズムを使用したことがなく、その存在についても知らないことが示されました。 したがって、私はすべての好奇心の強い人に記事のテキストを読むように勧めます。

そのため、例外フィルターは、 catchが、このブロックでキャッチされるために例外が満たさなければならない前提条件catch宣言できるメカニズムです。 このメカニズムは、 catch内でチェックを実行するようには機能しませ

カットの下には、VB.NET、F#、CIL、およびC#コード、およびフィルターメカニズムを処理するためのさまざまな逆コンパイラーのチェックがあります。

例外フィルターがある場合


例外フィルターはCLRに組み込まれており、例外を処理するときに環境が使用するメカニズムの1つです。 シーケンスは次のとおりです。



適切なcatchの検索フェーズではcatch CLRは例外ハンドラーの内部スタックをクロールし、例外フィルターも実行します。 これは、 finallyブロックでコードが実行されるに発生すること注意してください。 この点については後で説明します。

VB.NETでの表示

VB.NET言語でネイティブにサポートされている例外フィルター。 フィルターを使用するコードの例を次に示します。

 Sub FilterException() Try Dim exception As New Exception exception.Data.Add("foo", "bar1") Console.WriteLine("Throwing") Throw exception Catch ex As Exception When Filter(ex) '   Console.WriteLine("Caught") Finally Console.WriteLine("Finally") End Try End Sub Function Filter(exception As Exception) As Boolean Console.WriteLine("Filtering") Return exception.Data.Item("foo").Equals("bar") End Function 


このコードが実行されると、次のメッセージチェーンが発行されます。

 Throwing Filtering Caught Finally 


F#での表示

この記事を準備する中で、F#が例外フィルターをサポートしていることをインターネットで発見しました。 まあ、それをチェックしてください。 サンプルコードを次に示します。

F#のコード
 open System let filter (ex : Exception) = printfn "Filtering" ex.Data.["foo"] :?> string = "bar" let filterException() = try let ex = Exception() ex.Data.["foo"] <- "bar" printfn "Throwing" raise ex with //   | :? Exception as ex when filter(ex) -> printfn "Caught" [<EntryPoint>] let main argv = filterException() 0 



このコードは、通常のcatch [mscorlib]System.Objectを使用して、フィルターなしでコンパイルします。 F#コンパイラーで例外フィルターを作成することはできませんでした。 これを行う別の方法を知っている場合は、コメントを歓迎します。

CILでの表示

CIL(Common Intermediate Language)は、.NETマシンの低レベルアセンブリ言語に類似しています。 コンパイルされたアセンブリは、 ilasmツールを使用してこの言語に分解し、.NETに同ilasmれているilasmを使用してilasmことができます。

ildasm見たVB.NETのコードスニペットを以下にildasmます。

多くのCILコード
 .method public static void FilterException() cil managed { // Code size 110 (0x6e) .maxstack 3 .locals init ([0] class [mscorlib]System.Exception exception, [1] class [mscorlib]System.Exception ex) IL_0000: nop IL_0001: nop .try { .try { IL_0002: newobj instance void [mscorlib]System.Exception::.ctor() IL_0007: stloc.0 IL_0008: ldloc.0 IL_0009: callvirt instance class [mscorlib]System.Collections.IDictionary [mscorlib]System.Exception::get_Data() IL_000e: ldstr "foo" IL_0013: ldstr "bar" IL_0018: callvirt instance void [mscorlib]System.Collections.IDictionary::Add(object, object) IL_001d: nop IL_001e: ldstr "Throwing" IL_0023: call void [mscorlib]System.Console::WriteLine(string) IL_0028: nop IL_0029: ldloc.0 IL_002a: throw IL_002b: leave.s IL_006b } // end .try filter { IL_002d: isinst [mscorlib]System.Exception IL_0032: dup IL_0033: brtrue.s IL_0039 IL_0035: pop IL_0036: ldc.i4.0 IL_0037: br.s IL_0049 IL_0039: dup IL_003a: stloc.1 IL_003b: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception) IL_0040: ldloc.1 IL_0041: call bool FilterSamples.VbNetFilter::Filter(class [mscorlib]System.Exception) IL_0046: ldc.i4.0 IL_0047: cgt.un IL_0049: endfilter } // end filter { // handler IL_004b: pop IL_004c: ldstr "Caught" IL_0051: call void [mscorlib]System.Console::WriteLine(string) IL_0056: nop IL_0057: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError() IL_005c: leave.s IL_006b } // end handler } // end .try finally { IL_005e: nop IL_005f: ldstr "Finally" IL_0064: call void [mscorlib]System.Console::WriteLine(string) IL_0069: nop IL_006a: endfinally } // end handler IL_006b: nop IL_006c: nop IL_006d: ret } // end of method VbNetFilter::FilterException 



ご覧のとおり、VB.NETコンパイラーはもちろん、コードをCILの形式で大きく塗りつぶしました。 最も興味があるのはfilterブロックです。

 filter { // ,      System.Exception: IL_002d: isinst [mscorlib]System.Exception IL_0032: dup IL_0033: brtrue.s IL_0039 IL_0035: pop IL_0036: ldc.i4.0 //   -  : IL_0037: br.s IL_0049 IL_0039: dup //  -  : IL_003a: stloc.1 IL_003b: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception) //  ,     : IL_0040: ldloc.1 IL_0041: call bool FilterSamples.VbNetFilter::Filter(class [mscorlib]System.Exception) IL_0046: ldc.i4.0 IL_0047: cgt.un IL_0049: endfilter } // end filter 


そのため、コンパイラーは、フィルターブロックで例外の種類のチェックと、関数の呼び出しを発行しました。 フィルターブロックの実行の最後に値1スタックにある場合、このフィルターに対応するcatchブロックが実行されます。 それ以外の場合、いいえ。

C#コンパイラーはfilterブロックで型チェックを行わず、型を示す特別なCIL構造を使用することに注意してください。 つまり、C#コンパイラはfilterメカニズムをまったく使用しません。

ところで、このブロックを生成するには、 ILGenerator.BeginExceptFilterBlockメソッドを使用できます(独自のコンパイラを作成している場合)。

逆コンパイラでの表示

このセクションでは、いくつかの有名なツールを使用して結果のコードを逆コンパイルし、何が起こるかを確認します。

最新のJetBrains dotPeek 1.1は 、フィルターを使用してアセンブリを逆コンパイルしようとしたときに、次のことを喜んで報告しました。

 public static void FilterException() { // ISSUE: unable to decompile the method. } 


.NET Reflector 8.2はより適切に動作し、C#で何かを逆コンパイルできました。

 public static void FilterException() { try { Exception exception = new Exception(); exception.Data.Add("foo", "bar"); Console.WriteLine("Throwing"); throw exception; } catch when (?) { Console.WriteLine("Caught"); ProjectData.ClearProjectError(); } finally { Console.WriteLine("Finally"); } } 


まあ、悪くない-コードはコンパイルされていませんが、それによってフィルターを見ることができます。 フィルターが復号化されなかったという事実は、C#トランスレーターの欠点に起因する可能性があります。 VB.NETのトランスレーターでも同じことを試してみましょう。

 Public Shared Sub FilterException() Try Dim exception As New Exception exception.Data.Add("foo", "bar") Console.WriteLine("Throwing") Throw exception Catch obj1 As Object When (?) Console.WriteLine("Caught") ProjectData.ClearProjectError Finally Console.WriteLine("Finally") End Try End Sub 


残念ながら、この試みは同じように失敗しました-何らかの理由で、逆ildasmはフィルター関数の名前を判別できませんでした(ただし、上記で見たように、 ildasmは素晴らしい仕事をしました)。

ここで説明したツールは、.NET4.5フィルターコードではうまく機能しないと推測できます。

これは、 catch本体のチェックとどのように違いますか


VB.NETコードとほぼ同様のコードを考えてみましょう。

C#コード
 static void FilterException() { try { var exception = new Exception(); exception.Data["foo"] = "bar"; Console.WriteLine("Throwing"); throw exception; } catch (Exception exception) { if (!Filter(exception)) { throw; } Console.WriteLine("Caught"); } } static bool Filter(Exception exception) { return exception.Data["foo"].Equals("bar"); } 



次に、C#とVB.NETの例の動作の違いを見つけてみましょう。 とても簡単throw;throw; C#では、スタック上の行番号が失われます。 false返すようにフィルターを変更すると、アプリケーションはメッセージでクラッシュします

 Unhandled Exception: System.Exception: Exception of type 'System.Exception' was thrown. at CSharpFilter.Program.FilterException() in CSharpFilter\Program.cs:line 25 at CSharpFilter.Program.Main(String[] args) in CSharpFilter\Program.cs:line 9 


スタックから判断すると、19行目( throw exception; throw; )ではなく、25行目(ラインのthrow; )で例外がスローされました。 同じ条件下でのVB.NETコードは、例外の最初の場所を示しています。

UPD。 当初、私は誤ってそのthrow;書きましたthrow; スタック全体が失われますが、コメントでは、これは実際にはまったくないことが示唆されています。 スタックの行番号にわずかな変更のみがあります。 さらに、これはモノでは再生されません-例外スタックはthrow;後に変更されませんthrow; (これらの詳細についてはkekekeksに感謝します )。

セキュリティについて


ブログのEric Lippertは、例外フィルターにより、悪意のある側が昇格した特権でコードを実行できる場合があると考えています。

要するに、外部の潜在的に破壊的なコードに対して一時的な特権エスカレーションを実行している場合、 finallyに依存することはできません。 finallyブロックを実行する前に、コールスタックの上にある例外フィルターを呼び出すことができます(そして、攻撃者はこれらのフィルターのコードで何でもできます)。 要確認-正しいcatch見つけることは、 finallyブロックが実行される前に必ず行われます。

おわりに


今日は、C#でめったに出会わないCLRプログラマーの1人に注目しました。 私自身はVB.NETには書き込みませんが、この情報は.NETプラットフォームのすべての開発者に役立つと信じています。 このプラットフォーム用の言語、コンパイラ、またはデコンパイラを開発している場合、この情報は特に役立ちます。

PS。 コード、画像、記事のテキストはgithub: github.com/ForNeVeR/ClrExceptionFiltersに投稿されています。

Source: https://habr.com/ru/post/J192144/


All Articles