Last time I talked about transparency, but now it’s time to look at how to use it in CLR 2.0 (…ominous foreshadowing).
Remember the layers that I mentioned last time? If not, remember that transparent code cannot elevate privilege or cause permission demands to succeed, while critical code can. To make this distinction in your code, there are three attributes in the System.Security namespace you need to know about:
- SecurityTransparentAttribute
- SecurityCriticalAttribute
- SecurityTreatAsSafeAttribute
If you want to leverage transparency in your assembly, the first thing you must do is to tell the CLR that you want it. You can do this by marking your assembly with the SecurityTransparentAttribute or the SecurityCriticalAttribute. The difference? If you mark your assembly with the SecurityTransparentAttribute (1), all of the code inside that assembly is now transparent. If, on the other hand, you mark your assembly with the SecurityCriticalAttribute (2), all of the code inside that assembly is transparent unless you say otherwise. You can also mark your assembly with the SecurityCriticalAttribute and specify a SecurityCriticalScope of Everything (3). This means that everything in your assembly is critical code.
-
[assembly: SecurityTransparent]
-
[assembly: SecurityCritical]
-
[assembly: SecurityCritical(SecurityCriticalScope.Everything)]
If you choose option 1, you’re done; you can’t do anything more with regards to transparency. You’re stating that none of your code performs any permission elevations and all demands for permissions will flow right through your assembly. To give you an example from the framework, System.Data.Linq.dll (LINQ to SQL) is marked as transparent. All of the work that it does does not require any permissions. The places that demand permissions are later down in the pipeline (think of System.Data.dll for SqlClientPermission, mscorlib.dll for ReflectionPermission, etc.) and there is no possible way that System.Data.Linq can satisfy these permission demands.
If you choose option 2, you need to locate all of the pieces of code which are not transparent and mark them with the SecurityCriticalAttribute. System.Data.Entity.dll (Entity Framework) is marked as SecurityCritical because it does a few things like access assembly names that it needs to do even if the caller does not have these permissions. Thus wherever System.Data.Entity asserts for permissions, it also marks that code as SecurityCritical.
But wait! You’re not done yet. The CLR 2.0′s enforcement of transparency means that SecurityTransparent code cannot call non-public SecurityCritical code. That is, the following code will throw a MethodAccessException when Main attempts to call GetAssemblyName.
[assembly: SecurityCritical]
public class Program
{
public static void Main()
{
GetAssemblyName(typeof(Program).Assembly);
}
[SecurityCritical]
[FileIOPermission(SecurityAction.Assert, AllLocalFiles = FileIOPermissionAccess.PathDiscovery)]
private AssemblyName GetAssemblyName(Assembly assembly)
{
return assembly.GetName();
}
}
If you are absolutely positively sure that you want your transparent code to call into critical code, you can apply the SecurityTreatAsSafeAttribute to the critical method in order to allow this to happen. Mike Downen, a program manager on the CLR Security team, summarizes the reasons quite nicely in his MSDN article What’s New With Code Access Security in .NET 2.0:
The reason for the SecurityTreatAsSafe attribute may not be obvious at first. Think of the security-transparent and security-critical code within your assembly as actually separated into two assemblies. The security-transparent code would not be able to see the private or internal members of the security-critical code. Additionally, the security-critical code is generally audited for access to its public interface. You would not expect private or internal state to be accessible outside of the assembly—you would want to keep the state isolated. So to allow the isolation of state between security-transparent and security-critical code while still providing the ability to override when necessary, the SecurityTreatAsSafe attribute was introduced. Security-transparent code cannot access private or internal members of security-critical code unless those members have been marked with SecurityTreatAsSafe. Before adding SecurityTreatAsSafe, the author of critical code should audit that member as though it were being exposed publicly.
Marking GetAssemblyName as SecurityTreatAsSafe indeed solves our problem:
[SecurityCritical]
[SecurityTreatAsSafe]
[FileIOPermission(SecurityAction.Assert, AllLocalFiles = FileIOPermissionAccess.PathDiscovery)]
internal AssemblyName GetAssemblyName(Assembly assembly)
{
return assembly.GetName();
}