CATEGORII DOCUMENTE |
Asp | Autocad | C | Dot net | Excel | Fox pro | Html | Java |
Linux | Mathcad | Photoshop | Php | Sql | Visual studio | Windows | Xml |
One of the advantages of virtualized execution environments such as the CLR is that one can develop new security models 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 (e.g., 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 (CAS), which is the focus of this chapter.
Systems that are dynamically composed from components have unique security requirements. Because the 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 one normally would need to protect from malicious code. Unfortunately, the classic principal-based security model of Windows NT and UNIX ignores where the code came from and focuses only on who is running the code. For classic 1980s-era programs built before the widespread advent of 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 coarse-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. The CLR associates this evidence with the in-memory representation of the assembly, and the security system uses it to determine what privileges to grant to the newly loaded code. The CLR makes this determination by running the evidence through a security policy. The security policy accepts evidence as input and produces a permission set as output. To avoid undue performance costs, the system typically does not run security policy until an explicit security demand is made.
Permission sets, such as 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 one can transparently integrate user-defined permission types into the model.
The determination of which code is assigned which permissions is called policy. The CLR uses a distinct set of mechanisms to enforce security policy. 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, 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 their 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 CLI categorizes code into two broad families: verifiable code and nonverifiable code. Verifiable code can be mathematically proven to adhere to the type-safe execution model that the CLI encourages. VB.NET and C# produce verifiable code by default. However, certain language features are not verifiably type-safe, C++'s reinterpret_cast being the canonical example. For that reason, code emitted by the C++ compiler is explicitly nonverifiable, and the use of this code can compromise the security of the system. To protect the integrity of the system, the ability to load nonverifiable code is itself a permission that one must explicitly grant to code through policy. The default policy that is installed with the CLR grants this permission only to code that is installed on a local file system. Similarly, the ability to call out to classic C-based or COM DLLs (which by definition are nonverifiable) is also a trusted operation that, by default, is granted only 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 occurs only 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, you can minimize the cost of enforcement by factoring security into your design, typically by avoiding excessive enforcement through explicit programming techniques. These techniques will be discussed later in this chapter.
The code-access security story begins with evidence. Evidence acts as testimony as to 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 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 the detection of a particular compilation of a piece of code, independent of version number.
Collectively, these seven types of evidence are called host evidence because they are implemented by the host environment. It is possible to define your own evidence types, which collectively are called assembly evidence because they are explicitly provided by the assembly itself. Defining new assembly evidence types also involves extending the policy mechanism to recognize them, something that is beyond the scope of this book. For the remainder of this chapter, the focus will be on the built-in host evidence types.
Recall that the assembly loader ultimately works in terms of codebase URLs, some of which may be file-based. The CLR uses the codebase URL to determine three of the four location-based evidence types: Url, Zone, and Site. The Url evidence is the easiest to understand because it is simply the codebase URL in its 'raw' form. The Site and Zone types are derived from the codebase URL based on its contents.
The Site evidence type is simply the host name portion of an HTTP-based or FTP-based URL. For example, if an assembly's codebase is https://www.acme.com/foo.dll, its Site would be www.acme.com. However, had the codebase been a file-based URL (e.g., file:///C:/usr/bin/foo.dll), then there would be no Site in the evidence for the assembly. One typically uses the Site evidence type 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.SecurityThe MyComputer zone applies to all code loaded from a local file system. The CLR categorizes code that originates from remote file systems based on settings made in the Internet Options dialog box of Internet Explorer.
Internet Explorer defines three special-case categories of URLs. The Local Intranet category (represented by SecurityZone.Intranet) applies to all code loaded off of a remote file system using UNC-style paths (e.g., serversharecode.dll). This zone also applies to HTTP-based URLs that use WINS-style names rather than DNS or IP-based host names (e.g., 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 these categories. 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. The host application must explicitly provide this evidence type, which 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. One typically uses the ApplicationDirectory evidence type 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 in the mscorlib assembly. For example, the following code creates the Url, Site, and Zone objects for a given codebase URL:
using System;Note that the Site type is special-cased. This is because file-based URLs do not have an associated Site, and the Site.CreateFromUrl method will throw a System.ArgumentException if passed a file-based URL.
The discussion so far has looked at 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 the 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 loader will initialize the Name, Version, and PublicKey properties 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, as shown here:
using System;Be aware that one can create StrongName evidence only for assemblies with public keys. Also note that, in this example, one needs 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.
Evidence based on StrongName assumes that all parties recognize the public key as identifying a particular development organization. Unrecognized public keys are useless because 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 sixth evidence type, Publisher.
The assembly loader adds the Publisher evidence type 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 issue certificates only 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 untrustworthy 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 useful only for kicking the tires, not for shipping code. For more details on acquiring legitimate certificates, visit your favorite CA's Web site.
One can apply certificates to an assembly using the signcode.exe tool. The signcode.exe 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 following code demonstrates how to construct a Publisher evidence object based on a raw X.509 certificate loaded from disk:
using System;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 book.
The final evidence type to be discussed is the Hash evidence type. The Hash evidence is simply a compact identifier that uniquely identifies a particular compilation of a component. The assembly loader adds the Hash evidence to all assemblies to allow 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 following code demonstrates how to construct a new Evidence object that contains Url, Zone, and Site evidence:
using System;When run against the codebase URL https://www.microsoft.com/foo.dll, this program emits the following:
<System.Security.Policy.Url version='1'>Note that each evidence object emits an XML-based representation of itself. The use of this syntax will be explained later in this chapter.
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. The following code fragment illustrates how to access the evidence for an arbitrary object's assembly:
using System.Reflection;As we will see throughout this chapter, evidence is used primarily by security policy and is rarely accessed explicitly by programmers.
Evidence by itself is relatively useless. Rather, the raison d'tre for 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. System administrators and users can configure CLR security policy. The CLR security policy is also extensible, allowing one to plug in custom policy algorithms to the existing infrastructure.
One can specify security policy at up to four levels, which are represented by the System.Security.PolicyLevelType enumeration:
namespace System.SecurityThe User policy
level is specific to an individual user, whereas the Machine
policy level applies to all users on a specific host machine. The
Of the four policy levels, all
but the AppDomain level are loaded automatically from
XML-based configuration files that one can edit as raw XML or by using the caspol.exe
tool or mscorcfg.msc MMC snap-in. The Machine
and
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. One derives the resultant set of permissions that will be granted by taking the intersection (not the union) of the permissions granted by each of the four policy levels, as shown in Figure 9.1.
The policy hierarchy uses the
intersection rather than the union because the security model of the CLR is
based on granting, not denying, privilege. 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 DACLs, this model gives one no way to explicitly
deny access to a protected operation or resource. To that end, the default
Like evidence, the policy hierarchy is used implicitly by the security infrastructure but is also available for programmatic access. The CLR exposes each level in the hierarchy via the System.Security.Policy.PolicyLevel type, and one exposes the collection of policy levels via the System.Security.SecurityManager.PolicyHierarchy method. The following code uses this method to enumerate the policy levels used by the current program and to display the file names used to load the policies:
using System;On the author's machine, this program displays the following:
Note that, by default, there is no AppDomain-specific policy.
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 one uses 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. Because extending security policy is outside the scope of this book, we will 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. This is illustrated in Figure 9.2. 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.
Membership conditions are strongly typed and are represented by CLR types that implement the System.Security.Policy.IMembershipCondition interface. This interface has one interesting method, Check:
namespace System.Security.PolicyGiven an arbitrary policy level, the following method determines whether or not a given assembly is a member of the root code group:
static bool IsInRootGroup(Assembly assm,Because the root code group for all built-in policies matches all assemblies, this method would always return true.
The system ships with eight built-in membership condition types. The Check method for the AllMembershipCondition always returns true independent of the evidence (and is used on the root code group in each policy level). The Check method for the ApplicationDirectoryMembershipCondition checks to see whether the assembly in question is loaded from the application directory. This condition uses the Url evidence to determine the assembly's code base. This condition also relies on an ApplicationDirectory evidence object being present in the host-provided evidence. The host application, such as ASP.NET, must establish this evidence. The remaining six membership condition types (UrlMembershipCondition, ZoneMembershipCondition, etc.) correspond directly to the six host evidence types (e.g., Url, Zone, etc.) 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 the evaluation of a large number of permission sets. 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 9.3. If the membership condition for group A is not satisfied, then no other code groups will be considered (and the resultant 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.
One can set two properties on a code group that 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 have been ignored. This prevents less-privileged users and administrators from disabling critical components by neglecting to grant them the needed permissions.
One uses the CodeGroup.ResolveMatchingCodeGroups method to expose the ability to resolve a body of evidence into a collection of matching code groups. 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.PolicyTo find the matching code groups across all applicable policy levels, one can instead call the SecurityManager.ResolvePolicyGroups static method. This method simply calls PolicyLevel.ResolveMatchingCodeGroups on each policy level in the policy hierarchy (e.g., machine, enterprise, etc). The following C# program uses SecurityManager.ResolvePolicyGroups to enumerate all groups that a given assembly is a member of:
using System;The SecurityManager.ResolvePolicyGroups method is somewhat limited because the permission set names in the resultant code groups make sense only when one knows which policy level the code group came from. Fortunately, there are also several higher-layer methods that will resolve the permission sets. These methods will be described later in this section.
The default policy files that ship with the CLR include a built-in set of code groups. Figure 9.4 shows these groups. 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. One typically adds new user-defined code groups as children to the All_Code group, unless one will be using the security zone as part of the membership condition.
Microsoft periodically updates the default security policy to address security issues discovered after the initial release of the CLR. The exact policy shown here corresponds to the RTM version of the CLR. Subsequent updates and service packs are likely to alter these defaults.
For example, running the example program just described on the mscorlib assembly would produce the following:
UnionCodeGroup['All_Code']:FullTrustNote that in the first and third policy levels (shown on lines 1 and 5), the only matching code group is All_Code and that it 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. The second policy level (shown on lines 2-4) corresponds 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 ECMA_Strong_Name code groups, both of which grant FullTrust permissions to the code.
The previous discussion mentioned several built-in named permission sets. The default policy files that ship with the CLR each contain seven predefined named permission sets. Table 9.1 shows these sets. The Nothing set grants no permissions, and the root code group uses it 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, whereas the FullTrust permission set simply acts as a signal to implicitly grant all possible permissions, including those that cannot be known a priori. As shown earlier in Figure 9.4, the FullTrust permission set is granted to all code loaded from a local file system.
Table 9.1. Built-in Permission Sets |
|
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 |
SecurityPermission: Execution FileDialogPermission: Open Isolated Storage Permission: DomainIsolationByUser (quota=10240) UIPermission: OwnClipboard | SafeTopLevelWindows PrintingPermission: SafePrinting |
LocalIntranet |
SecurityPermission: Execution | Assert | RemotingConfiguration FileDialogPermission: Unrestricted Isolated Storage Permission: AssemblyIsolationByUser (no quota) UIPermission: Unrestricted PrintingPermission: DefaultPrinting EnvironmentPermission: Read-only (USERNAME/TMP/TEMP) ReflectionPermission: ReflectionEmit DNSPermission: Yes EventLog: Instrument |
The Internet and LocalIntranet 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 dialog boxes that require the end user to explicitly select the file name. The Internet code group is more restrictive because 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 provides two special types of code groups that dynamically produce a permission set based on the presented evidence. These two code group types are NetCodeGroup and FileCodeGroup. The 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 9.4, the default machine policy uses the NetCodeGroup to grant Web access to code from the Internet, Intranet, and Trusted security zones, and one uses the FileCodeGroup 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. The CLR exposes this functionality 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 not only a System.Security.Policy.PolicyStatement object, which indicates the resultant permission set, but also the PolicyStatementAttribute, which indicates whether the policy statement is exclusive and/or level final. This is illustrated in the following C# program:
using System;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, one would need to take the intersection of each permission set, taking the PermissionStatementAttribute.LevelFinal into account. Fortunately, the CLR provides this functionality via the higher-level SecurityManager.ResolvePolicy static method. The example just shown could be rewritten to use ResolvePolicy as follows:
Note how the ResolvePolicy method returns only a permission set, not a policy statement as did PolicyLevel.Resolve. This is because one needs the additional Attribute property on the policy statement only 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 resultant permission set.
In addition to properly intersecting each level's permission set, the SecurityManager.ResolvePolicy method adds identity permissions based on the presented evidence. Each type of host evidence (e.g., Url, Zone, 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.
Permission sets are ultimately just a collection of zero or more individual permissions. The CLR exposes permission sets programmatically via System.Security.PermissionSet. Because PermissionSet implements the System.Collections.ICollection interface, one can treat the permission set as a standard collection. The elements of that collection are guaranteed to implement at least the System.Security.IPermission interface.
The IPermission interface is the primary interface for dealing with permissions. The IPermission interface 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 in that category. The IPermission interface provides the following methods to support set operations on permission objects:
namespace System.SecurityThe first three methods allow one to treat permission objects of the same type as sets. This is illustrated in Figure 9.5.
Programming against IPermission is fairly intuitive. For example, consider the following C# method:
static void DoIt()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 the 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 whether every operation supported by p1 is also supported by p3, an answer that one can calculate by comparing p1's bitmask to the result of intersecting p1 and p3.
The IPermission methods assume that the provided permission is 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, whereas 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. The CLR factors this type-specific information into the IPermission method implementations. For example, consider the following example, which uses the FileIOPermission type:
static void DoIt()The exact semantics of
Permission types typically support a constructor that accepts a System.Security.Permissions.PermissionState enumeration to allow one to set the new permission object to a well-known state. The PermissionState enumeration is simple:
namespace System.Security.PermissionsPermission objects that are initialized with PermissionState.None represent the most restrictive set of permissions for that type. Permission objects that are initialized with PermissionState.Unrestricted represent 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.PermissionsPermission 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 one to treat permission objects 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 permBe 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.
Although 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. Table 9.2 lists the most commonly used permission types. Note that all but the identity permissions support IUnrestrictedPermission. Also, many of the permission types allow one to set permissions based on pattern matching. This is true for permissions that protect location-based resources such as the file system and network resources.
It is often useful to combine permissions of different types together. To that end, the CLR provides the PermissionSet type, which is used to aggregate permission objects of arbitrary type together. 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 already held. For example, consider the following C# method:
static void DoIt()
Table 9.2. Built-in Permission Types |
||||
Namespace |
Name |
Description |
Unre-stricted |
Pattern-based |
System.Security.Permissions |
Security |
Basic execution environment capabilities |
Yes |
No |
Reflection |
Read and write CLR metadata |
Yes |
No |
|
Environment |
Access to OS environmental 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 evidence |
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 to per-user storage system |
Yes |
No |
|
FileDialog |
Display file dialogs to 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 |
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.
The PermissionSet
type supports the three set-oriented operations of IPermission
(i.e.,
Performing set operations on PermissionSet objects is trivial. For example, consider the following C# code:
static void DoIt()This code produces the same permission set that the previous example did, albeit in a somewhat less direct manner.
Permission sets may also grant unrestricted permissions. One accomplishes this 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 the 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 (e.g., the identity permissions). However, it is legal to call AddPermission on an unrestricted permission set to explicitly grant those specific permissions.
As important as security policy is, it spends most of its life lying dormant until it is time for enforcement. The CLR itself sometimes implicitly enforces security policy; however, security policy is most often enforced explicitly by trusted libraries that wish to protect a secure resource. One enforces 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 the 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 the Demand method will throw a System.Security.SecurityException. 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, one first needs either a permission or a permission set object that specifies which permissions are being demanded. Consider the following C# method:
using System.Net;The Dns.GetHostByName method calls Demand internally to require that all callers have the System.Net.DnsPermission:
using System.Security.Permissions;Note that the GetHostByName method worries about security only 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 our Utils.LookupHost method wants to deal with this, the exception would need to be handled explicitly:
using System.Net;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.
As shown in Figure 9.7, 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. This figure also illustrates the top-of-stack permission set. 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, the CLR uses a similar snapshot from the spawning thread 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 CLR calculates the top-of-stack permissions 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 just shown 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.PermissionsFor this part of the discussion, the only SecurityAction to consider is Demand. Consider the following imperative demand:
using System.Security.Permissions;This security action allows one to rewrite the preceding imperative demand as follows:
using System.Security.Permissions;The advantage of the latter example is that one can determine the security requirements of the code strictly by looking at the metadata for the type. The disadvantage to declarative security demands is that they cannot support permissions requiring dynamic information (for example, file paths) that cannot be known at compile time. One can apply security attributes such as DnsPermission to either individual methods or to a type. The latter 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. One typically does this to restrict the permissions that a given piece of code will run with. The CLR provides two ways to do this. One way is to restrict the effective permissions to a subset of those granted by policy. The other way is to explicitly deny one or more permissions. One can take both of these actions either declaratively or imperatively. One can use the SecurityAction.PermitOnly and SecurityAction.Deny with a SecurityAttribute to accomplish this declaratively. For example, consider the following code:
using System.Net;This example using declarative security is equivalent to the following imperative security-based example:
using System.Net;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;This is the imperative equivalent:
using System.Net;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, one can have only one permission set in place using Deny or PermitOnly. For example, consider the following method:
using System.Net;In this example, the second call to Deny would cause an exception to be raised because there is already a Deny in place. To prohibit both permissions, one would need to use a permission set as follows:
using System.Net;In this case, both permissions would be denied to Dns.GetHostByName.
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 method asserts a given security permission, any demands for that permission will stop walking the stack at that method frame. As shown in Figure 9.8, by asserting a permission, one is 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;
It is important to note that an assertion will succeed only if the requesting method was granted the permission being asserted. One cannot 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 following C# code:
using System.Net;In this example, all methods in the assembly will have at least the DnsPermission and EnvironmentPermission. If policy neglects to grant either of these permissions, then the loading of the assembly will fail. Additionally, the CLR will grant the assembly any FileIOPermissions allowed by policy. However, the CLR will explicitly deny the ability to write to the file C:autoexec.bat. Figure 9.9 shows how the effective permissions for a given stack frame are calculated.
There are two other security actions that have not been 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. For example, consider the following C# abstract base class:
using System.Security.Permissions;Any types that derive from MyWindow must have the UIPermission. The CLR will perform this security demand implicitly as part of the loading of the derived type.
The SecurityAction.LinkDemand attribute is similar to the SecurityAction.Demand attribute; however, SecurityAction.LinkDemand is used to place demands on the direct caller and not the entire stack. When the JIT compiler attempts to JIT-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 look only 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 evaluated only at JIT-compile time, and that makes it considerably cheaper to enforce.
Both LinkDemand and InheritanceDemand 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 (e.g., public, internal) are extremely coarse-grained and don't allow you to grant access to a method to particular assemblies. However, by applying a LinkDemand attribute to the method that demands that the caller have a particular identity permission (e.g., StrongNameIdentityPermission), one could demand that all callers have a particular public key in their assembly name. For example, consider the following simple C# class:
using System;Despite the fact that the DoIt method is marked as public, it can be called only 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, one could mark an abstract base class with an InheritanceDemand attribute:
Given this type definition, any types that derive from PersonImpl must belong to assemblies bearing the specified public key. Attempts to load and initialize a type that derives from PersonImpl from assemblies not bearing this public key will fail at type initialization time.
The CLR supports a component-centric security model known as code-access security. Code-access security 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. Although 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.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1531
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved