Dela via


Where Does the Stack Walk Start or: Why Do Demands from Main Always Succeed?

When starting to play with CAS a lot of people come up with toy programs that simply do a Demand for some permission or another, then copy that program to various locations that will cause it to be granted different permission sets.  It's generally a surprise when those demands pass, even though caspol confirms that their assemblies aren't being granted the permission that is being demanded.

The reason for this has to do with which stack frame the CLR starts at when doing the stack walk: instead of starting with the method that calls Demand(), we start with that method's caller.  This means that doing a Demand in the Main method of an exe will never cause a SecurityException, even if that program wasn't granted the permission that was demanded since Main() doesn't have a caller on the call stack.  (For instance, if you tried to pull the IsFullTrust property into Main in this post, you would always think that you were running FullTrust since the Demand will never throw).

For instance, lets look at two Main methods, both in exes on a file share:

public static void Main()
{
    try
    {
        new FileIOPermission(FileIOPermissionAccess.Read, @"c:\boot.ini").Demand();
        Console.WriteLine("No exception");
    }
    catch(SecurityException)
    {
        Console.WriteLine("Got a SecurityException");
    }
}

public static void Main()
{
    try
    {
        File.OpenText(@"c:\boot.ini");
        Console.WriteLine("No exception");
    }
    catch(SecurityException)
    {
        Console.WriteLine("Got a SecurityException");
    }
}

In this case, the call stacks at the time of the Demand essentially look like:

Demand() Demand()
Main() File::Read()
Main()

Applying the rule where the caller of the method which calls Demand() is the first on the stack walk, the first program begins its stack walk at Main's caller, which doesn't exist.  Therefore, since no frames on the stack walk were not granted FileIOPermission, the demand succeeds and no exception is thrown.  In the second case, the rule tells us that Main is the first frame to check on the stack walk ... and since Main is not granted FileIOPermission to c:\boot.ini a SecurityException is thrown.  And in fact, this is exactly the behavior we see when running the two programs.

At first glance this might appear to be a security issue, after all we're skipping over a method that clearly does not have the permissions being requested.  However, since the method we're skipping over is the one which contains the code to call Demand(), if that demand did cause a SecurityException, then the person attempting to do whatever it is that they're being denied access to could simply omit calling Demand() in the first place.  In fact, most malicious code isn't security conscious enough to ensure that it's allowed permission to do whatever evil action it wants to anyway ... "Oh, the user doesn't want me to upload their Microsoft Money data to my server.  Well, in that case I guess I'll just move on." :-)

Comments

  • Anonymous
    December 06, 2005
    Shawn,

    Interesting post...Personally, I find it more interesting that the reason why program 2 fails is because File.OpenText() takes the responsiblity for performing the FileIOPerm demand when creating and opening an instance of the FileStream class.

    I think that hits home the point that - maybe for anyone developing a library/framework - you need to take ownership and/or responsiblity of what your code is about to do.

    Maybe, more simply put, adds fuel to the fire for security code reviews...

    Thanks again for the great post.

    H

  • Anonymous
    November 06, 2006
    I read following note on MSDN : The optimization behavior for the demand operation differs between 64 bit and 32 bit platforms. On 64 bit platforms, a demand will not check the grant set of the assembly containing the demand in cases where no other calling assemblies are present. However, this optimization does not cause an elevation of privilege because a stack walk is still performed when calling assemblies are present. On 32 bit platforms the demand operation checks the grant set of the assembly containing the demand and all calling assemblies. Why is there such a difference between 2 versions ?

  • Anonymous
    November 14, 2006
    We do not check the demanding assembly on either architecture -- can you leave feedback on the MSDN page so that the documentation can be corrected? -Shawn