When is ReflectionPermission Needed?
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:
System.Activator | System.AppDomain |
System.RuntimeType | System.Type |
System.IO.IsolatedStorage.IsolatedStorageFile | System.Reflection.Assembly |
System.Reflection.ConstructorInfo | System.Reflection.EventInfo |
System.Reflection.FieldInfo | System.Reflection.MethodBase |
System.Reflection.PropertyInfo | System.Reflection.RuntimeConstructorInfo |
System.Reflection.RuntimeEventInfo | System.Reflection.RuntimeFieldInfo |
System.Reflection.RuntimeMethodInfo | System.Reflection.RuntimePropertyInfo |
System.Reflection.Emit.AssemblyBuilder | Sytem.Reflection.Emit.MethodRental |
System.Resources.ResourceManager |
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:
System.RuntimeFieldHandle | System.RuntimeMethodHandle |
System.RuntimeTypeHandle | System.Reflection.RuntimeFieldInfo |
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:
IntPtr handleValue = IntPtr.Zero;
try
{
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
handleValue = (IntPtr)handleProperty.GetValue(cert, null);
}
finally
{
CodeAccessPermission.RevertAssert();
}
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
Comments
- Anonymous
March 08, 2005
You can reflect and change a delegate to point to unmanaged code. So why have a separate reflection permission for that kind of stuff, since if you can set private types, you can run unmanaged code? - Anonymous
March 09, 2005
TypeInformation doesn't seem to have been obsoleted yet in the February CTP, so testing the changed behaviour is a wee bit difficult. From your description, it sounds like code without any reflection permission will be able to reflect into a type marked as internally visible as long as it hits only publicly visible members. Is this really the case, or will ReflectionPermissionMemberAccess be required to grab a type of limited visibility?
Also, do you have any idea what the rationale behind the extra protection for the "special" classes might be? After all, if there's no security benefit in preventing access to public members of public types via reflection, then what's the point of doing so for these classes? - Anonymous
March 09, 2005
Michael,
There are a set of permissions that we consider "FullTrust equivilents", basically meaning that granting them is enough for malicious code to elevate itself to fully trusted code. The most obvious example is UnmanagedCode, but things like unrestricted FileIO (since they could rewrite the policy file), and Reflection permission also fall into that category. None of these permissions are granted in any of the default code groups except for Everything and (obviously) FullTrust.
However, just because you could exploit having the permission to elevate yourself, doesn't mean that hte permission isn't worth having at all. Just because I could turn unrestricted FileIO into FullTrust, doesn't mean its not worth having File IO sitting around :-)
-Shawn - Anonymous
March 09, 2005
Nicole,
I'm not sure what build the TypeInformation obsoletion went in ... I thought it would be there by the Feb CTP (though if there's no ObsoleteAttribute on the TypeInformation value in the ReflectionPermissionFlag enum, then you'll see it in Beta 2).
The special classes all contain some functionality that internally require stack walks. Placing latebound calls to them make it difficult for them to figure out who their real caller is, and makes them more suceptable to having problems. Since IsDangerousMethod works only on the class level, and not on a method-by-method basis, the entire class ends up protected even if its really needed for only one method.
-Shawn - Anonymous
March 09, 2005
Yea, I see the point. So I'm guessing Reflection needs an "Unrestricted=true" property to allow me to edit private fields of system types?
I just mentioned this since perhaps someone might make their own policy, and think reflection is ok and allow it. But I guess that's not a real wide usage eh? - Anonymous
March 09, 2005
You'll need MemberAccess (which Unrestricted implies), and you won't find that in anything lower than the Everything permission set in default policy.
-Shawn - Anonymous
March 14, 2005
Maybe I'm just being overly picky, but the approach selected for achieving this protection seems more than a little odd to me. Would use of a marker interface or attribute on the target types or members have been so much more expensive that it's worth hard-coding the type list?
Also, why not use a separate permission or a new flavour of ReflectionPermission given that this usage does not correspond to the documented purpose of ReflectionPermissionMemberAccess? After all, it had even you misinterpreting the permissions required for reflection into public members of public types... ;) - Anonymous
March 16, 2005
:-) I'll have to leave comment on that to the Reflection team.
-Shawn - Anonymous
July 27, 2005
I'm running a user-control (.NET 1.1) hosted in IE, but I want to make use of multiple threads, so I'm using Invoke and BeginInvoke.
This eventually results in a call to COMMember::InvokeMethod_Internal, which requires ReflectionPermission.TypeInformation, and the CLR throws a MethodAccessException.
Leaving multiple threads and the Invoke method out for the moment, I just tested to see if I could grant my assembly ReflectionPermission based on its strong name. However, when I Assert and Demand it, the demand still fails.
When I Assert and Demand SecurityPermissions, e.g. UnmanagedCode, those work just fine. And if I assign ReflectionPermission to the Internet Zone (so I don't have to Assert it) it works fine.
Do you know what is different about ReflectionPermission that the Assert is failing? - Anonymous
July 27, 2005
A few additional comments about my previous post...
1. The Assert that fails is an imperative Assert in my method. If I do a declarative (Attribute) Assert on my class (or the calling class), the Demand succeeds.
2. Even though the Assert works, it doesn't help me since the Invoke calls come through from higher on the stack and there is no way I know of to get an Assert inserted there.
3. This is just an academic question: after re-reading your post I realized I could get rid of the ReflectionPermission demand by making sure my methods were all public. But I still don't understand why the imperative Assert call doesn't work... - Anonymous
November 18, 2005
Is ReflectionPermission only needed for other assemblies or for the own assembly too?
Do I really need special ReflectionPermission to read meta-informations from the same assembly?
Thanks, Thomas