Security in .NET

The Security Infrastructure of the CLR Provides Evidence, Policy, Permissions, and Enforcement Services

Don Box

This article assumes you're familiar with .NET

Level of Difficulty123

SUMMARY

The common language runtime of the .NET Framework has its own secure execution model that isn't bound by the limitations of the operating system it's running on. In addition, unlike the old principal-based security, the CLR enforces security policy based on where code is coming from rather than who the user is. This model, called code access security, makes sense in today's environment because so much code is installed over the Internet and even a trusted user doesn't know when that code is safe.

In this article, Don Box explains how code access security works in the CLR. He discusses the kinds of evidence required by policy, how permissions are granted, and how policy is enforced by the runtime.

Contents

Components and Security
Evidence
Policy
Permissions
Enforcement
Conclusion

One of the advantages of virtualized execution environments like the common language runtime (CLR) is that new security models can be developed that transcend the underlying operating system's security model. To that end, the CLR implements its own secure execution model that is independent of the host platform. Beyond the benefits of bringing security to platforms that have never had it (for example, Windows® 98), this also is an opportunity to impose a more component-centric security model that takes into account the nature of dynamically composed systems. This component-centric model is known as code access security, which is the focus of this article.

Components and Security

Systems that are dynamically composed from components have unique security requirements. Because individual components of an application often come from disparate organizations, it is likely that different aspects of the application may warrant different degrees of trust. For example, components from trusted organizations may need access to private information or critical resources that normally would need to be protected from malicious code. Unfortunately, the classic principal-based security model of Windows NT® and Unix ignores where the code came from and only focuses on who is running the code. For 1980s-era programs built before components, this model made sense. However, for a component-centric world in which an application's code may come from all corners of the globe, this model is far too coarsely grained to be useful by itself. Hence, code access security.

The CLR implements a code access security model in which privileges are granted to code, not users. Upon loading a new assembly, the CLR gathers evidence about the code's origins. This evidence is associated with the in-memory representation of the assembly by the CLR and is used by the security system to determine what privileges to grant to the newly loaded code. This determination is made by running the evidence through a security policy. The security policy accepts evidence as input and produces a permission set as output. To avoid performance hits, security policy is typically not run until an explicit security demand is made.

Permission sets like those returned by policy are simply a collection of permissions. A permission is a right to perform some trusted operation. The CLR ships with a set of built-in permission types to protect the integrity of the system and the privacy of the user. However, this system is extensible and user-defined permission types can be transparently integrated into the model.

The determination of which code is assigned which permissions is called policy. The enforcement of this policy is done using a distinct set of mechanisms. Prior to executing a privileged operation, trusted code is expected to enforce the security policy by explicitly demanding that the callers have sufficient privileges to perform the operation. Note the use of the plural "callers." By default, enforcement will demand that all callers, both direct and indirect, have sufficient permissions to perform the privileged operation. This prevents untrusted components from luring a gullible (yet trusted) piece of code into performing an operation on its behalf, thus assuming the privileges of the gullible piece of code.

Like garbage collection, code access security requires an omniscient and omnipotent runtime. In particular, that means that calling code that is not written to a strict format can thwart the security system. To that end, the Common Language Infrastructure (CLI) categorizes code into two broad families: verifiable code and non-verifiable code. Verifiable code can be mathematically proven to adhere to the type-safe execution model that the CLI encourages. Visual Basic® .NET and C# produce verifiable code by default. However, certain language features are not verifiably type-safe, the C++ reinterpret_cast being the canonical example. For that reason, code emitted by the C++ compiler is explicitly not verifiable and the use of this code can compromise security. To protect the integrity of the system, the ability to load non-verifiable code is itself a permission that must be explicitly granted to code through policy. The default policy that is installed with the CLR grants this permission only to code installed on a local file system. Similarly, the ability to call out to classic C-based or COM DLLs (which by definition are not verifiable) is also a trusted operation that by default is only granted to code installed on the local file system.

In general, the performance impact of code access security is slight. The assembly loader has additional work to do to gather evidence. However, this only occurs at load time and the cost is amortized over the lifetime of the assembly in memory. The key performance concern is policy enforcement, which can cause a potentially expensive traversal of the entire call stack. Fortunately, the cost of enforcement can be minimized by factoring security into your design, typically by avoiding excessive enforcement through explicit programming techniques, which I'll discuss later.

Evidence

The code access security story begins with evidence. Evidence acts as testimony of the origins of a given piece of code. The assembly loader is responsible for gathering evidence at load time based on where the code is loaded from as well as from the metadata for the assembly itself.

The CLR ships with seven types of evidence. Four of these evidence types (Site, Url, Zone, and ApplicationDirectory) relate to where the code was loaded from. Two of these evidence types (StrongName and Publisher) relate to who wrote the code. Finally, the seventh evidence type, Hash, is based on the overall contents of the assembly and allows you to detect a particular compilation of a piece of code, independent of version number.

Collectively, these seven types of evidence are called host evidence, as they are implemented by the host environment. It is possible to define your own evidence types, which collectively are called assembly evidence, as they are explicitly provided by the assembly itself. Defining new assembly evidence types also involves extending the policy mechanism to recognize them, which I won't be discussing in this article. For the remainder of the article, the focus will be on the built-in host evidence types.

The assembly loader ultimately works in terms of codebase URLs, some of which may be file based. The codebase URL is used to determine three of the four location-based evidence types: Url, Zone, and Site. The Url evidence is the easiest to understand, since it's just the codebase URL in its raw form. Site and Zone are derived from the codebase URL based on its contents.

The Site evidence type is simply the hostname part of an HTTP based URL. For example, if an assembly's codebase is https://www.example.com/foo.dll, its Site would be www.example.com. However, had the codebase been a file-based URL (for example, file:///C:/usr/bin/foo.dll), then there would be no Site in the evidence for the assembly. The Site evidence type is typically used to grant blanket trust to code downloaded from a trusted repository.

The Zone evidence type is also derived from the codebase URL. The CLR splits the world into five possible security zones, which are represented by the System.Security.SecurityZone enumeration:

namespace System.Security { public enum SecurityZone { MyComputer, Intranet, Trusted, Internet, Untrusted, NoZone = 0xFFFFFFFF } }

The MyComputer zone applies to all code loaded from a local file system. Code that originates from remote file systems is categorized based on settings made in the Internet Options dialog of Microsoft® Internet Explorer.

Internet Explorer defines three special-case URL categories. The Local Intranet category (represented by SecurityZone.Intranet) applies to all code loaded off of a remote file system using mapped network drives or UNC-style paths (for example, \\server\share\ code.dll). This zone also applies to HTTP-based URLs that use Windows Internet Name Service (WINS)-style names rather than DNS or IP-based host names (for example, https://server/vroot/code.dll). Internet Explorer allows you to further refine what constitutes qualification in this zone, but these are the default tests used.

Internet Explorer also defines two categories for recognized trustworthy and malicious sites. By default, these categories are empty; however, users or system administrators may add any number of pattern-based URLs to them. These categories are represented by SecurityZone.Trusted and SecurityZone.Untrusted, respectively. Codebase URLs that do not fall into any of the three special-case categories just described are placed in the generic SecurityZone.Internet zone.

The final location-based evidence type is ApplicationDirectory. This evidence type is actually provided by the host application and specifies the base directory for the running application. This evidence type is similar to the Zone type in that it partitions codebase URLs into categories. The ApplicationDirectory evidence type is typically used in concert with the Url evidence type to grant special permissions to DLLs loaded from the APPBASE directory of an application.

The CLR provides a programmatic type for each type of evidence. These types all reside in the System.Security.Policy namespace and are part of the mscorlib assembly. The code in Figure 1 creates the Url, Site, and Zone objects for a given codebase URL. Note that the Site type is a special case. This is because file-based URLs do not have an associated Site, and Site.CreateFromUrl will throw a System.ArgumentException if passed a file-based URL.

Figure 1 Creating Evidence Objects

using System; using System.Security; using System.Security.Policy; class App { static void Main(string[] argv) { // accept codebase URL as command-line argument string codebase = argv[0]; // create the three evidence objects Url url = new Url(codebase); Zone zone = Zone.CreateFromUrl(codebase); Site site = null; try { site = Site.CreateFromUrl(codebase); } catch (ArgumentException) { /* ignore */ } // display the interesting bits Console.WriteLine("url: {0}", url.Value); Console.WriteLine("zone: {0}", zone.SecurityZone); if (site != null) Console.WriteLine("site: {0}", site.Name); } }

So far I've discussed the location-based forms of evidence. The CLR also supports two evidence types that pertain to who developed the code independent of where it was loaded from. Of the two, the StrongName type is easier to understand.

Assemblies that have public keys as part of their name will be assigned a StrongName evidence type at load time. The three properties of the StrongName correspond to three of the four properties of an assembly name. The Name, Version, and PublicKey properties will be initialized by the loader based on the metadata of the assembly being loaded. As was the case with Site, Url, and Zone, you can construct a StrongName evidence object programmatically (see Figure 2). Be aware that StrongName evidence can only be created for assemblies with public keys. Also, note that in Figure 2 I needed a wrapper object of type System.Security.Permissions.StrongNamePublicKeyBlob to wrap the byte array containing the public key. This wrapper object accepts either public keys or public key tokens.

Figure 2 Creating Wrapper Object

using System; using System.Reflection; using System.Security; using System.Security.Permissions; using System.Security.Policy; class App { static StrongName CreateFromAssembly(Assembly assm) { // get the name and public key AssemblyName name = assm.GetName(); byte[] pk = name.GetPublicKey(); // construct a new StrongName evidence object StrongNamePublicKeyBlob blob = new StrongNamePublicKeyBlob(pk); return new StrongName(blob, name.Name, name.Version); } static void Main(string[] argv) { // accept assembly name as command-line argument string name = argv[0]; // load the assembly and grab the strong-name evidence Assembly assm = Assembly.Load(name); StrongName sn = CreateFromAssembly(assm); // display the interesting bits Console.WriteLine(sn.Name); Console.WriteLine(sn.Version); Console.WriteLine(sn.PublicKey); } }

Evidence based on strong name assumes that all parties recognize the public key as identifying a particular development organization. Unrecognized public keys are useless as there is no way to algorithmically discern the identity of the public key's owner. This capability is provided by X.509 certificates, which are used by the fifth evidence type, Publisher.

The Publisher evidence type is added by the assembly loader to code that is signed with an X.509 certificate. Unlike public/private key pairs, which can be reliably generated autonomously on the developer's machine, certificates assume the presence of a trusted certificate authority (CA), such as VeriSign, Entrust, or Thawte. These authorities only issue certificates to known entities who can prove their identity via out-of-band techniques. To allow developers to get started with certificates, Microsoft ships two tools that emit unverified certificates for testing purposes: makecert.exe and cert2spc.exe. These two tools produce X.509 certificates and Software Publisher Certificates (SPCs), respectively, however, the certificates they produce have no meaningful CA, so they are only useful for kicking the tires, not for shipping code. For details on acquiring legitimate certificates, visit your favorite CA's Web site.

Certificates can be applied to an assembly using the signcode.exe tool. This tool places the certificate in a well-known location in the DLL and calculates a digital signature to prevent tampering. The assembly loader notices this certificate at load time and attaches a Publisher evidence object to the loaded assembly. The code in Figure 3 demonstrates how to construct a Publisher evidence object based on a raw X.509 certificate loaded from disk. The first three properties of the certificate (Name, IssuerName, and ExpirationDate) convey most of what developers and system administrators care about. The remaining properties keep crypto-wonks from losing sleep and are beyond the scope of this article.

Figure 3 Construct Publisher Evidence

using System; using System.Reflection; using System.Security; using System.Security.Permissions; using System.Security.Policy; using System.Security.Cryptography.X509Certificates; class App { static void Main(string[] argv) { // accept cert file name as command-line argument string name = argv[0]; // load the certificate and grab the Publisher evidence X509Certificate cert = X509Certificate.CreateFromCertFile(name); Publisher pub = new Publisher(cert); // display the interesting bits X509Certificate value = pub.Certificate; Console.WriteLine(value.GetName()); Console.WriteLine(value.GetIssuerName()); Console.WriteLine(value.GetExpirationDateString()); Console.WriteLine(value.GetEffectiveDateString()); Console.WriteLine(value.GetCertHashString()); Console.WriteLine(value.GetSerialNumberString()); Console.WriteLine(value.GetPublicKeyString()); Console.WriteLine(value.GetKeyAlgorithm()); } }

The final evidence type I'll discuss is the Hash. The Hash evidence is simply a compact identifier that uniquely identifies a particular compilation of a component. The Hash evidence is added by the assembly loader to all assemblies and allows security policy to recognize particular builds of an assembly, even when the assembly version numbers have not changed.

The CLR defines a built-in type (System.Security.Policy.Evidence) for holding the pieces of evidence that are used by the security policy. The Evidence type is itself a simple collection and implements the System.Collections.ICollection interface. The Evidence type differs from a generic collection in that it keeps two internal collections: one for built-in host evidence objects and one for user-defined assembly evidence objects.

The code in Figure 4 demonstrates how to construct a new Evidence object that contains Url, Zone, and Site evidence. When run against the codebase URL https://www.microsoft.com/foo.dll, this program emits the following:

<System.Security.Policy.Url version="1"> <Url>https://www.microsoft.com/foo.dll</Url> </System.Security.Policy.Url> <System.Security.Policy.Zone version="1"> <Zone>Internet</Zone> </System.Security.Policy.Zone> <System.Security.Policy.Site version="1"> <Name>www.microsoft.com</Name> </System.Security.Policy.Site>

Note that each evidence object emits an XML-based representation of itself. I'll explain the use of this syntax later.

Figure 4 Construct Location Evidence

using System; using System.Security.Policy; class App { static void Main(string[] argv) { // accept codebase URL as command-line argument string codebase = argv[0]; // create and populate an Evidence object Evidence evidence = new Evidence(); evidence.AddHost(new Url(codebase)); evidence.AddHost(Zone.CreateFromUrl(codebase)); try { evidence.AddHost(Site.CreateFromUrl(codebase)); } catch (ArgumentException) { /* ignore */ } // display the interesting bits foreach (object part in evidence) { Console.WriteLine(part); } } }

Constructing Evidence objects programmatically is occasionally useful. However, the assembly loader is the primary user of this facility. The assembly loader makes an assembly's evidence available to security policy and to programmers via the System.Reflection.Assembly.Evidence property. Take a look at the following code fragment to see how you can access the evidence for an arbitrary object's assembly:

using System.Reflection; using System.Security.Policy; public sealed class Util { public static Zone WhichZone(object obj) { Evidence ev = obj.GetType().Module.Assembly.Evidence; IEnumerator i = ev.GetHostEnumerator(); while (i.MoveNext()) { Zone zone = i.Current as Zone; if (zone != null) return zone; } return null; // no zone } }

As you will see throughout this article, evidence is used primarily by security policy and is rarely accessed explicitly by programmers.

Policy

Evidence by itself is relatively useless. The real purpose of evidence is to act as input to a security policy. The CLR uses security policy to determine what permissions to assign to a given assembly based on the assembly's evidence. CLR security policy is configurable by system administrators and users. The CLR security policy is also extensible, allowing custom policy algorithms to be plugged into the existing infrastructure.

Security policy may be specified at up to four levels, which are represented by the System.Security.PolicyLevelType enumeration:

namespace System.Security { public enum PolicyLevelType { User, Machine, Enterprise, AppDomain } }

The User policy level is specific to an individual user, while the Machine policy level applies to all users on a specific host machine. The Enterprise policy level applies to a family of machines that are part of an Active Directory® installation. Finally, the AppDomain policy level is specific to a specific application running inside an operating system process.

Of the four policy levels, all but the AppDomain level are loaded automatically from XML-based configuration files that can be edited as raw XML or using the caspol.exe tool or the mscorcfg.msc MMC snap-in. The Machine and Enterprise levels are read from the files security.config and enterprisesec.config, respectively. These files reside in the CONFIG subdirectory of the version-specific installation directory for the CLR. The User policy level is read from the Application Data\Microsoft\CLR Security Config\v1.0.nnnn\security.config file found under the user-specific profile directory. The AppDomain policy must be specified programmatically by calling the System.AppDomain.SetAppDomainPolicy method.

The combination of the four policy levels is called the policy hierarchy of the system. Each level in the hierarchy grants a set of permissions based on the presented evidence. The resulting set of permissions that will be granted is derived by taking the intersection (not the union) of the permissions granted by each of the four policy levels, as shown in Figure 5.

Figure 5 Intersecting Permissions

Figure 5** Intersecting Permissions **

The policy hierarchy uses intersection rather than union because the security model of the CLR is based on granting privilege, not denying it. For readers familiar with Windows NT security, this model is similar to the notion of Win32® privileges or COM+ roles; that is, the only way to deny access is to neglect to grant the appropriate permissions. Unlike Win32 discretionary access control lists (DACLs), there is no way to explicitly deny access to a protected operation or resource. To that end, the default Enterprise, User, and AppDomain policy levels all grant full-trust permission regardless of the presented evidence. However, the default Machine policy level only grants full-trust permissions to code loaded from the MyComputer security zone. Code that is not from Microsoft or ECMA is loaded from other security zones and is granted considerably fewer permissions.

Like evidence, the policy hierarchy is used implicitly by the security infrastructure, but is also available for programmatic access. Each level in the hierarchy is exposed via the System.Security.Policy.PolicyLevel type, and the collection of policy levels is exposed via the System.Security.SecurityManager.PolicyHierarchy method. The code in Figure 6 uses this method to enumerate the policy levels used by the current program and display the file names used to load the policies. When executed, this program displays the following:

Enterprise: C:\WINNT\Microsoft.NET\Framework\v1.0.3512\ config\enterprisesec.config Machine: C:\WINNT\Microsoft.NET\Framework\v1.0.3512\ config\security.config User: C:\Documents and Settings\dbox\Application Data\ Microsoft\CLR Security Config\v1.0.3512\security.config

Note that by default, there is no AppDomain-specific policy.

Figure 6 Enumerating Policy Levels

using System; using System.Collections; using System.Security; using System.Security.Policy; class App { static void Main() { IEnumerator i = SecurityManager.PolicyHierarchy(); while (i.MoveNext()) { PolicyLevel level = (PolicyLevel)i.Current; Console.WriteLine("{0,10}: {1}", level.Label, level.StoreLocation); } } }

Each policy level consists of three components. A policy level contains a list of named permission sets (visible via the PolicyLevel.NamedPermissionSets property), each of which grants zero or more privileges. A policy level also contains a code group hierarchy (visible via the PolicyLevel.RootCodeGroup property) that is used to determine which permission sets to apply given a particular body of evidence. Finally, a policy level contains a list of full-trust assemblies (visible via the PolicyLevel.FullTrustAssemblies property) that explicitly lists the assemblies that contain types needed to enforce policy. For example, if a custom permission type is defined, its assembly must appear in this list in order for the security policy to recognize it. Since extending security policy is outside the scope of this article, I'll focus on the code groups and named permission sets.

Code groups are used to grant permissions based on evidence. To that end, a code group has two primary properties: a membership condition and the name of a permission set (see Figure 7). The membership condition uses the presented evidence to determine whether or not the assembly in question is a member of this code group. If the membership condition acknowledges that the evidence satisfies the condition, then the policy will grant the rights contained in the named permission set.

Figure 7 Code Group Conditions

Figure 7** Code Group Conditions **

Membership conditions are strongly typed and presented by CLR types that implement the System.Security.Policy.IMembershipCondition interface, which has one interesting method, Check:

namespace System.Security.Policy { public interface IMembershipCondition { bool Check(Evidence evidence); // remaining methods elided for clarity } }

Given an arbitrary policy level, the following method determines whether a given assembly is a member of the root code group:

static bool IsInRootGroup(Assembly assm, PolicyLevel level) { // grab evidence for assembly Evidence evidence = assm.Evidence; // grab condition for root code group CodeGroup group = level.RootCodeGroup; IMembershipCondition cond = group.MembershipCondition; // check for membership return cond.Check(evidence); }

Because the root code group for all built-in policies matches all assemblies, this method should always return true.

The system ships with eight built-in membership condition types. The Check method for AllMembershipCondition always returns true independent of the evidence (and is used on the root code group in each policy level). The Check method for ApplicationDirectoryMembershipCondition checks to see if the assembly in question is loaded from the application directory. This condition uses the Url evidence to determine the assembly's code base and relies on an ApplicationDirectory evidence object being present in the host-provided evidence. This evidence must be established by the host application, such as ASP.NET. The remaining six membership condition types (UrlMembershipCondition, ZoneMembershipCondition, and so on) correspond directly to the six host evidence types (Url, Zone, and so on) and allow membership to be determined directly by a particular piece of evidence.

To allow more than one permission set to be applied to a particular assembly, code groups are hierarchical and may contain child code groups. These child groups are visible via the CodeGroup.Children property. Most code groups that appear in a security policy are instances of type System.Security.Policy.UnionCodeGroup. If its membership condition is satisfied, the UnionCodeGroup enumerates all child code groups and takes the union of the permission sets of each child whose membership condition is also satisfied. Because a child code group may itself have children, this process may result in a large number of permission sets being evaluated. However, if the membership condition for a given code group is not satisfied, none of its children will be considered. For example, consider the code group hierarchy shown in Figure 8. If the membership condition for group A is not satisfied, then no other code groups will be considered (and the resulting permission set would be empty). If the membership condition for group A is satisfied, the groups B, C, G, H, I, and N will be considered. If group C's membership condition is satisfied, then groups D and E will be considered.

Figure 8 A Hierarchy

Figure 8** A Hierarchy **

Two properties can be set on a code group to alter its interpretation. The PolicyStatementAttribute.Exclusive property indicates that no other sibling code groups may be combined with this code group. Had this attribute been set on group B in the previous example, then if B's membership condition were satisfied, groups C, G, H, I, and N could not be considered. It is considered a policy error if the presented evidence matches more than one exclusive group in a given level of the hierarchy. The second property that can alter the interpretation of a code group is the PolicyStatementAttribute.LevelFinal. This property informs the security manager to disregard any lower policy levels. For example, had this attribute been set on a matching code group in the SecurityLevel.Machine policy level, the SecurityLevel.User and SecurityLevel.AppDomain policy levels would be ignored. This prevents less-privileged users and administrators from disabling critical components by neglecting to grant them the needed permissions.

The ability to resolve a body of evidence into a collection of matching code groups is exposed via the CodeGroup.ResolveMatchingCodeGroups method. This method simply runs the membership condition on the current code group and all children to determine which code groups match. As a convenience, the PolicyLevel type has a ResolveMatchingCodeGroups method that simply forwards the call to the root code group of the policy level after ensuring that the policy level has in fact been loaded:

namespace System.Security.Policy { public class Policylevel { CodeGroup ResolveMatchingCodeGroups(Evidence ev) { this.CheckLoaded(true); // if (ev == null) throw new ArgumentException("evidence"); return this.RootCodeGroup.ResolveMatchingCodeGroups(ev); } // remaining members elided for clarity } }

To find the matching code groups across all of the applicable policy levels, you can call the SecurityManager.ResolvePolicyGroups static method instead. This static method simply calls PolicyLevel.ResolveMatchingCodeGroups on each policy level in the policy hierarchy (machine or enterprise, for example). The C# program shown in Figure 9 uses SecurityManager.ResolvePolicyGroups to enumerate all groups of which a given assembly is a member.

Figure 9 Find Membership Groups

using System; using System.Collections; using System.Reflection; using System.Security; using System.Security.Policy; class App { static void DumpGroup(CodeGroup group, string prefix) { Console.WriteLine("{0}{1}['{2}']:{3}", prefix, group.GetType().Name, group.Name, group.PermissionSetName); foreach (CodeGroup child in group.Children) DumpGroup(child, prefix + " "); } static void Main(string[] argv) { string assmname = argv[0]; Evidence ev = Assembly.Load(assmname).Evidence; IEnumerator i = SecurityManager.ResolvePolicyGroups(ev); while (i.MoveNext()) DumpGroup((CodeGroup)i.Current, " "); } }

The SecurityManager.ResolvePolicyGroups method is somewhat limited, as the permission set names in the resulting code groups only make sense when you know which policy level the code group came from. Fortunately, there are several higher-layer methods that will resolve the permission sets as well. I'll describe these methods later in this article.

The default policy files that ship with the CLR include a built-in set of code groups (see Figure 10). Note that there is a distinct code group for each of the five SecurityZone values, as well as a distinguished group for assemblies with the Microsoft and ECMA public keys. New user-defined code groups typically are added as children to the All_Code group, unless the security zone will be used as part of the membership condition.

Figure 10 Built-in Code Groups

Figure 10** Built-in Code Groups **

For instance, running the example program described in Figure 9 on the mscorlib assembly would produce the following:

UnionCodeGroup['All_Code']:FullTrust UnionCodeGroup['All_Code']:Nothing UnionCodeGroup['ECMA_Strong_Name']:FullTrust UnionCodeGroup['My_Computer_Zone']:FullTrust UnionCodeGroup['All_Code']:FullTrust

Note that in the first and third policy levels (shown on lines 1 and 5 in the preceding code sample), the only matching code group is All_Code. Also notice that All_Code grants the FullTrust permission set to the code. That is because the first and third policy levels are the enterprise and user levels, both of which default to simply granting FullTrust to every assembly no matter what the evidence may be. The second policy level (shown on lines 2-4) correspond to the machine policy level. In that policy level, the All_Code group grants no permissions. However, the mscorlib also matched the membership conditions for the My_Computer_Zone and the ECMA_Strong_Name code groups, both of which grant FullTrust permissions to the code.

In the previous discussion, I mentioned several built-in named permission sets. The default policy files that ship with the CLR each contain seven predefined named permission sets (see Figure 11). The Nothing set grants no permissions and is used by the root code group in the Machine policy level. This allows the root code group to safely match any assembly without granting any permissions. The FullTrust and Everything permission sets are both used only for highly trusted components. The Everything group explicitly specifies all known permissions, where the FullTrust permission set simply acts as a signal to implicitly grant all possible permissions, including those that cannot be known in advance. As shown in Figure 10, the FullTrust permission set is granted to all code loaded from a local file system.

Figure 11 Built-in Permission Sets

Permission Set Description
Nothing The empty permission set (grants nothing)
FullTrust Implicitly grants unrestricted permissions for all permission types
Everything Explicitly grants unrestricted permissions for all built-in permission types
SkipVerification SecurityPermission: SkipVerification
Execution SecurityPermission: Execution
Internet FileDialogPermission: Open
IsolatedStoragePermission: DomainIsolationByUser (quota = 10240)
UIPermission: OwnClipboard | SafeTopLevelWindows
Printing Permission: SafePrinting
LocalIntranet SecurityPermission: Execution | Assert | RemotingConfiguration
FileDialogPermission: Unrestricted
IsolatedStoragePermission: AssemblyIsolationByUser (no quota)
UIPermission: Unrestricted
PrintingPermission: DefaultPrinting
EnvironmentPermission: Read-only (USERNAME/TMP/TEMP)
ReflectionPermission: ReflectionEmit
DNSPermission: Yes
EventLog: Instrument

The Internet and LocalIntranet permission sets both provide only a limited subset of permissions and are used for code loaded from remote file systems or the Internet. In both cases, only verifiable code may be run, and no access to unmanaged code is allowed. Moreover, access to the local file system is restricted based on file dialogs that require the user to explicitly select the file name. The Internet code group is more restrictive, as it does not allow code to be generated nor does it allow the clipboard to be read or DNS names to be resolved.

In addition to the predefined permission sets, the CLR also provides two special types of code groups that dynamically produce a permission set based on the presented evidence: NetCodeGroup and FileCodeGroup. NetCodeGroup produces a permission set that contains a dynamically calculated WebPermission that grants connect access to the site from which the code was downloaded. The FileCodeGroup produces a permission set that contains a dynamically calculated FileIOPermission that grants read-only file access to the directory from which the code was loaded. As shown in Figure 10, the NetCodeGroup is used to grant Web access to code from the Internet, Intranet, and Trusted security zones, and the FileCodeGroup is used to grant file access to code from the Intranet security zone.

It is important to remember that the result of running a body of evidence through a security policy is a permission set. This functionality is exposed via the Resolve method on the CodeGroup and PolicyLevel types. This method accepts a body of evidence and returns the corresponding permission set based on which code groups are matched. The Resolve methods return a System.Security.Policy.PolicyStatement object that indicates not only the resulting permission set but also the PolicyStatementAttribute that indicates whether the policy statement is exclusive and/or level final (see Figure 12).

Figure 12 Get Policy Levels

using System; using System.Collections; using System.Security; using System.Security.Policy; class App { static void Main(string[] argv) { string codebase = argv[0]; // construct evidence Evidence evidence = new Evidence(); evidence.AddHost(new Url(codebase)); evidence.AddHost(Zone.CreateFromUrl(codebase)); try { evidence.AddHost(Site.CreateFromUrl(codebase)); } catch (ArgumentException) { /* ignore */ } // walk each policy level resolving evidence along the way IEnumerator i = SecurityManager.PolicyHierarchy(); while (i.MoveNext()) { PolicyLevel level = (PolicyLevel)i.Current; PolicyStatement statement = level.Resolve(evidence); Console.WriteLine("Level: {0} (attributes = {1})", level.Label, statement.Attributes); Console.WriteLine(statement.PermissionSet); } } }

The Resolve method first finds the matching code groups and then takes the union of the permissions granted by each code group's permission set. To find the permissions used in the aggregate, you would need to take the intersection of each permission set, taking the PermissionStatementAttribute.LevelFinal property into account. Fortunately, the CLR provides this functionality via the higher-level SecurityManager.ResolvePolicy static method. The sample code in Figure 12 could be rewritten to use ResolvePolicy, as shown in Figure 13.

Figure 13 Using ResolvePolicy

using System; using System.Security; using System.Security.Policy; class App { static void Main(string[] argv) { string codebase = argv[0]; // construct evidence Evidence ev = new Evidence(); evidence.AddHost(new Url(codebase)); evidence.AddHost(Zone.CreateFromUrl(codebase)); try { evidence.AddHost(Site.CreateFromUrl(codebase)); } catch (ArgumentException) { /* ignore */ } // get the aggregate permissions from all levels PermissionSet ps = SecurityManager.ResolvePolicy(ev); Console.WriteLine(ps); } }

Note how the ResolvePolicy method returns only a permission set, not a policy statement as did PolicyLevel.Resolve. This is because the additional Attribute property on the policy statement is only needed when combining permissions from multiple policy levels. Because SecurityManager.ResolvePolicy has already taken into account every applicable policy level, there is no need to return anything beyond the resulting permission set.

In addition to properly intersecting each level's permission set, the SecurityManager.ResolvePolicy method also adds identity permissions based on the presented evidence. Each type of host evidence (for example Url, Zone, or StrongName) has a corresponding permission type. SecurityManager.ResolvePolicy walks the list of host evidence and gathers an extra permission from each piece of evidence that supports the IIdentityPermissionFactory interface. All of the built-in evidence types support this interface. Identity permission is always added by policy and can be used to demand that a caller or callers belong to a particular security zone or originate from a particular site.

Permissions

Permission sets are ultimately just a collection of zero or more individual permissions. Permission sets are exposed programmatically via the System.Security.PermissionSet namespace. Because PermissionSet implements the System.Collections.ICollection interface, you can treat a permission set as a standard collection. The elements of the collection are guaranteed to implement at least the System.Security.IPermission interface.

The IPermission interface is the primary interface for dealing with permissions. It models the permission settings for one category of operations. The CLR ships with a dozen or so built-in categories, each of which supports the IPermission interface. For a particular category of permission, there may be multiple protected operations. The IPermission interface provides the following methods to support set operations on permission objects:

namespace System.Security { public interface IPermission { IPermission Union(IPermission rhs); IPermission Intersect(IPermission rhs); bool IsSubsetOf(IPermission rhs); IPermission Copy(); void Demand(); } }

The first three methods allow permission objects of the same type to be treated as sets, as shown in Figure 14.

Figure 14 Permission Sets

Figure 14** Permission Sets **

Programming against IPermission is fairly intuitive, as shown in the C# method in Figure 15. In this example, the SecurityPermission type supports a bitmask that indicates which operations are allowed. The call to the Union method returns a new SecurityPermission object that has both the Execution and SkipVerification bits set (as if a bitwise OR had been performed). When the Intersect method is called in this example, the result is a new SecurityPermission object that has only the Execution bit set (as if a bitwise AND had been performed). The IsSubsetOf method simply tests to see whether every operation supported by p1 is also supported by p3, which can be calculated by comparing p1's bitmask to the result of intersecting p1 and p3.

Figure 15 Using IPermission

static void DoIt() { SecurityPermissionFlag f1 = SecurityPermissionFlag.Execution; SecurityPermissionFlag f2 = SecurityPermissionFlag.SkipVerification; IPermission p1 = new SecurityPermission(f1); IPermission p2 = new SecurityPermission(f2); // add p1 and p2's permissions together IPermission p3 = p1.Union(p2); // take permission that are in p1 and p3 IPermission p4 = p3.Intersect(p1); Debug.Assert(p4.IsSubsetOf(p3)); Debug.Assert(p1.IsSubsetOf(p3)); }

The IPermission methods assume that the provided permissions are of the same type. For example, it would be an error to pass a FileIOPermission object to the Intersect method of a WebPermission object. Also, while most permission types support a bitmask to indicate which operations are enabled, many permission types also carry additional information, such as a file path or host name. This type-specific information is factored into the IPermission method implementations. For example, consider the following example that uses the FileIOPermission type:

static void DoIt() { FileIOPermissionAccess all = FileIOPermissionAccess.AllAccess; IPermission p1 = new FileIOPermission(all,@"C:\etc"); IPermission p2 = new FileIOPermission(all,@"C:\etc\bin"); IPermission p3 = p1.Union(p2); // C:\etc allowed IPermission p4 = p1.Intersect(p2); // C:\etc\bin allowed Debug.Assert(p2.IsSubsetOf(p1)); Debug.Assert(p1.IsSubsetOf(p2) == false); }

The exact semantics of Union, Intersect, and IsSubsetOf are type specific. Consult the documentation for details for a particular permission type.

Permission types typically support a constructor that accepts a System.Security.Permissions.PermissionState enumeration to allow the new permission object to be set to a well-known state. The PermissionState enumeration is simple:

namespace System.Security.Permissions { public enum PermissionState { None, Unrestricted } }

Permission objects that are initialized with PermissionState.None represent the most restrictive set of permissions for that type. PermissionState.Unrestricted represents the least restrictive set of permissions for that type. Moreover, to allow permission objects to be checked for this least-restrictive state generically, permission types that support unrestricted access must also support the System.Security.Permissions.IUnrestrictedPermission interface:

namespace System.Security.Permissions { public interface IUnrestrictedPermission { bool IsUnrestricted(); } }

Permission types that support this interface indicate that they support the notion of unrestricted permissions, which are an implicit granting of all possible permissions for that permission type. When a permission object grants unrestricted permissions, all possible operations in that permission object's type are implicitly allowed.

PermissionState and IUnrestrictedPermission allow permission objects to be treated uniformly despite the details of how unrestricted access may be specified, allowing the following generic code to always hold true for all permission types T.

IUnrestrictedPermission perm = new T(PermissionState.Unrestricted); Debug.Assert(perm.IsUnrestricted());

Be aware, however, that a permission object may be unrestricted even if it is not explicitly initialized that way. For example, passing the SecurityPermissionFlag.AllAccess parameter to the constructor for SecurityPermission is equivalent to passing PermissionState.Unrestricted. Additionally, performing union operations on permission objects may produce an unrestricted permission object as the result.

While it is possible to define your own permission types by implementing the IPermission interface (and a few of its close cousins that are used to encode a permission object into XML), the CLR provides a family of built-in permission types that are used to protect various system-level resources. Figure 16 lists the most commonly used permission types. Note that all but the identity permissions support IUnrestrictedPermission. Also many of the permission types allow permissions to be set based on pattern matching. This is true for permissions that protect location-based resources such as the file system and network resources.

Figure 16 Built-in Permission Types

Namespace Name Description Unrestricted Pattern-based
System.Security.
Permissions
Security Basic execution environment capabilities Yes No
Reflection Read and write CLR metadata Yes No
Environment Access to OS environment variables Yes Yes
UrlIdentity Asserts codebase URL in assembly evidence No Yes
SiteIdentity Asserts Site in assembly evidence No Yes
ZoneIdentity Asserts SecurityZone in assembly evidence No No
StrongNameIdentity Asserts public key in assembly name No No
PublisherIdentity Asserts certificate in assembly evidence No No
Registry Access to Windows registry Yes Yes
FileIO Access to directories and files Yes Yes
IsolatedStorage Access private storage system Yes No
FileDialog Display file dialogs to the user Yes No
UI Access to window hierarchy and clipboard Yes No
System.Drawing Printing Access to attached and default printer Yes No
System.Net Dns DNS address translation Yes No
Socket Low-level socket usage (accept/connect) Yes Yes
Web High-level Web access (accept/connect) Yes Yes
System.
Messaging
MessageQueue Access to MSMQ features Yes Yes
System.Data.
Common
DBData Access to database provider features Yes No

It is often useful to combine permissions of different types. To that end, the CLR provides the PermissionSet type, which is used to aggregate permission objects of arbitrary type. A PermissionSet object holds at most one permission object per permission type. Adding a permission object that is an instance of a type already held by the permission set results in the presented permission object being unioned with the permission object that's already held. For example, consider the C# method shown in Figure 17. At the end of this method, the newly created permission set will contain two permission objects, one of type FileIOPermission and one of type SecurityPermission that allows both Execution and SkipVerification.

Figure 17 Combine Permissions

static void DoIt() { FileIOPermissionAccess all = FileIOPermissionAccess.AllAccess; SecurityPermissionFlag f1 = SecurityPermissionFlag.Execution; SecurityPermissionFlag f2 = SecurityPermissionFlag.SkipVerification; PermissionSet ps = new PermissionSet(PermissionState.None); ps.AddPermission(new SecurityPermission(f1)); ps.AddPermission(new SecurityPermission(f2)); ps.AddPermission(new FileIOPermission(all, @"C:\etc")); }

The PermissionSet type supports the three set-oriented operations of IPermission (Union, Intersect, and IsSubsetOf). The PermissionSet implementations of these methods simply traverse the individual permission objects and perform the set operations on the matching permission object in the second permission set (see Figure 18).

Figure 18 PermissionSet Operations

Figure 18** PermissionSet Operations **

Performing set operations on PermissionSet objects is trivial. For example, consider the C# code in Figure 19. This code produces the same permission set as the code in Figure 17 albeit in a somewhat less direct manner.

Figure 19 Same PermissionSet

static void DoIt() { FileIOPermissionAccess all = FileIOPermissionAccess.AllAccess; SecurityPermissionFlag f1 = SecurityPermissionFlag.Execution; SecurityPermissionFlag f2 = SecurityPermissionFlag.SkipVerification; PermissionSet ps1 = new PermissionSet(null); PermissionSet ps2 = new PermissionSet(null); PermissionSet ps3 = new PermissionSet(null); ps1.AddPermission(new SecurityPermission(f1)); ps2.AddPermission(new SecurityPermission(f2)); ps2.AddPermission(new FileIOPermission(all, @"C:\etc")); ps3 = ps1.Union(ps2); }

PermissionSets may also grant unresticted permissions. This is accomplished by passing PermissionState.Unrestricted to the permission set's constructor. When a permission set grants unrestricted permissions, all possible operations in all possible permission types are implicitly supported, provided that permission type supports IUnrestrictedPermission. A permission set that grants unrestricted permissions does not implicitly grant any rights for permission types that do not support IUnrestrictedPermission (the identity permissions, for example). However, it is legal to call AddPermission on an unrestricted permission set in order to explicitly grant those specific permissions.

Enforcement

As important as security policy is, it spends most of its life lying dormant until it is time for enforcement. Security policy is always enforced implicitly by the CLR itself. However, it is most often enforced explicitly by trusted libraries that want to protect a secure resource. You enforce security policy by demanding that all callers have been granted a particular permission or set of permissions. To that end, both the IPermission interface and PermissionSet class support a Demand method to allow explicit policy enforcement.

The Demand method triggers a stack walk in which the permissions of every method are inspected. The CLR calculates the permissions of each method by running the evidence from the method's assembly through the security policy. If at least one method on the stack does not have the permission being demanded, then a System.Security.SecurityException will be thrown by the Demand method. If, however, all methods on the stack do have the permission being demanded, then the Demand method will indicate this by not throwing the exception.

To call the Demand method, you first need either a permission or permission set object that specifies which permissions are being demanded. Consider the following C# method:

using System.Net; public sealed class Utils { public static IPAddress LookupHost(string host) { return Dns.GetHostByName(host).AddressList[0]; } }

The Dns.GetHostByName method requires that all callers have the System.Net.DnsPermission by calling Demand internally:

using System.Security.Permissions; namespace System.Net { public sealed class Dns { public static IPHostEntry GetHostByName(string host) { // enforce security policy DnsPermission perm = new DnsPermission(PermissionState.Unrestricted); perm.Demand(); // if we get here, DNS lookups are allowed by policy so // do the actual work (elided here for clarity) } }

Note that the GetHostByName method only worries about security at the beginning of the method. If policy prohibits DNS lookups, then the Demand method will prevent the remainder of the method from executing. Of course, if the Utils.LookupHost method wants to take care of this, the exception would need to be handled explicitly:

using System.Net; using System.Security; public sealed class Utils { public static IPAddress LookupHost(string host) { try { return Dns.GetHostByName(host).AddressList[0]; } catch (SecurityException) { return IPAddress.Loopback; } } }

For applications that care, the type of permission that was not satisfied is available as a property of the security exception object via the SecurityException.PermissionType property.

The Demand method requires that each method on the stack have at least the permissions being demanded, where "at least" is defined by the implementation of the IsSubsetOf method for the permission type. The top-of-stack permission set is established when a thread begins using the CLR. For threads that are explicitly created by CLR-based programs, this permission set is simply the intersection of permissions for each method in the spawning thread's call stack. Similarly, when a thread pool thread begins to service a work request, a snapshot from the spawning thread is used to set the top-of-stack permissions. In both cases, this prevents the secondary thread from performing operations that the spawning thread could not legally perform itself. For all other threads (including the "main" thread of a CLR-based application), the top-of-stack permissions are calculated based on the evidence provided when the AppDomain was created. The AppDomain.CreateDomain method accepts a parameter of type System.Security.Policy.Evidence for this very purpose.

The example from the System.Net.Dns class shown previously is was an example of an imperative security demand. It is called imperative because an explicit programmatic statement was executed. The CLR also supports declarative security demands based on attributes. For each permission type, the CLR defines a permission-specific custom attribute that derives from System.Security.Permissions.CodeAccessSecurityAttribute. These permission-specific attributes all accept a mandatory constructor parameter of type System.Security.Permissions.SecurityAction:

namespace System.Security.Permissions { public enum SecurityAction { Demand = 1, Assert, Deny, PermitOnly, LinkDemand, InheritanceDemand, RequestMinimum, RequestOptional, RequestRefuse, } }

For this part of the discussion, the only SecurityAction to consider is Demand. This security action would allow the following imperative demand

using System.Security.Permissions; namespace System.Net { public sealed class Dns { public static IPHostEntry GetHostByName(string host) { DnsPermission perm = new DnsPermission(PermissionState.Unrestricted); perm.Demand(); // if we get here, then the demand succeeded! } }

to be rewritten like this:

using System.Security.Permissions; namespace System.Net { public sealed class Dns { [DnsPermission(SecurityAction.Demand, Unrestricted=true)] public static IPHostEntry GetHostByName(string host) { // if we get here, then the demand succeeded! } }

The advantage of the latter example is that you can determine the security requirements of the code strictly by looking at the metadata for the type. The disadvantage of declarative security demands is that they cannot support permissions requiring dynamic information (for example, file paths) that cannot be known at compile time. Security attributes such as DnsPermission can be applied to either individual methods or to a type, which in effect applies the attribute to all methods of the type.

It is often desirable to tune the permissions that are granted to a given method. This is typically done to restrict the permissions that a given piece of code will run with. The CLR provides two ways to do this. You can restrict the effective permissions to a subset of those granted by policy or you can explicitly deny one or more permissions. Both of these actions can be taken either declaratively or imperatively. SecurityAction.PermitOnly and SecurityAction.Deny can be used with a SecurityAttribute to accomplish this declaratively. For example, consider the following code:

using System.Net; using System.Security.Permissions; public sealed class Utils { [ DnsPermission(SecurityAction.PermitOnly, Unrestricted=true) ] public static IPAddress LookupHost(string host) { return Dns.GetHostByName(host).AddressList[0]; } }

This example using declarative security is equivalent to the following imperative security-based example:

using System.Net; using System.Security.Permissions; public sealed class Utils { public static IPAddress LookupHost(string host) { DnsPermission perm = new DnsPermission(PermissionState.Unrestricted); perm.PermitOnly(); return Dns.GetHostByName(host).AddressList[0]; } }

In either example, if the DnsGetHostByName method were to demand any permission beyond DnsPermission, the demand would be denied even if the LookupHost method (and all of its callers) were in fact granted the requested permission by policy. Had the LookupHost simply wanted to deny the use of one particular permission, the following code would have sufficed:

using System.Net; using System.Security.Permissions; public sealed class Utils { [ FileIOPermission(SecurityAction.Deny, Unrestricted=true) ] public static IPAddress LookupHost(string host) { return Dns.GetHostByName(host).AddressList[0]; } }

And here's the imperative equivalent:

using System.Net; using System.Security.Permissions; public sealed class Utils { public static IPAddress LookupHost(string host) { FileIOPermission perm = new FileIOPermission(PermissionState.Unrestricted); perm.Deny(); return Dns.GetHostByName(host).AddressList[0]; } }

In this case, any demands for FileIOPermission in Dns.GetHostByName would be denied; however, any other permission types would be subject to policy.

It is important to note that for a given stack frame, only one permission set may be in place using Deny or PermitOnly. For example, consider the following method:

using System.Net; using System.Security.Permissions; public sealed class Utils { public static IPAddress LookupHost(string host) { FileIOPermission p1 = new FileIOPermission(PermissionState.Unrestricted); RegistryPermission p2 = new RegistryPermission(PermissionState.Unrestricted); p1.Deny(); p2.Deny(); return Dns.GetHostByName(host).AddressList[0]; } }

In this example, the second call to Deny will fail with an exception, because there already is a permission set in effect. To prohibit both permissions, you would need to use a permission set like the one shown in Figure 20. In this case, both permissions would be denied to Dns.GetHostByName.

Figure 20 Deny Both Permissions

using System.Net; using System.Security.Permissions; public sealed class Utils { public static IPAddress LookupHost(string host) { FileIOPermission p1 = new FileIOPermission(PermissionState.Unrestricted); RegistryPermission p2 = new RegistryPermission(PermissionState.Unrestricted); PermissionSet ps = new PermissionSet(null); ps.AddPermission(p1); ps.AddPermission(p2); ps.Deny(); return Dns.GetHostByName(host).AddressList[0]; } }

Trusted components often need permissions that their callers may not necessarily have been granted. For example, the System.Net.Dns type needs to call the underlying gethostbyname API function, which, like all API calls, causes the CLR to demand the SecurityPermissionFlags.UnmanagedCode permission. However, requiring all callers to have this permission would make the Dns type useless to all but the most trusted components. To address this situation, the CLR supports the Assert security action.

When a given security permission is asserted by a method, any demands for that permission will stop walking the stack at that method frame. By asserting a permission, you're indicating that the caller's permissions are not to be considered. Ironically, the act of asserting a permission is itself a protected operation that requires the asserting method to have the SecurityPermissionFlag.Assertion permission. Methods that assert a permission typically do so in concert with a demand for a lesser permission that callers are likely to have. For example, the Dns.GetHostByName method might carry the following security attributes:

using System.Security.Permissions; namespace System.Net { public sealed class Dns { [ DnsPermission(SecurityAction.Demand, Unrestricted=true), SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode) ] public static IPHostEntry GetHostByName(string host) { // if we get here, then the demand and assertion succeeded! } } }

It is important to note that an assertion will only succeed if the requesting method was granted the permission being asserted. You can't use assertion to gain permissions not already granted to the requesting method.

It is also possible to annotate an entire assembly to influence which permissions are actually granted. The SecurityAction.RequestMinimum action indicates that the specified permission must be granted to the assembly in order to successfully load. If the specified permissions cannot be granted by policy, then the assembly loader will fail the load. Moreover, the actual permissions that will be granted to the assembly's methods will be a subset of those granted by policy.

Specifically, the effective permissions of the method will simply be all of the permissions marked SecurityAction.RequestMinimum unioned with any permissions marked with a SecurityAction.RequestOptional attribute (policy allowing) less those permissions marked with a SecurityAction.RequestRefuse attribute. For example, consider the C# code in Figure 21. In this example, all methods in the assembly will have at least DnsPermission and EnvironmentPermission. If policy neglects to grant either of these permissions, the loading of the assembly will fail. Additionally, the assembly will also be granted any FileIOPermissions allowed by policy. However, the ability to write to the file C:\autoexec.bat will be explicitly denied. Figure 22 shows how the effective permissions for a given stack frame are calculated.

Figure 21 DnsPermission and EnvironmentPermission

using System.Net; using System.Security.Permissions; [assembly: DnsPermission(SecurityAccess.RequestMinimum, Unrestricted=true), EnvironmentPermission(SecurityAction.RequestMinimum, Unrestricted=true), FileIOPermission(SecurityAccess.RequestOptional, Unrestricted=true) FileIOPermission(SecurityAccess.RequestRefuse, Write=@"C:\autoexec.bat") ] public sealed class Utils { public static IPAddress LookupHost(string name) { return Dns.GetHostByName(name).AddressList[0]; } }

Figure 22 Permissions per Stack Frame

Figure 22** Permissions per Stack Frame **

There are two other security actions that I have not yet discussed, SecurityAction.InheritanceDemand and SecurityAction.LinkDemand. A SecurityAction.InheritanceDemand attribute allows a base type to demand that a derived type has been granted a given permission. Consider the following C# abstract base class:

using System.Security.Permissions; [ UIPermission(SecurityAction.InheritanceDemand, Unrestricted=true) ] public abstract class MyWindow { public abstract void ShowMe(); }

Any types that derive from MyWindow must have the UIPermission. This security demand will be performed implicitly as part of the loading of the derived type.

The SecurityAction.LinkDemand attribute is similar to SecurityAction.Demand. However, LinkDemand is used to place demands on the direct caller, not the entire stack. When the JIT compiler attempts to compile a particular method, any calls within that method to methods marked with the LinkDemand attribute will force a security check. Unlike a normal Demand call, this check will only look at the direct caller (in this case, the method being JIT-compiled). Also, unlike a normal SecurityAction.Demand attribute, which is evaluated each time the method is called, a SecurityAction.LinkDemand is only evaluated at JIT-compile time, which makes it much cheaper to enforce.

LinkDemand and InheritanceDemand both provide an ideal venue for using identity permissions. For example, consider the case in which a collection of assemblies from the same vendor need to work closely together. Unfortunately, the normal access modifiers (for example, public or internal) are extremely coarse-grained and don't allow you to grant access to a method to individual assemblies. However, by applying a LinkDemand attribute to the method that demands that the caller have a particular identity permission (StrongNameIdentityPermission, for instance), you could demand that all callers have a particular public key in their assembly name. Consider the simple C# class in Figure 23. Despite the fact that the DoIt method is marked public, it can only be called by methods bearing the public key that is specified in the LinkDemand attribute. Attempts to call this method from assemblies not bearing the designated public key will fail at JIT compile time. In the same vein, you could mark an abstract base class with an InheritanceDemand attribute (see Figure 24).

Figure 24 Requiring Public Key Again

{ StrongNameIdentityPermission( SecurityAction.InheritanceDemand, PublicKey= "002400000480000094000000060200000024000" + "0525341310004000001000100cb3cec5f0ac3e5" + "30b73fe823ce6084be139df6119fdc0d4ff3a65" + "680da90f2819a10ef6a1db0eb5c5e6ea3822456" + "92a1505f88ad8f6716d366fb2d9d553e0f680b3" + "09f7e78dca447a23ec892d13f150e7c7b7997e8" + "50dc64273860e752c1ffb75ed244522d293b46f" + "74d511e17f76b2874ee80cb82babea3b624b745" + "baca48b7") ] public abstract class PersonImpl { // members elided for clarity }

Figure 23 Requiring Public Key

using System; using System.Security.Permissions; using System.Reflection; [assembly: AssemblyKeyFile("pubpriv.snk") ] public sealed class Utils { [ StrongNameIdentityPermission(SecurityAction.LinkDemand, PublicKey= "002400000480000094000000060200000024000" + "0525341310004000001000100cb3cec5f0ac3e5" + "30b73fe823ce6084be139df6119fdc0d4ff3a65" + "680da90f2819a10ef6a1db0eb5c5e6ea3822456" + "92a1505f88ad8f6716d366fb2d9d553e0f680b3" + "09f7e78dca447a23ec892d13f150e7c7b7997e8" + "50dc64273860e752c1ffb75ed244522d293b46f" + "74d511e17f76b2874ee80cb82babea3b624b745" + "baca48b7") ] public static void DoIt() { Console.WriteLine("Hello, world"); } }

Given this type definition, any types that derive from PersonImpl must belong to assemblies bearing the specified public key. All attempts to load and initialize a type that derives from Person-Impl from assemblies not bearing this public key will fail at type initialization time.

Conclusion

The CLR supports a component-centric security model known as code access security. This model assumes that each assembly can provide evidence as to its origins, both in terms of who wrote the code and where it was downloaded from. Code access security uses a configurable security policy to grant permissions to code based on evidence. While the CLR implicitly enforces some aspects of security policy, it is the job of trusted libraries to enforce security explicitly, using either imperative programmatic interfaces or declarative attributes.

For related articles see:
Code Access Security
Introduction to Code Access Security
Code Access Security Basics

Don Boxis an architect at Microsoft working on next generation Web Service protocols and plumbing. His interests include type systems for XML and Web Services, metadata and discovery, and CLR-based software integration. Don's work with Web Services began in 1998 as one of the original authors of the SOAP specification. This article is adapted from Don's upcoming book, Essential .NET Volume 1: The Common Language Runtime, which is due out shortly from Addison-Wesley.