Understanding .NET Permissions: Where Did That Permission Come From?by Mike Gunderloy
The .NET security system is a marvelously intricate invention. You can customize the permissions available to an individual assembly or a group of assemblies (such as all code from a particular publisher) on an amazingly granular level. But many developers are a bit hazy on how all of the pieces fit together to generate these permissions. In this article, I'll walk you through the process of calculating permissions by hand. Armed with this knowledge, you can more effectively configure .NET to secure your assemblies.
The Basic Concepts
To get started in .NET security, you need to understand three basic concepts:
- Permission Sets
- Code Groups
You can work with all of these concepts in code, or through the Microsoft .NET Framework Configuration tool (which you can launch from Start > Programs > Administrative Tools). I'll focus on the graphical tool in this article, since my primary goal is understanding the process rather than customizing it in code. Figure 1 shows this tool in action, expanded to show some of the security-related nodes in its MMC treeview.
|Microsoft .NET Framework Configuration Tool|
Permissions are the individual abilities that the Common Language
Runtime (CLR) can grant or deny to .NET code. A permission can be as broad as
"unlimited power to use any printer" or as narrow as "power to read from a
particular Registry key." The .NET Framework includes its own extensive set of
permissions to control access to system resources. You'll find these in the
namespace. You can also create your own permissions in code if your program
manages custom resources that should be included in the .NET security
Permission Sets are, of course, sets of permissions. A permission set contains one or more permissions that are granted or denied as a unit. In fact, you can't grant or deny individual permissions; to grant a single permission, you need to create a permission set containing only that permission, and then grant the permission set. The .NET Framework includes seven built-in permission sets:
- The Nothing permission set grants no permissions.
- The Execution permission set grants permission to run but not to access protected resources.
- The Internet permission set grants limited permissions designed for code of unknown origin.
- The LocalIntranet permission set grants high permissions designed for code within an enterprise.
- The Everything permission set grants all permissions except for the permission to skip verification.
- The SkipVerification permission set grants the permission to skip security checks.
- The FullTrust permission set grants full access to all resources. This permission set includes all permissions.
You can also create your own custom permission sets. In the .NET Framework Configuration Tool, right-click a Permission Sets node and select New. Assign a name and description to the new permission set and click Next. This will open the dialog box shown in Figure 2, which lets you pick the permissions that will be a part of your new permission set. Select the permissions that you want to include, using the Properties button to customize the individual permissions, if you like. Then click Finish to create the permission set.
|Selecting permissions to make up a permission set|
Code groups are sets of assemblies that share a security context. You define a code group by specifying the membership condition for the group. Every assembly in a code group receives the same permissions from that group; however, because assemblies can be members of multiple code groups, two assemblies in the same group might end up with different permissions in the end. The .NET Framework supports seven different membership conditions for code groups:
- The application directory membership condition selects all code in the installation directory of the running application.
- The cryptographic hash membership condition selects all code matching a specified cryptographic hash. Practically speaking, this is a way to define a code group that consists of a single assembly.
- The software publisher membership condition selects all code from a specified publisher, as verified by Authenticode signing.
- The site membership condition selects all code from a particular Internet domain.
- The strong name membership condition selects all code with a specified strong name.
- The URL membership condition selects all code from a specified URL.
- The zone membership condition selects all code from a specified security zone (Internet, Local Intranet, Trusted Sites, My Computer, or Untrusted Sites).
The .NET Framework includes some built-in code groups. Of course, you can also create your own code groups. Right-click on a Code Groups node in the .NET Framework Configuration Tool and select New to create a new code group. Assign a name and description to the code group and click Next. Specify the membership permission for the code group and click Next. Select the permission set for the code group and click Next, then Finish to create the code group.
Code groups are arranged in a hierarchy. If code is in a parent group in the hierarchy, it might also be in one or more of the child groups in the hierarchy. If code isn't in a parent group, then it won't be in any of the child groups, even if it matches their membership condition. Each code group has properties, which you can see by right-clicking and choosing Properties. Figure 3 shows the Properties dialog box for a particular code group. Take note of the two checkboxes at the bottom of the General tab. The "This policy level will only have the permissions from the permission set associated with this code group" checkbox sets the Exclusive property for the code group. The "Policy levels below this level will not be evaluated" checkbox sets the LevelFinal property for the code group.
|Viewing the permissions of a code group|
Now that you know the basics, you can follow the permissions process to determine the actual permissions applied to any given piece of code. To begin the process, think about permissions at the Enterprise level only. The Common Language Runtime starts by examining the evidence a particular piece of code presents to determine its membership in code groups at that level. Evidence is just an overall term for the various factors (publisher, strong name, hash, and so on) that can go into code group membership.
As it's determining membership, the CLR walks down the hierarchy, checking the child code groups of each code group where the code being evaluated is a member. In general, the CLR will examine all of the code groups in the hierarchy to determine membership. However, the CLR stops checking for group membership if code is found to be a member of an Exclusive code group. Whether it's part of an Exclusive code group or not, code will be determined to be a member of zero or more code groups at the end of this first step.
Next, the CLR retrieves the permission set for each code group that contains the code. If the code is a member of an Exclusive code group, only the permission set of that code group is taken into account. If the code is a member of more than one code group and none of them is an Exclusive code group, all of the permission sets of those code groups are taken into account. The permission set for the code is the union of the permission sets of all relevant code groups. That is, if code is a member of two code groups, and one code group grants Isolated Storage File permissions, but the other does not, the code will have Isolated Storage File permission from this step. This is a "least-restrictive" combination of permissions.
That accounts for the permissions at one level (the Enterprise level). But there are actually four levels of permissions: Enterprise, Machine, User, and Application Domain. Only the first three levels can be managed within the .NET Framework Configuration Tool, but if you need specific security checking within an application domain (roughly speaking, an application domain is a session in which code runs), you can do this in code. An application domain can reduce the permissions granted to code within that application domain, but it cannot expand them.
The Common Language Runtime determines which of the four levels are relevant by starting at the top (the Enterprise level) and working down all the way to the Application Domain level. But remember the LevelFinal property; if code is a member of a code group with this property set, then the CLR stops there. For example, if code is a member of a code group on the Machine level, and that group has the LevelFinal property, only the Enterprise and Machine levels are considered in assigning security. The CLR computes the permissions for each level separately and then assigns the code the intersection of the permissions of all relevant levels. That is, if code is granted Isolated Storage File permission on the Enterprise and Machine levels but is not granted Isolated Storage File permission on the User level, the code will not have Isolated Storage File permission. Across levels, this is a "most-restrictive" combination of permissions.
After looking at all of the relevant levels, the CLR knows what permissions should be granted to the code in question, considered in isolation. But code does not run in isolation; it runs as part of an application. The final step of evaluating code access permissions is to perform a stack walk. In a stack walk, the CLR examines all code in the calling chain from the original application to the code being evaluated. The final permission set for the code is the intersection of the permission sets of all code in the calling chain. That is, if code is granted Isolated Storage File permission but the code that called it was not, the code will not be granted Isolated Storage File permission.
Checking Your Work
Now that you know how to compute permissions by hand, I'll leave you with a way to check your work. In the .NET Framework Configuration Tool, right-click the Runtime Security Policy node and select Evaluate Assembly. Use the Browse button to locate the assembly in which you're interested. You can choose to view permissions granted to this assembly or to view code groups that grant permissions to this assembly. You can also choose whether to evaluate all levels or a particular level. Click Next and the results will come back, as shown in Figure 4. If you're having trouble figuring out why a particular assembly doesn't have the permissions you expect, this list of memberships can be a great troubleshooting tool.
|Evaluating permissions for an assembly|
The .NET security system is set up to be as unobtrusive as possible, while still providing protection from rogue code. In particular, all code on your own computer is granted Full Trust permission by the My_Computer_Zone built-in code group at the Machine level. When you're ready to run code from elsewhere, or to restrict what a particular assembly can do, you'll need to dig further. Follow the process I've outlined, and you too can customize .NET security to do whatever you wish.
Mike Gunderloy is the lead developer for Larkware and author of numerous books and articles on programming topics.
Return to .NET DevCenter