All About Assert Part I: What Assert Actually Does

There are several common misconceptions about the Assert stack modifier, not the least of which are:

  • Assert changes an assembly's permission grant
  • Assert is just a perf optimization
  • You don't need the permissions that you're Asserting in order to effectively Assert them
  • Assert will last for the lifetime of the process
  • Assert stops all stack walks from proceeding further up the call chain

Lets take a quick look at what Assert actually does, before addressing these misconceptions.  Asserting a permission or permission set is your method's way of saying that it vouches for every method that is calling into it; that the stack walk should go no further.

Looking at an example might make things a little more clear.  Lets say I have a shared assembly, Pi.dll, which lives in the GAC and will get FullTrust. Pi.dll contains one class, Pi, which has a Value property that exposes the value of Pi to one million digits.  Obviously this is an expensive calculation, so as a performance optimization, I write the calculated value out to %TEMP%\pi.txt, and use this cached value as long as its available.

Now, I write an application that sits on my web site, and calculates the area of a circle.  It requires that the Pi library exist on the user's machine, and uses the very accurate Pi value in order to provide accurate results of its own.  However, since the application runs from the Internet it does not get very many permissions, and certainly doesn't have Environment permission to read %TEMP% or File IO permission to read from and write to %TEMP%\pi.txt.

When calling into my Pi class, the call stack looks like this:

  Call Stack                            Grant               Requires
 ==================================================================
 mscorlib.dll!System.IO.File::.ctor()  FullTrust           FileIO
 Pi.dll!Pi::GetCachedValue()           FullTrust           Environment, FileIO
 Pi.dll!Pi::get_Value()                FullTrust           Execution
 CircleCalc.exe!CircleCalc::Main()     Internet            Execution
 

Now, when the File constructor does its demand for FileIOPermission, its going to find that Pi::GetCachedValue has this permission, and Pi::get_Value also has this permission.  Then it will hit CircleCalc::Main() and find that the Internet permission set does not contain FileIO permission, causing a SecurityException.

Since GetCachedValue is only opening a very specific file, and does not allow the calling code arbitrary access to the file system, file's contents, or environment, and since I've done a full security review on its code, I've decided to enable this scenario.  I can do that by calling Assert() for Environment and FileIO permission in GetCachedValue(), which will create a situation like the following:

  Call Stack                            Grant               Requires
 ==================================================================
 mscorlib.dll!System.IO.File::.ctor()  FullTrust           FileIO
 Pi.dll!Pi::GetCachedValue()           FullTrust           Environment, FileIO
--------------------Assert(Environment, FileIO)-------------------
 Pi.dll!Pi::get_Value()                FullTrust           Execution
 CircleCalc.exe!CircleCalc::Main()     Internet            Execution
 

Now, when the File constructor demands FileIO permission it is going to check GetCachedValue(), find that it has permission, then hit the wall that the Assert put up, and goes no further up the stack.  Since everything checked so far has enough trust, the demand succeeds, and my untrusted application has caused a file on the local disk to be read.

Obviously this can be very dangerous, and only code that has been very carefully looked at should call Assert.  For instance, if in the example above there was a utility method SetCacheFile(string path), where the user could set the cache file, and the return value of Pi::Value was simply the bytes contained in that file, this code would be horribly unsafe.

In order to reduce the risk of a malicious caller being able to take advantage of the fact that your code is going to Assert permissions, one of the easiest things to do is not Assert until just before you need to use the given permission, and always RevertAssert immediately after you are done with the asserted permissions.