Code Access Security in .NET
The .NET code access security system works like this: Every time an assembly is loaded into an application domain, the security system determines what permission set should be granted to that assembly. The .NET runtime does this by examining evidence about the assembly. Assemblies are categorized into one or more code groups based on their evidence. The policy evaluator then determines which permissions to grant based on which code groups the assembly belongs to (just as a role-based system determines which permissions to grant to users based on which user groups they belong to).
When the code runs, if it attempts to perform some task that requires a permission (such as deleting a file), the security system checks to ensure that the code was granted the appropriate permission. If not, it throws an exception and the attempt fails.
The Machine Policy Level
Let's take a look at the out-of-the-box policy. Go to your .NET Framework SDK directory and run the mscorcfg.msc file to pop up the management console shown in Figure 19-1.
Figure 19-1. The .NET Runtime Security Policy management console.
As you can see, under Runtime Security Policy, there are three policy levels: Enterprise, Machine, and User. (There is also a fourth level, not shown, which is the Application policy level, which is discussed later in this chapter.) Open the Machine policy level, and you'll see that there is a tree of code groups. Each code group is associated with a particular permission set and evidence condition.
For example, code that has the My Computer Zone evidence is granted the FullTrust permission set; code that is installed on your machine is granted permission to do anything. Code that has the LocalIntranet Zone evidence is granted the LocalIntranet permission set, which is rather more restrictive. If you run a managed assembly off of a share on your local intranet, it will be able to run, produce user-interface elements, and so on, but is not granted the right to modify your security settings or read or write to any file on your disk.
Notice that the root code group in the Machine policy level is All Codeevery assembly is a member of this group irrespective of its evidence. If you look at the permission set granted by that group, however, it grants no permissions whatsoever. It denies the right to execute at all. What's up with that?
Within a policy level, the permission set granted to an assembly is (usually) the least-restrictive union of all the permission sets of all the applicable code groups. Code that belongs to the All Code group (which grants nothing) and the LocalIntranet Zone code group (which grants the LocalIntranet permission set) will be granted the permissions from the less-restrictive group.
We say "usually" because there are ways of creating custom policies that enforce rules other than "take the least-restrictive union." For instance, you could create a policy tree with the rule "take the permission set granted by the first matching code group and ignore everything else." Policy trees can become quite complex. |
Kinds of Evidence
So far we have seen the All Code group, which does not consider evidence at all, and various zone code groups that consider evidence about "where code comes from" in a broad sense. Zones describe whether the code comes from the local machine, the local intranet, an explicitly trusted Internet site, an explicitly untrusted Internet site, or an Internet site of unknown trustworthiness.
When we discuss the User policy level, you will see a much more specific kind of location-based evidence; you can create policies that grant permissions if the code is running from specific local or network directories or Web sites.
A close look at the Machine policy level shows two child code groups, subsets of the My Computer Zone code group, that grant full trust to assemblies in the My Computer Zone and are strong named with the Microsoft or ECMA keys. You will learn more about strong-name evidence, and why it should always be in a child code group, later in this chapter.
Finally, there is evidence associated with individual assemblies. Every assembly has a statistically unique "hash number" associated with it; it is possible to create policies that grant permissions to specific assemblies by checking their hash numbers. Assemblies can also be signed with a publisher certificate (such as a VeriSign code-signing certificate). When the loader attempts to load a publisher-signed assembly, it automatically creates evidence describing the certificate. You could create code groups that grant permissions to all assemblies signed with your internal corporate certificate, for instance.
Combining Policy Levels
Take a look at the Enterprise policy level shown in Figure 19-1. Unless your network administrator has set policy on your machine, this policy level should be much simpler than the Machine policy level. It consists of a single code group that matches all code and grants full trust.
But hold on a momentif the Enterprise policy is "grant full trust to all code," how does this security system restrict anything whatsoever?
The .NET security system determines the grant set for each policy levelEnterprise, Machine, User, and Applicationand actually grants the permission only if a permission is granted by all four levels.
Setting the Enterprise policy level to "everything gets full trust" cannot possibly weaken the restrictions of the other three groups. If the Machine policy level refuses to grant, say, permission to access the file system, it does not matter what the other three policy levels grantthat permission will not be granted to the assembly.
It works the other way, too: Suppose the Enterprise policy level states "grant full trust to all assemblies except for this known-to-be-hostile Trojan horse assembly." If you accidentally install the Trojan horse on your machine, the Machine policy level will grant full trust, but the Machine policy level cannot weaken the Enterprise policy level. Every policy level must agree to grant a permission for it to be granted, so the evil code will not run.
We discuss later in this chapter ways to get around the requirement that a permission must be granted by all four levels.
The User Policy Level
Take a look at your User policy level while logged in to a machine where you have been creating VSTO 2005 projects with Visual Studio. The contents of the User policy level as shown in Figure 19-2 might be a little bit surprising.
Figure 19-2. The User policy levelVSTO automatically creates policy so that VSTO projects are allowed to run on your development machine.
At the root, we have an All Code group that grants full trust, just like the Enterprise level. In keeping with the general rule that a policy level grants the least-restrictive union of permissions, it would seem that any further code groups in the policy tree for this level would be superfluous. And yet there is a child code group for VSTO projectsalso an All Code group, although it grants no permissions. It in turn has a code group for every project you have created, which again is an All Code group that grants no permissions. (The code group is given a GUID as its name to ensure that the group is unique no matter how many projects you create.)
The project-level code groups have URL-based child groups for every build configuration you have built that grant only execution permission, nothing else, to all code in the named directory. And those have children that grant full trust to the specific customization assemblies.
What the heck is going on here? It looks like Visual Studio has gone to great lengths to ensure that the User policy level explicitly grants full trust to your customization assemblies. And yet the User policy level's root code group already grants full trust. How is this not redundant?
There is a good reason for this, but before we get to that, we should talk about full trust versus partial trust.
Full Trust and Partial Trust
As anyone who has ever been infected by a Word or Excel macro virus knows, the code behind a customized document does not always do what you want, and you do not always know what it does. Fortunately, that is exactly the scenario that code access security systems were invented to handle. However, there is a problem with code access security in Office customizations. There is no way to partially trust code that accesses the Word and Excel object models. Trust is all or nothing.
The Internet Explorer object model was specifically designed from day one so that code running inside the Web browser was in a "sandbox." Code can run, but it is heavily restricted. The browser's objects inherently cannot do dangerous things such as write an arbitrary file or change your registry settings. Code is partially trusted: trusted enough to run, but not trusted enough to do anything particularly dangerous. The Word and Excel object models by contrast are inherently powerful. They manipulate potentially sensitive data loaded from and saved to arbitrary files. These object models were designed to be called only by fully trusted code. Therefore, when a VSTO customization assembly is loaded, it must be granted full trust in order to run at all.
This fact has serious implications for the application domain security policy created by the VSTO runtime when a customization starts.
The VSTO Application Domain Policy Level
Before examining the details of VSTO security policy, let's take a step back and consider why anyone has any security policy at all.
It is the same reason why stores have merchandise exchange policy, governments have foreign policy, and parents have bedtime policy: Policy is a tool that enables us to make thoughtful decisions ahead of time instead of having to make decisions on a case-by-case basis. The Enterprise, Machine, and User policy levels allow network administrators, machine administrators, and machine users to independently make security decisions ahead of time so that the .NET runtime can enforce those decisions without user interaction.
Decisions about policy can also be made by application domains (or AppDomains, for short). Because only those permissions granted by all four policy levels are actually granted to the assembly, the AppDomain policy level can strengthen the overall security policy by requiring more stringent evidence than the other policy levels.
We know that VSTO customizations must be granted full trust. By default, the Enterprise and User policy levels grant full trust to all assemblies regardless of evidence. The Machine policy level grants full trust to all assemblies installed on the local machine. In the absence of an AppDomain policy level, a VSTO customization copied to your local machine is granted full trust.
That seems like a reasonable decision for an application that you have deliberately installed on your local machine. Users typically install applications that they trust, applications that perform as expected and do what users want them to do, so it makes sense to implicitly grant full trust to assemblies in the Local Machine Zone.
But spreadsheets are not usually thought of as applications. Do users realize that by copying a customized document to their machine, they are essentially installing an application that will then be fully trusted, capable of doing anything that the users themselves can do? Probably not! Users do not tend to think of customized documents as applications; they are much less careful about copying random spreadsheets to their machines than they are about copying random executables to their machines.
Good security policies take typical usage scenarios into account. Therefore, the VSTO runtime tightens up the overall security policy by creating an AppDomain policy level that grants all the permissions of the other three policy levels except for those permissions that would have been granted solely on the basis of membership in either an All Code code group or a zone code group. All other permissions granted because of URL evidence, certificates, strong names, and so on are honored.
Let's take a look at an example.
Resolving VSTO Policy
Consider a VSTO customization assembly that you have just built on your development machine that you want to run. The customization assembly must be granted full trust by all four policy levels; otherwise, it will not run. The Enterprise and User policy levels grant full trust to all code. The Machine policy level grants full trust to code from the My Computer Zone. Three of the four levels have granted full trust.
What about the AppDomain policy level? It grants the same permissions as the other three policy levels except for those permissions granted solely by All Code and zone code groups. The Enterprise policy level consists of a single All Code code group, so it is ignored by the AppDomain policy level. The Machine policy level consists only of zone code groups, plus two strong-name code groups for the Microsoft and ECMA strong names. Unless you happen to work for Microsoft and have access to the code-signing hardware, it is likely that those code groups do not apply; so effectively, the AppDomain policy level is going to ignore all of these, too. Things are not looking good; the AppDomain policy has found nothing it can use to grant full trust yet. If the User policy level also consists solely of an All Code code group, as it does on a clean machine, the customization will not run.
But the User policy level on your development machine has a code group that is not ignored by the AppDomain policy level; it has a URL code group that explicitly trusts the customization assembly based on its path. The AppDomain policy sees this and grants full trust to the assembly. Because all four policy levels have granted full trust, the code runs.
Now it should be clear why Visual Studio modified your User security policy and added a seemingly redundant code group for the assembly. The VSTO AppDomain policy level requires that the customization assembly not only be fully trusted, but be fully trusted for some better reason than "we trust all code" or "we trust all code installed on the local machine." Therefore, there has to be some Enterprise, Machine, or User code group that grants full trust on the basis of some stronger evidence.
Because the VSTO AppDomain policy level refuses to grant full trust on the basis of zone alone, you're pretty much forced to come up with a suitable policy to describe how you want the security system to treat VSTO customization assemblies. Take off your software developer hat for a moment and think like an administrator setting security policy for an enterprise. Let's go through a few typical security policies that you might use to ensure that customized Word and Excel documents work in your organization while still preventing potentially hostile customizations from attackers out on the Internet from running. After discussing the pros and cons of each, we talk about how to roll out security policy over an enterprise.