Special Permissions in the SSCLI
Before digging into a pretty clever optimization that the SSCLI makes for certain special permission demands, I want to point out that everything I’m about to cover is an implementation detail. Although this optimization does occur today, we can and will change it for future versions of the CLR (and potentially service packs for the v2.0 version) … the only thing guaranteed about the behavior of a demand is that it throws a SecurityException if the call stack does not meet the required permissions; and does nothing if it does.
With that being said, the v2.0 CLR has a few special permissions which it can apply a clever optimization to in order to speed up demands. If you’ve got the SSCLI v2 installed, you can see these permission defined in managed code in the PermissionType enumeration (clr\src\bcl\System\Security\CodeAccessSecurityEngine.cs) and mirrored in the VM (clr\src\VM\SecurityPolicy.h). (Look for the set of #defines starting at line 297 with SECURITY_UNMANAGED_CODE).
There are two categories of these special permissions defined:
- A flag of a permission (Assert, SkipVerification, UnmanagedCode, etc)
- An unrestricted permission (UI, Environment, Security, etc)
Each of these is given an integer value in SecurityPolicy.h which matches its value in the PermissionType enumeration. The CLR maps these values into flags by using the corresponding bit of a DWORD to indicate that this permission is the one being talked about. For instance, given:
#define UI_PERMISSION 9
We use 1 << UI_PERMISSION or 0x00000200 as a flag to indicate that we’re referencing unrestricted UI permission. In this way, we can simply logically or together as many of these permissions as is needed to talk about any given set of the special permissions. This does imply a limit of 32 special permissions however -- currently the CLR defines only 18, so there is plenty of room to grow there. One interesting thing to note is that although we do define SECURITY_FULL_TRUST to be bit 7, generally the VM uses 0xFFFFFFFF to refer to the FullTrust special permission bitmask.
Calculating Special Permission Flags
When an assembly or an AppDomain is loaded, policy is resolved to get the grant set. The entry point for this resolution is SecurityPolicy::ResolvePolicy (clr\src\VM\SecurityPolicy.cpp), which takes the parameters you would expect such as the evidence and assembly level declarative permission sets. It returns the grant set, and as an out parameter the denied permission set. As an extra out parameter, dwSpecialFlags, is the set of special permission flags which are calculated from the grant and deny set.
The logic is to return a bitmask that represents everything that was in the grant set and not in the deny set. So if an assembly was being resolved in SecurityPolicy::ResolvePolicy, and its grant set included SkipVerification, UnmanagedCode and unrestricted UIPermission while its deny set included SkipVerification and unrestricted EnvironmentPermission, the output would be:
((1 << SECURITY_SKIP_VER) | (1 << SECURITY_UNMANAGED_CODE) | (1 << UI_PERMISSION) ) &
~( (1 << SECURITY_SKIP_VER) | (1 << ENVIRONMENT_PERMISSION) ) =
(0x00000103) & !(0x00000402) =
0x00000103 & 0xFFFFFBFD =
0x00000101 =
(1 << UI_PERMISSION) | (1 << SECURITY_UNMANAGED_CODE)
Which is what we would expect when you take the set (SkipVerification, UnmanagedCode, UIPermission) and remove (SkipVerification, EnvironmentPermission).
The actual work of resolving policy and calculating these flags is delegated by SecurityPolicy::ResolvePolicy to System.Security.SecurityManager.ResolvePolicy (clr\src\bcl\System\Security\SecurityManager.cs). After ResolvePolicy computes the grant and deny sets, it passes those to SecurityManager.GetSpecialFlags, whose job it is to do the mapping from the permission sets to any appropriate special permission flags. It does this by scanning for any flags on permissions which have their flags represented in the special permissions (currently just SecurityPermission and ReflectionPermission) and saving those flags away. It also does a scan for any permissions which have their unrestricted permissions set as a special flag, saving those as well.
Once all the permissions which may map to a special flag are found, SecurityManager.MapToSpecialFlags is called to map the flags from SecurityPermission and ReflectionPermission back to an appropriate flag, while the GetSpecialFlags method itself adds bits for any of the other permissions which are unrestricted.
I mentioned earlier that FullTrust is represented as 0xFFFFFFFF rather than 0x00000080, you can see that mapping in the very first line of SecurityManager.GetSpecialFlags.
Security Descriptors and Special Permissions
The base SecurityDescriptor class (clr\src\VM\SecurityDescriptor.h) defines a protected member, the DWORD m_dwSpecialFlags, which holds the set of special permission flags that apply to the VM object the security descriptor refers to (such as an AppDomain or Assembly). In addition to this set of flags, the ApplicationSecurityDescriptor (clr\src\VM\SecurityDescriptorAppDomain.h) defines a second member to hold these flags -- m_dwDomainWideSpecialFlags.
The m_dwSpecialFlags member is populated with the result from calling SecurityPolicy::ResolvePolicy on the object being loaded. The extra field in the ApplicationSecurityDescriptor however is kept up to date differently, and allows us to perform an optimization when a demand for one of these special permissions comes in.
After the AppDomain has had its policy resolved, m_dwDomainWideSpecialFlags is initialized to match the special flags of the AppDomain. (This is done in ApplicationSecurityDescriptor::InitializePLS in clr\src\VM\SecurityDescriptorAppDomain.cpp). This means an AppDomain with no loaded assemblies has an m_dwDomainWideSpecialFlags indicating the special permission grant set of the domain itself.
As each assembly is loaded into the AppDomain, the AssemblySecurityDescriptor’s m_dwSpecialFlags is initialized while policy is resolved on that assembly. (See AssemblySecurityDescriptor::ResolveWorker in clr\src\VM\SecurityDescriptorAssembly.cpp). The AppDomain then updates the m_dwDomainWideSpecialFlags field with the information from the AssemblySecurityDescriptor in ApplicationSecurityDescriptor::AddNewSecDescToPLS. The domain wide flags are updated by doing a bitwise and between the current domain wide flags and the flags of the assembly’s security descriptor.
Following this logic along, the domain wide special flags start out as the set of special permissions granted to the AppDomain itself, and then are potentially reduced by each assembly loaded so that at any given point the domain wide special flags are always the set of special permissions which are granted to every assembly in the domain as well as the domain boundary.
With that setup, if a demand for one of the special permissions is done in the AppDomain, we can check to see if the domain wide flag for that permission is set. If it is, then the CLR can cause the demand to succeed without ever having to do a stack walk, since it knows that for the special permission bit to be set everything in the AppDomain must be granted the given permission. The code which maps the permissions in a demand to a set of special flags is in SecurityStackWalk::GetPermissionSpecialFlags (clr\src\VM\SecurityStackWalk.cpp).
Obviously this optimization does not work in the face of stack walk modifiers such as Deny or PermitOnly, which can cause the permission set of the call stack to become a subset of the permissions granted to the assemblies in the domain -- and if you look at the code in SecurityStackWalk::HasFlagsOrFullyTrustedIgnoreMode (clr\src\VM\security.inl) this is what the check for pThread->GetOverridesCount() == 0 is doing. [But as Alton Brown might say, that’s another show.] This is just one more reason that you really want to avoid using Deny and PermitOnly in your code if at all possible.
Also note that this logic works for determining if a demand will pass, however it cannot be used to determine that a demand will fail. For instance, if an AppDomain has assemblies A, B, and C loaded into it, and the AppDomain, assembly A, and assembly B are all granted SkipVerification while assembly C is not, the SECURITY_SKIP_VER bit will be clear on the domain wide flags. However, if a skip verification demand is done while only A and B are on the call stack (or if C is on the stack, but either A or B has done an Assert preventing a stack walk from reaching C), then the demand will still succeed even though the bit is clear.
The take away from this is not that you should try to only demand special permissions -- again, these were all implementation details subject to change at any point. You should always demand the most appropriate permission for the resource you’re trying to protect. More importantly, it’s interesting to see how the CLR can optimize the performance of some demands, and how operations like Deny and PermitOnly can ruin the optimizations.
Comments
- Anonymous
June 19, 2006
&nbsp;
Web Resources
&nbsp;
[Default] Game Developers: Make Contact in Seattle
August...