With the release of .NET 4.0, Microsoft made some large scale changes to the framework’s security model. Concluding that the legacy model was little understood by both developers and IT administrators, Microsoft decided to do what is normally a very prudent action: they decided to simplify it.
In previous versions of the .NET Framework, the security model was tangible in the form of Code Access Security policies. Each policy was a set of expressions which used information associated with assemblies in order to determine which code group said assemblies belonged to. Each code group would contain a permission set, which would then be referred to by code access demands made in response to attempts to perform privileged actions.
.NET 4.0 got rid of all that nonsense with the introduction of level 2 transparent code (although technically, CAS still exists; what’s been eliminated is CAS policy). Basically, under this system, machine-wide security policy is off by default, and all desktop applications run as full trust. Code is either transparent, safe-critical, or critical; assemblies lacking annotation will have all their types and members treated as being critical if the assembly is fully trusted, or transparent if the assembly is partially trusted only.
Instead of worrying about having to make a bunch of strange security-related assertions and demands, all one needs to do to run under the new model is annotate their code appropriate using the three main attributes: SecurityTransparent, SecuritySafeCritical, and SecurityCritical. Sounds good; simple is better, but don’t use this new system. It isn’t ready for the real-world yet.
This article is not meant to actually criticize the substance of the new security model. I think the model is a huge improvement, but there’s a a few specific issues that cause me to view it as a mess as it now stands. Before we get into one of those issues, let’s look at reality first.
Most of the .NET Framework Doesn’t Use It
One of the ways I gauge the viability of new technologies from Microsoft is by trying to get a handle on whether or not Microsoft itself it. This approach has never failed me, and has saved me from a countless number of time sinks that would have affected endeavors both professional and personal. So, in order to check out the immediate viability of the new level 2 transparency model, let’s look at the primary .NET Framework assemblies and see whether or not they use it.
We can tell whether or not a particular assembly is using level 2 transparency by taking a look at the assembly’s metadata. If the assembly is operating under the level 2 transparency model, it would contain a SecurityRulesAttribute with a SecurityRuleSet.Level2 value being passed to that. However, it is important to remember that level 2 transparency is used by default, so if the attribute is not declared, then we should assume it to be used level 2. It is against “guidelines”, however, not to declare this attribute.
If the assembly is operating under the level 1 transparency model, the SecurityRulesAttribute is declared with a SecurityRuleSet.Level1 value passed to it instead.
Let’s then see what we come up with from looking at some of the core .NET Framework assemblies. In order to do this, I wrote a program which enumerated over every single assembly installed to the standard .NET Framework 4.0 installation directory, checking the SecurityRulesAttribute attribute present on each one.
The results are interesting:
- Total Level 2
- Total Level 2 with Obsolete Security Actions
(this means the assembly included a SecurityPermissionAttribute with a SecurityAction value deemed obsolete under the new model)
- Total Level 2 Lacking Assembly-wide Notation
(this means that no SecurityTransparentAttribute, SecurityCriticalAttribute, or AllowPartiallyTrustedCallersAttribute was found on the assembly metadata)
- Total Level 1
The majority of Level 2 assemblies were completely lacking notation (i.e. no SecurityRulesAttribute), which goes against Microsoft’s own guidelines. As we can see, however, there are a number of level 2′s, however the majority of the level 2′s are insignificant (except for mscorlib.dll and System.Security.dll), whereas the important .NET assemblies are what constitute the Level 1 group.
Here’s the list of Level 1 assemblies:
These are some of the most important assemblies in the BCL, and they’re all using the legacy security model.
But you know, all of this is just an observation, it doesn’t mean anything. Indeed, I was just recounting reality right now. What I just talked about isn’t the primary reason not to use it. What caused my jaw to drop was when I found out about the issues Visual Studio’s code coverage had with it.
Visual Studio Code Coverage Doesn’t Work With It
…if you’re assembly is annotated, at least. Deal breaker for me.
By “annotated”, I mean your assembly is set to either be SecurityCritical, SecurityTransparent, or AllowPartiallyTrustedCallers
If you do any of those, and then annotate the rest of your code properly using FxCop and SecAnnotate, you will get the following error if you run any unit tests with Visual Studio’s built-in code coverage:
System.TypeInitializationException: The type initializer for ‘xxx’ threw an exception. —>
System.MethodAccessException: Attempt by security transparent method ‘Microsoft.VisualStudio.Coverage.Init_[...].Register()’ to call native code through method ‘Microsoft.VisualStudio.Coverage.Init_[...].VSCoverRegisterAssembly(UInt32, System.String)’ failed.
Methods must be security critical or security safe-critical to call native code.
The reason why this happens is because during instrumentation a bunch of unattributed methods get inserted that make P/Invoke calls. I find it a little ridiculous that Microsoft’s own tools don’t support this new model, and especially one that I feel is rather critical to the development process. Microsoft clearly does not use its own tools for code coverage analysis, at least with the assemblies that are level 2.
So, my advice if you are starting a new library or whatnot: If security is a priority with you (like if you want to allow partially trusted callers), then use the legacy model. If it isn’t, then you can declare level 2 compatibility in your assembly metadata, but don’t add any other level-2 specific assembly-wide attribute until the model is more supported.