- Introduction
- Security Policy and Assemblies
- Evidence-Based Security Policy
- Determining Security Policy
- Administering Security Policy
- Principle of Least Privilege
Security Policy and Assemblies
Under .NET, the units of deployment are called assembliesapplications and components that can be composed of one or more dynamic link libraries or executables. Assemblies are self-describing, so there is no requirement for information to be in any external store (such as the Registry) for them to be deployed. Assemblies can be given unique names (strong names) using digital signature technology. With a unique name, an assembly can be versioned so that different versions of the same assembly can coexist on the same machine. Security policy permissions are granted or denied to assemblies.
Permissions
Examples of permissions include the following:
SecurityPermission controls access to the security system. This includes the right to call unmanaged code; control threads; and control principals, app domain, evidence, and the like.
FileIOPermission controls access to the file system.
ReflectionPermission controls access to nonpublic metadata and the dynamic generation of modules, types, and members.
All the permission classes inherit from the CodeAccessPermission base class, so they all behave in the same way. CodeAccessPermission has a method, Demand, that is used to request the permission. If the demand fails, the CLR throws a System.Security.SecurityException. Whenever a permission is demanded you have to be prepared to catch the exception if the permission demand was not granted. In many cases, the .NET Framework libraries will do that for you. For example, when you instantiate an instance of the FileInfo class to open a file, the constructor will demand to see if you have the right to open the file. Nonetheless, you still have to be prepared to handle the exception.
Following is an example of a simple code demand (CodeDemand). Before opening a file for reading, we demand all access rights to the file. While this request is superfluous because the FileInfo constructor will make the identical demand, it illustrates the code pattern. (Note that this example assumes the default security policy settings that permit file access for local code.)
using System; using System.IO; using System.Security; using System.Security.Permissions; public class Simple { public static int Main(string[] args) { string filename = ".\\read.txt"; try { // need full path for security check string fileWithFullPath = Path.GetFullPath(filename); FileIOPermission fileIOPerm = new FileIOPermission (FileIOPermissionAccess.AllAccess, fileWithFullPath); fileIOPerm.Demand(); } catch(Exception e) { Console.WriteLine(e.Message); return 1; } try { FileInfo file = new FileInfo(filename); StreamReader sr = file.OpenText(); string text; text = sr.ReadLine(); while (text != null) { Console.WriteLine(text); text = sr.ReadLine(); } sr.Close(); } catch(Exception e) { Console.WriteLine(e.Message); } return 0; } }
Without ACL rights to the file you will get an UnauthorizedAccessException.
You can also programmatically deny permissions. Change the Demand method to the Deny method in the previous example and the code will not be able to read the file.
When the CLR checks whether permission should be granted, it makes sure that every assembly on the call stack has that permission. Otherwise, untrusted code could use trusted code to circumvent the security system. A method in one assembly may work in one scenario but not another, depending on the rights of the code in another assembly calling that method.
The PermissionSet Class
If you want to demand or deny more than one permission at a time, you need to use the PermissionSet class. Here is a code fragment using this class:
UIPermission uiPerm = new UIPermission(PermissionState.Unrestricted); FileIOPermission fileIOPerm = new FileIOPermission(PermissionState.Unrestricted); PermissionSet ps = new PermissionSet(PermissionState.None); ps.AddPermission(uiPerm); ps.AddPermission(fileIOPerm);
This example creates a permission set containing two permissions: the FileIO permission and the user interface (UI) permission. With the user interface permission you can restrict the types of windows that code is allowed to display. For example, you can prevent code from putting up a window to gather unauthorized information. You can even prevent code from drawing or accessing user interface events or the Clipboard.
You can then use the Demand or Deny method on the permission set to request or restrict the permissions that are part of the set. The PermissionSet class also has a RemovePermission method.
Why Bother?
Why would anyone care about demanding or denying permissions? If you think in terms of security based on the identity executing the code, this idea of demanding and denying permissions seems bizarre. Consider, however, the case of using a third-party component, or wanting to protect against bugs in certain components. (As we will discuss shortly, some of this can be accomplished with a security policy as well.) Some code is just not trusted as much as other code. Code access security allows you to prevent untrusted code executing with a trusted user identity from performing certain actions.
To illustrate both the utility and the power of code access security, the following simple ThirdPartyCode example shows how you can use code access security to control third-party code. Let's say that our company wants to be able to use code from a variety of vendors. To that end, it defines an interface that all vendors can use in order to be able to integrate with outside software:
public interface IUserCode { int PotentialRogueCode(); }
Our code is written in such a way that any code that fulfills this specification can be used:
public void OurCode(IUserCode code) { ... int v = code.PotentialRogueCode(); ... }
This third-party code could be in a separate assembly or downloaded from the Internet. In any case, we use this third-party code in the following fashion:
public static int Main(string[] args) { ThirdParty thirdParty = new ThirdParty(); OurClass ourClass = new OurClass(); ourClass.OurCode(thirdParty); return 0; }
We pass an instance of the third-party code to be used by our routine, and then we invoke a method that was defined by the common interface.
Since we didn't write the third-party code, we don't fully trust it. In traditional Windows security, it would be very difficult to restrict what the code could do without restricting what our own trusted code could do. With code access security, we can deny permissions to our program when the third-party code executes, and restore those permissions when the third-party code is done. Here's the full version of the OurCode method:
public void OurCode(IUserCode code) { try { UIPermission uiPerm = new UIPermission(PermissionState.Unrestricted); FileIOPermission fileIOPerm = new FileIOPermission(PermissionState.Unrestricted); PermissionSet ps = new PermissionSet(PermissionState.None); ps.AddPermission(uiPerm); ps.AddPermission(fileIOPerm); ps.Deny(); Console.WriteLine("Permissions denied."); int v = code.PotentialRogueCode(); CodeAccessPermission.RevertDeny(); Console.WriteLine("Permissions allowed."); v = code.PotentialRogueCode(); } catch(Exception e) { Console.WriteLine(e.Message); } return; }
Note the use of the RevertDeny method to restore the permissions before trying to execute the method a second time. In our example, the PotentialRogueCode method attempts to read a file and display the output in a message box:
public int PotentialRogueCode() { string output = null; try { string filename = ".\\read.txt"; FileInfo file = new FileInfo(filename); StreamReader sr = file.OpenText(); string text; text = sr.ReadLine(); while (text != null) { output = output + " " + text; text = sr.ReadLine(); } sr.Close(); } catch(Exception e) { Console.WriteLine(e.Message); } try { MessageBox.Show(output, "Potential Rogue Code"); } catch(Exception e) { Console.WriteLine(e.Message); } return 0; }
When the permissions are denied, the code cannot read the file or put up a message box. When the permissions are restored, the file can be read and the contents displayed.
So we now understand how code rights work in the .NET environment, and we understand the concept of code access security. Note that your code, either explicitly or via the framework on your behalf, must ask the CLR whether the executing code has the appropriate permissions.