📜 ⬆️ ⬇️

CLR Exception Filters

Hi, habra people. Today we will look at one of the CLR mechanisms that is not directly available to C # developers — exception filters.

A survey among my familiar C # programmers showed that they (by themselves) never used this mechanism and did not even know about its existence. Therefore, I suggest that all inquisitive people read the text of the article.

So, exception filters are a mechanism that allows a catch declare preconditions that an exception must satisfy in order to be caught by a given block. This mechanism does not work in exactly the same way as performing checks inside a catch .
')
Under the cut - the code on VB.NET, F #, CIL and C #, as well as checking various decompilers for processing the filter mechanism.

From where there are exception filters


Exception filters are built into the CLR and are one of the mechanisms that the environment uses when handling exceptions. The sequence is as follows:



In the search for a suitable catch CLR crawls its internal stack of exception handlers and also performs exception filters. Note that this happens before the code is executed in the finally block. We will discuss this point later.

How it looks on VB.NET

Exception filters are natively supported by the VB.NET language. Here is an example of what the code using filters looks like:

 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 


When executing this code, the following message chain will be displayed:

 Throwing Filtering Caught Finally 


How it looks in F #

When preparing the article, I found information on the Internet that F # supports exception filters. Well, check it out. Here is a sample code:

F # code
 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 



This code is compiled without filters, with the usual catch [mscorlib]System.Object . I never managed to force the F # compiler to make an exception filter. If you know of alternative ways to do this - welcome to the comments.

What it looks like in CIL

CIL (Common Intermediate Language) is an analogue of a low-level assembly language for a .NET machine. Compiled assemblies can be disassembled into this language using the ilasm tool, and collected back using the ilasm that comes with .NET.

I will give a code fragment on VB.NET, as I saw it in ildasm :

A lot of CIL code
 .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 



As you can see, the VB.NET compiler, of course, strongly painted our code in the form of CIL. We are most interested in the filter block:

 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 


So, the compiler has passed an exception type check in the filter block, as well as a call to our function. If at the end of the execution of the filter block the value is 1 on the stack, then the catch block corresponding to this filter will be executed; otherwise not.

It is worth noting that the C # compiler does not put type checks into the filter block, but uses a special CIL construct with a type indication. That is, the C # compiler does not use the filter mechanism at all.

Incidentally, to generate this block, you can use the ILGenerator.BeginExceptFilterBlock method (if you are writing your own compiler).

What it looks like in a decompiler

In this section, I will try to decompile the resulting code with a few well-known tools and see what happens.

The last JetBrains dotPeek 1.1 when trying to decompile a filter assembly happily reported the following:

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


.NET Reflector 8.2 entered more adequately and was able to decompile something in 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"); } } 


Well, not bad - although the code is not compiled, but at least you can see the presence of a filter. The fact that the filter has not been decrypted can be attributed to the shortcomings of the C # -translator. Let's try the same thing with the translator in 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 


Alas, the attempt failed in the same way - for some reason the decompiler could not determine the name of the filtering function (although, as we saw above, ildasm did a fine job with this).

I can only assume that the tools reviewed so far do not work well with the .NET4.5 filter code.

How is this different from checks in the body of a catch


Consider a code snippet almost similar to the code on VB.NET:

C # code
 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"); } 



And now let's try to find the difference in behavior between examples in C # and VB.NET. It's quite simple: a throw; expression throw; in C # it loses the row number in the stack. If you change the filter so that it returns false , the application will drop with the message

 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 


Judging by the stack, an exception was generated on the 25th line (the string throw; ), and not on the line 19 ( throw exception; ). The code on VB.NET in the same conditions shows the original location of the exception.

UPD. Initially, I mistakenly wrote that throw; loses the whole stack, but in the comments suggested that this is really not at all the case. There is only a minor modification of the line number in the stack. Moreover, on mono this is not reproduced - the exception stack does not change there after throw; (thanks to kekekeks for these details).

About security


Eric Lippert in his blog examines the situation when exception filters allow a malicious party to execute its code with elevated privileges in some cases.

In short: if you perform a temporary privilege escalation for some kind of external and potentially destructive code, then you cannot rely on finally , because before the finally block is executed, exception filters may be called that are located higher in the call stack (and the attacker can get whatever he wants in the code of these filters). Remember - finding the right catch always done before the finally block is executed.

Conclusion


Today, we looked at one of the rare CLR programmers on C # mechanisms. I myself do not write on VB.NET, but I believe that this information may be useful to all developers of the .NET platform. Well, if you are developing languages, compilers or decompilers for this platform, then all the more this information is useful to you.

Ps. The code, images and text of the article are posted on github: github.com/ForNeVeR/ClrExceptionFilters .

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


All Articles