Don't Deny SkipVerification

SkipVerification permission, which allows the JIT to compile any code even if it cannot prove the safety of that code, is a bit of a special permission.  For instance, it's the only permission which causes an exception other than SecurityException when it is missing in a required situation.  It's also the only permission which is not always checked for using a stack walk.

Suppose you wrote the following code:

public static void UnverifiableCaller()
{
    byte[] array1 = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    byte[] array2 = new byte[10];

    new SecurityPermission(SecurityPermissionFlag.SkipVerification).Deny();
    MemCpy(array2, array1, array1.Length);
}

private unsafe static void MemCpy(byte[] lhs, byte[] rhs, long count)
{
    fixed(byte *pLhs = lhs, pRhs = rhs)
    {
        byte *src = pRhs;
        byte *dest = pLhs;

        for(long i = 0; i < count / 8; i++, src += 8, dest += 8)
            *((long *)dest) = *((long *)src);
        for(long i = 0; i < count % 8; i++, src++, dest++)
            *dest = *src;
    }
}

You might expect that the call to MemCpy wouldn't work, since you've denied SkipVerification permission, and clearly the MemCpy method is not verifiable.  (In fact, it's blatantly unsafe.  If I call it with a count that's too large, I'll end up writing all over memory).  However, if you compile (with the C# /unsafe flag), and run this code, you'll see that you don't get a VerificationException or even a SecurityException.

This can seem very surprising until you take a step back to think about it.  IL verification is done at JIT time, and the JIT runs on each method as it's called for the first time.   If the JIT were to check the call stack to ensure that everyone has SkipVerification permission, then every time a method got called for the first time, there would need to be a full stack walk.  Obviously that would have a large negative impact on performance.

But the problem goes even deeper than that.  Let's say that before the above snippet of code was run, another code path decided to call MemCpy(), and this time everything on the call stack had SkipVerification permission.  In this hypothetical world where the JIT does do stack walks, it would notice that it's OK to compile the unverifiable code, and go on its way.  Now, when our code comes along later in the program, it calls MemCpy(), but since the method was already compiled, the JIT is not invoked again.  This means that even though UnsafeCaller() does not have SkipVerification, it would be allowed to call the unverifiable code anyway.  In order to solve this problem, there would have to be a security stub inserted before each method call to check the call stack for any callers that did not have SkipVerification.

NGEN adds yet another level of complexity to the problem.  Since an NGENed image is JITed before it's ever run, the NGEN process has no idea if callers will be granted SkipVerification or not.  In order to deal with this situation, NGEN would have to emit two versions of each method, one if SkipVerification is allowed, and one if it is not.

So what does the JIT look at to see if it can compile unverifiable IL?  It checks that the assembly's grant set does contain SkipVerification, and that it's refused set does not contain SkipVerification.  That means that putting MemCpy in an assembly that had a RequestRefuse for SkipVerification would result in a VerificationException in UnsafeCaller(), regardless of what assembly UnsafeCaller() was placed in.  The same holds true if MemCpy() was in an assembly on a network share, since the default policy will only grant SkipVerification to local assemblies.  In fact, in the default policy the only named permission set that does contain SkipVerification is FullTrust.  The Everything permission set doesn't even contain it -- it's basically every permission that ships with the framework, with the exception of SkipVerification.

One thing to note is that the analysis above applies only to the JIT checking for SkipVerification before compiling a method (situations that would lead to a VerificationException or an InvalidProgramException if SkipVerification was not present).  If you're actually going to do a demand for SkipVerification, then the normal CAS rules apply.  That means that if I changed MemCpy() to:

[SecurityPermission(SecurityAction.Demand, SkipVerification = true)]
private unsafe static void MemCpy(byte[] lhs, byte[] rhs, long count)
{
    fixed(byte *pLhs = lhs, pRhs = rhs)
    {
        byte *src = pRhs;
        byte *dest = pLhs;

        for(long i = 0; i < count / 8; i++, src += 8, dest += 8)
            *((long *)dest) = *((long *)src);
        for(long i = 0; i < count % 8; i++, src++, dest++)
            *dest = *src;
    }
}

UnsafeCaller() from the first example would produce a SecurityException, since the explicit demand would cause a stack walk, which would have encountered the deny.

OK, if I want to prevent an assembly with unsafe code from executing in my process, and deny (and PermitOnly, etc) won't work, how would I go about it?  Probably the best way to do this would be to create a sandboxed AppDomain that does not grant SkipVerification.  Since the assembly then won't have SkipVerification in its grant set, any unverifiable methods it contains will cause the JIT to throw an exception.

The big takeaway from this is that you should almost never be using Deny or PermitOnly to remove SkipVerification permission from the call stack, since it almost certainly won't have the effect that you think it will.  The only effective ways to remove SkipVerification is by modifying the assembly's grant or refused sets.