Reflection and its interaction with security can sometimes be a bit of a confusing matter. The easiest portion to figure out is the permissions needed to use Reflection.Emit. In order to do anything with the reflection emit feature, you'll need to have ReflectionPermission with the ReflectionEmit flag set. In the default policy, you'll have this permission if you're running on LocalIntranet or MyComputer.
Things get a little more dicey when you start to figure out what permissions you need in order to use standard reflection. The general rule of thumb is that if you could do something with early binding, then you won't need ReflectionPermission in order to use reflection to do the same thing with late binding. So, you could easily create an XmlDocument and load a string into it with the Load method via reflection, even if you didn't have any ReflectionPermission. This makes sense because demanding a permission here really doesn't buy any extra security, since you could have just done these operations directly in your code anyway.
Getting a Type object for a type that is not visible to you, or getting an object to represent a member that is not visible has different behavior between v1.x and v2.0. On v1.x, you'll need ReflectionPermission with the TypeInformation flag. This means if you want to get a Type object for System.AssemblyHandle (which is a type in mscorlib marked as internal), you'll need this permission. You'll also need this permission to get a MethodInfo object for System.Array::GetMedian() even though Array is public, since GetMedian() is a private member. The same holds true of getting a FieldInfo for System.Security.PermissionSet::m_Unrestricted, a private field. Getting a PropertyInfo for the protected property System.Security.Cryptography.HashAlgorithm::HashSize would not require any permissions if you derived from HashAlgorithm, otherwise TypeInformation would be needed there as well. One thing that's interesting to note about this is that if you do not have TypeInformation, and you try to perform one of these protected operations, you won't get a SecurityException. Instead, reflection will just return a null object back to you. In Whidbey the TypeInformation flag has been marked as obsolete, and any code can get at these objects.
The reason this is safe is that once you have the appropriate xxxInfo object, and you want to either invoke it or read its value, you'll need ReflectionPermission with the ReflectionPermissionFlag.MemberAccess flag set. By default, both TypeInformation and MemberAccess are only granted to code running on the local machine.
So, from the discussion above, it would seem that I could easily call System.Reflection.Assembly::GetExecutingAssembly() from partial trust code, since Assembly is a public class, and GetExecutingAssembly() is a public method on that class. However, if I tried to do that, I would end up with a SecurityException.
Why is that? The answer lies deep within the CLR, inside the reflection internals. If you look inside the SSCLI, you can find the code that's responsible for dispatching the late-bound call in COMMember::InvokeMethod_Internal (located in sscli\clr\src\vm\COMMember.cpp, line 1154). Scanning down a bit through that method, there's an interesting comment on line 1231, where the reflection system calls an IsDangerousMethod() function to check to see if it needs to demand additional permissions. If IsDangerousMethod() returns true, then an RM_ATTR_RISK_METHOD flag gets set, which causes COMCodeAccessSecurityEngine::SpecialDemand(REFLECTION_MEMBER_ACCESS) to be called on line 1278.
It seems pretty obvious that this might be our problem, so lets go check out IsDangerousMethod() to see what it considers to be dangerous. This function starts at line 1007 of COMMember.cpp, and the meat of it is between lines 1073 and 1093. Basically, we check the method table of the method in question, and compare it to a method table of several classes. If the method belongs to one of these classes, we consider it dangerous and return true. The list of classes to compare against is initialized on lines 1031 to 1049. The 19 special classes are:
This table is defined in terms of macros that look like CLASS__APPDOMAIN, which should all be relatively easy to figure out on their own. However, if you'd like to find the source of this definition, you can check clr\src\vm\mscorlib.h, where all of these macros are created. For instance, CLASS__APPDOMIAN is created on line 64 of this file:
DEFINE_CLASS(APP_DOMAIN, System, AppDomain)
If a class belongs to this table, then none of its methods or properties are available through late binding unless the calling code is granted MemberAccess. That's why my partial trust code above couldn't call Assembly::GetExecutingAssembly().
In addition to the 19 classes listed above, Whidbey adds the following four classes to the dangerous list:
Another area where reflection and security come together is when the target code is protected with a LinkDemand. The problem with using reflection to invoke a method that contains a LinkDemand is that the reflection code and not your code will ultimately be the method's caller. This means that if the LinkDemand is for a set of CAS permissions (FullTrust for instance), it may be satisfied by the reflection code which lives in mscorlib, even though the demand may not have been satisfied by your code. In order to prevent malicious code from circumventing LinkDemands through the use of reflection, invoking a method through reflection has the effect of turning LinkDemands into full demands. If you don't want the whole stack checked, then you should Assert the permissions being demanded before calling the method with reflection, and then RevertAssert those permissions after the method returns. By following that pattern, you can most closely simulate the effect of the LinkDemand.
For instance, if I'm calling System.Security.Cryptography.X509Certificates.X509Certificate::Handle, which has a LinkDemand on its getter for UnmanagedCode, I would use something like this:
handleValue = (IntPtr)handleProperty.GetValue(cert, null);
To sum up, when working with reflection, if you keep the following rules in mind, you shouldn't run into any issues:
- If you're using Reflection.Emit, you need ReflectionPermissionFlag.Emit
- If you're accessing code that you would be able to access anyway, you don't need any permission
- If you're getting objects describing items you don't have access to, you need ReflectionPermissionFlag.TypeDiscovery
- If you're invoking objects you don't have access to, you need ReflectionPermissionFlag.MemberAccess
- There are some special classes unavailable without ReflectionPermissionFlag.MemberAccess
- All LinkDemands will turn into full Demands