Code Access Permissions

Team-Fly    

 
Application Development Using Visual Basic and .NET
By Robert J. Oberg, Peter Thorsteinson, Dana L. Wyatt
Table of Contents
Chapter 16.  Security


Code needs permissions in order to access a resource such as a file or to perform certain operations. A security policy (discussed later in the chapter) will give certain permissions to each assembly. Code access permissions can be requested by code. The CLR will decide which permissions to grant based on the security policy for that assembly. You can even implement your own custom permissions for very specialized security situations. However, that is beyond the scope of this book. Here are some examples of predefined code access permissions:

  • DNSPermission controls access to domain name servers on the network.

  • EnvironmentPermission controls read or write access to environment variables .

  • FileIOPermission controls access to files and directories.

  • FileDialogPermission allows files selected in an Open dialog box to be read. This is useful if FileIOPermission has not been granted.

  • ReflectionPermission controls the ability to access nonpublic metadata.

  • RegistryPermission controls the ability to access and modify the registry.

  • SecurityPermission controls the use of the security subsystem.

  • SocketPermission controls the ability to make or accept connections on a transport address.

  • UIPPermission controls the user of various user interface features, including the clipboard.

  • WebPermission controls making or accepting connections on a Web address.

The use of these permissions is referred to as Code Access Security because this permission is based not on the identity of the user running the code, but on whether the code itself has the right to take certain actions.

Simple Permission Code Request

The SimplePermissionCodeRequest example first requests permission to access a file. If the CLR does not grant that request, the CLR will throw a SecurityException inside the file constructor. However, this code first tests to see if it has that permission. If it does not, it just returns instead of trying to access the file. [8]

[8] We have not yet discussed how you set security policy, so we do not yet know how to grant or revoke this permission. By default, however, code running on the same machine that it resides on has this permission.

This step is generally superfluous because the CLR will do the demand inside of the constructor, but often you want to check permissions before you execute some code to ascertain if you have the rights you need.

The FileIOPermission class models the CLR file permissions. A full path must be supplied to its constructor, and we use the Path class we discussed in Chapter 10 to get the full path. We are asking for read, write, and append file access. Other possible access rights are NoAccess or PathDiscovery. The latter is required to access information about the file path itself. You might want to allow access to the file, but you may want to hide information in the path, such as directory structure or user names .

The demand request checks to see if we have the required permission. The Demand method checks all the callers on the stack to see if they have this permission. In other words, we want to make sure not only that the assembly that this code is running in has this permission, but that all the assemblies that this code is running on behalf of has this permission. If an exception was generated, we do not have the permission we demanded, so we then exit the program.

 graphics/codeexample.gif Dim filename As String = "..\read.txt" ' need full path for security check Dim fileWithFullPath As String = _  Path.GetFullPath(filename)  Try  Dim fileIOPerm As FileIOPermission = _   New FileIOPermission(_   FileIOPermissionAccess.AllAccess, _   fileWithFullPath)   fileIOPerm.Demand()  Catch e As Exception    Console.WriteLine(e.Message)    Return End Try Try    Dim file As FileInfo = New FileInfo(filename)    Dim sr As StreamReader =  File.OpenText()  Dim text As String    text =  sr.ReadLine()  While Not text Is Nothing       Console.WriteLine(text)       text =  sr.ReadLine()  End While    sr.Close() Catch e As Exception    Console.WriteLine(e.Message) End Try 

Even if the code has the CLR read permission, the user must have read permission from the file system. If the user does not, the UnauthorizedAccessException will be thrown when the OpenText method is called.

You have to be careful when passing objects that have passed a security check in their constructor to code in other assemblies. Since the check was made in the constructor, no further check is made by the CLR to ascertain access rights. The assembly you pass the object to might not have the same rights as your assembly. If you were to pass this FileInfo object to another assembly that did not have the CLR read permission, it would not be prevented from accessing the file by the CLR, because no additional security check will be made. This is a design compromise for performance reasons to avoid making security checks for every operation. This is true for other code access permissions as well.

How a Permission Request Works

To determine whether code is authorized to access a resource or perform an operation, the CLR performs checks on all the callers on the stack frame to make sure that each assembly that has a method call on the stack can be granted the requested permission. If any caller in the stack does not have the permission that was demanded, a SecurityException is thrown.

Less trusted code is not permitted to use trusted code to perform an unauthorized action. The procedures on the stack could come from different assemblies that have different sets of permissions. For example, an assembly that you build might have all rights, but it might be called by a downloaded component that you would want to have restricted rights, so that it cannot open your email address book, delete files, and so on.

As discussed in the next sections, you can modify the results of the stack walk by using Deny or Assert methods on the CodeAccessPermission base class.

Strategy for Requesting Permissions

Code should request permissions that it needs before it uses them so that it is easier to recover if the permission request is denied . For example, if you need to access several key files, it is much easier to check to see if you have the permissions when the code starts up rather than when you are halfway through a delicate operation and then have to roll back several operations. In some situations, the user could be told up front that certain functions will not be available, so that he or she will not be surprised later when certain operations are blocked.

On the other hand, you should not request permissions that you do not need. This will minimize the chances that your code will do damaging things from bugs or malicious third-party code and components . In fact, you can restrict the permissions you have to the minimum necessary to prevent such damage. For example, if you do not want a program to read and write the files on your disk, there is no good reason to permit it, and therefore, you should explicitly deny the right to do so.

Denying Permissions

One can apply the Deny method to the permission. Even though security policy would permit access to the file, any attempt to access the file will fail. The SimplePermissionCodeDenial example demonstrates this. Instead of demanding the permission, we invoke the Deny method on the FileIOPermission object. We then try to read the file in a separate method named ReadFile .

 graphics/codeexample.gif ... Try    fileIOPerm.  Deny  ()    Console.WriteLine("File Access Permission Denied") Catch se As SecurityException    Console.WriteLine(se.Message) End Try  ReadFile()  ... 

The reason we attempt to read the file in the separate ReadFile method will be explained shortly, when we discuss the Assert method. For now, notice that since the permission was denied, the FileInfo constructor in the ReadFile method will throw a SecurityException .

 Public Sub  ReadFile  ()    Try       Dim file As FileInfo =  New FileInfo(filename)  Dim sr As StreamReader = File.OpenText()       Dim text As String       Text = sr.ReadLine()       While text <> Nothing          Console.WriteLine("     " + text)          text = sr.ReadLine()       End While       sr.Close()  Catch se As SecurityException  Console.WriteLine(_          "Could not read file: " + se.Message)    End Try End Sub 

We then call the static RevertDeny method on the FileIOPermission class to remove the permission denial, and attempt to read the file again. This time the file can be read without throwing an exception. The call to Deny is good until the containing code returns to its caller or a subsequent call to Deny . RevertDeny removes all current Deny requests.

 ... Try  FileIOPermission.RevertDeny()  Console.WriteLine(_       "File Access Permission Restored") Catch se As SecurityException    Console.WriteLine(se.Message) End Try  ReadFile()  ... 

We then invoke the Deny method to once again remove the permission and attempt to read the file, throwing an exception.

 ... Try    fileIOPerm.  Deny()  Console.WriteLine(_       "File Access Permission Denied") Catch se As SecurityException   Console.WriteLine(se.Message) End Try  ReadFile()  ... 

Asserting Permissions

The Assert method allows you to demand a permission even though you do not have access rights to do so due to callers higher in the stack that have not been granted the necessary permission. Of course, you can only assert permissions that your assembly itself has been granted. If this were not the case, it would be trivial to circumvent CLR security.

The SimplePermissionCodeDenial example now asserts the FileIO-Permission and then attempts to read the file by calling the ReadFile method, then the ReadFileWithAssert method, and finally the ReadFile method again. The ReadFileWithAssert method is much the same as the ReadFile method, but it has its own call to the Assert method.

 Try    fileIOPerm.Assert()    Console.WriteLine(_       "File Access Permission Asserted") Catch se As SecurityException    Console.WriteLine(se.Message) End Try ReadFile() ReadFileWithAssert(fileIOPerm) Console.WriteLine(_    "Returned from read routine with assert.") ReadFile() 

The file-read operations in both calls to the ReadFile method fail, but the file-read in ReadFileWithAssert succeeds. The permission assertion is only good within the method that calls Assert , and the assertion disappears after returning from that method. The ReadFileWithAssert method can read the file because it asserts the permission within the method and then attempts the read. Assert stops the permission stack walk from checking permissions higher in the stack frame and allows the action to proceed, but it does not cause a grant of the permission. Therefore, if code further down the stack frame (like ReadFile ) tries to demand the denied permission (as the FileInfo constructor does), a SecurityException will be thrown. Similarly, Deny prevents callers higher in the stack frame from an action, but not on the current level.

 Public Sub  ReadFileWithAssert  (ByVal f As FileIOPermission)    Try       f.  Assert()  Console.WriteLine(_          "File Permission Asserted in same procedure           as read.")  Dim file As FileInfo = New FileInfo(filename)   Dim sr As StreamReader = file.OpenText()  Dim text As String       text = sr.  ReadLine()  While text <> Nothing          Console.WriteLine("     " + text)          text = sr.  ReadLine()  End While       sr.Close()    Catch se As SecurityException       Console.WriteLine(_          "Could not read file: " + se.Message)    End Try End Sub 

Remember that the permission assertion only applies to I/O operations done in this one method for the specific file that was passed into the FileIO-Permission constructor (i.e., read.txt). The call to Assert is good until the containing method returns. Hence, ReadFile fails again when it is attempted after ReadFileWithAssert returns. RevertAssert removes all current Assert requests.

Assert opens up security holes, because some caller in the stack frame might be able to use the routine that calls Assert to violate security.

Other Permission Methods

PermitOnly specifies the permissions that should succeed. You simply specify what resources you want to access. The call to PermitOnly is good until the containing code returns or a subsequent call to PermitOnly . RevertPermitOnly removes all current PermitOnly requests. RevertAll removes the effect of Deny , PermitOnly , and Assert .

SecurityPermission Class

The SecurityPermission class controls "meta permissions" that govern the CLR security subsystem. Let us look again at the RoleBasedSecurity example from earlier in the chapter. It used the AppDomain.SetPrincipalPolicy method to set the application domain's principal policy.

 Dim ap As AppDomain = AppDomain.CurrentDomain ap.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal) 

The type of principal returned by Thread.CurrentPrincipal will depend on the application domain's principal policy. An application domain can have one of three authentication policies, as defined by the System.Security.PrincipalPolicy enumeration:

  • WindowsPrincipal uses the current user associated with the thread. Thread.CurrentPrincipal returns a WindowsPrincipal object.

  • UnauthenticatedPrincipal uses an unauthenticated user. Thread.-CurrentPrincipal returns a GenericPrincipal object. This is the default.

  • NoPrincipal returns Nothing for Thread.CurrentPrincipal .

You set the policy with the SetPrincipalPolicy method on the AppDomain instance for the current application domain. The static method AppDomain.CurrentDomain will return the current instance. This method should be called before any call to Thread.CurrentPrincipal because the principal object is not created until the first attempt to access that property.

In order for the RoleBasedSecurity example to set the principal policy, it needs to have the ControlPrincipal right. To ascertain if the executing code has that right, you can demand that SecurityPermission before you change the policy. A SecurityException will be thrown if you do not have that permission.

 ... Dim sp As SecurityPermission = _    New SecurityPermission(_       SecurityPermissionFlag.  ControlPrincipal  ) Try    sp.  Demand()  Catch se As SecurityException    Console.WriteLine(se.Message)    Return End Try ... 

We first construct a new SecurityPermission instance, passing to the constructor the security permission we want to see if we have the right to use. SecurityPermissionFlag is an enumeration of permissions used by the SecurityPermission class. The ControlPolicy permission represents the right to change policy. Obviously, this should only be granted to trusted code. We then demand (i.e., request) the permission.

As mentioned earlier, you can only assert permissions that your assembly actually has, so rogue components cannot just assert permissions when running within your code. You can either set security policy or use the Security- Permission class to prevent components from calling Assert . Construct an instance of the class with the SecurityPermissionFlag.Assertion value, and then Deny the permission. Among the other actions you can control with the SecurityPermission class are the ability to create and manipulate application domains, specify policy, allow or disallow execution, control whether verification is performed, and access unmanaged code.

Calling Unmanaged Code

Asserts are necessary for controlling access to unmanaged code, because in order to call unmanaged code, you need the unmanaged code permission, since the CLR performs a stack walk to check if all the callers have the unmanaged code permission. Therefore, if you did not use the Assert method, you would have to grant all code the unmanaged code permission. Hence, assemblies other than your own trusted assemblies could perform operations through the Win32 API calls and subvert the framework's security system. [9]

[9] The underlying operating system identity that is running the program must have the rights to perform the operating system function.

It is much better to make unmanaged code calls through wrapper classes that are contained in an assembly that has the unmanaged code permission. The code in the wrapper class would first ascertain that the caller has the proper CLR rights by demanding the minimal set of permissions necessary to accomplish the task (such as writing to a file). If the demand succeeds, then the wrapper code can assert the right to call unmanaged code. [10] No other assembly in the call chain then needs to have the unmanaged code permission.

[10] By demanding first and then asserting, you can ensure that a luring attack (i.e., unprivileged code calling privileged code to do evil things) is not in progress.

For example, if you ask the .NET file classes to delete a file, they first demand the delete permission on the file. If that permission is granted, then the code asserts the unmanaged code permission and calls the Win32 API to perform the delete.

Attribute-Based Permissions

graphics/codeexample.gif

The SimplePermissionAttributeRequest example shows how you can use attributes to make permission requests. This example uses an attribute to put in the metadata for the assembly that you need to have the ControlPrincipal permission to run. This enables you to query in advance which components conflict with security policy.

 graphics/codeexample.gif ... <Assembly: SecurityPermissionAttribute(_    SecurityAction.RequestMinimum, _  ControlPrincipal:=True  )> ... 

The SecurityAction enumeration has several values, some of which can be applied to a class or method and some that can be applied to an entire assembly, as in this example. For assemblies these are RequestMinimum , RequestOptional , and RequestRefuse . RequestMinimum indicates to the metadata those permissions the assembly requires to run. RequestOptional indicates to the metadata permissions that the assembly would like to have, but can run without. RequestRefuse indicates permissions that the assembly would like to be denied. [11]

[11] An assembly would do this to prevent code from another assembly executing on its behalf from having this permission.

If you change the attribute in this example to RequestRefuse and run it, you will find that the assembly will load, but you will get a SecurityException when you attempt to change the policy.

Other values apply to classes and methods. LinkDemand is acted upon when a link is made to some type. It requires your immediate caller to have a permission. The other values apply at runtime. InheritanceDemand requires a derived class to have a permission. Assert , Deny , PermitOnly , and Demand do what you would expect.

Here is an example of a FileIOPermission demand being applied to a class through an attribute. The named value All means all file access is being demanded for the specified file. A full file path is required.

 <FileIOPermissionAttribute(SecurityAction.Demand, _    All:="c:\foo\read.txt")> _ Public Class Simple     ... End Class 

Principal Permission

Role-based security is controlled by the PrincipalPermission class. The PrincipalPermission example uses this class to make sure that the user identity under which the program is being run is an administrator. We do that by passing the identity name and a string representing the role to the constructor. Once again, we use the Demand method on the permission to check the validity of our permission request.

 graphics/codeexample.gif  Dim PrincipalPerm As PrincipalPermission = _   New PrincipalPermission(_   wi.Name, adminRole)  Try    PrincipalPerm.  Demand  ()    Console.WriteLine(_       "Code demand for an administrator succeeded.") Catch e As SecurityException    Console.WriteLine(_       "Demand for Administrator failed.") End Try 

If the current user were an administrator, the demand would succeed; otherwise , it would fail with an exception being thrown. The code then checks for the user with the name JaneAdmin (not a system administrator, but part of the CustomerAdmin group ).

 Dim customerAdminRole As String = _   "HPDESKTOP\CustomerAdmin" Dim pp As PrincipalPermission pp = New PrincipalPermission(_    "HPDESKTOP\JaneAdmin", customerAdminRole) Try    pp.Demand()    Console.WriteLine(_       "Demand for Customer Administrator succeeded.") Catch e As SecurityException    Console.WriteLine(_       "Demand for Customer Administrator failed.") End Try 

Next, the PrincipalPermission example tests to see if either of these two administrators is the identity of the running code. The PrincipalPermission class has methods for creating permissions that are the union or the intersection of other permissions. The following shows how to form a union of the permissions of the users Administrator and PeterT.

 Dim  id1  As String =  "HPDESKTOP\Administrator"  Dim  id2  As String =  "HPDESKTOP\PeterT"  Dim  pp1  As PrincipalPermission = _    New  PrincipalPermission  (_  id1  , adminRole) Dim  pp2  As PrincipalPermission = _    New  PrincipalPermission  (_  id2  , adminRole) Dim  ipermission  As IPermission =  pp2.Union(pp1)  Try  ipermission.Demand()  Console.WriteLine(_       "Demand for either administrator succeeded.") Catch e As SecurityException    Console.WriteLine(_       "Demand for either administrator failed.") End Try 

The code then sees if any administrator is the current identity of the running code.

 Dim  pp3  As PrincipalPermission = _    New  PrincipalPermission  (_  Nothing  , adminRole) Try    pp3.  Demand()  Console.WriteLine(_       "Demand for any administrator succeeded.") Catch e As SecurityException    Console.WriteLine(_       "Demand for any administrator failed.") End Try 

If the users are unauthenticated, even if they do belong to the appropriate roles, the Demand will fail.

PermissionSet

You can deal with a set of permissions through the PermissionSet class. The AddPermission and RemovePermission methods allow you to add instances of a CodeAccessPermission derived class to the set. You can then Deny , PermitOnly , or Assert sets of permissions instead of individual permissions. This makes it easier to restrict what third-party components and scripts might be able to do. The PermissionSet example demonstrates how this is done.

We first define an interface IUserCode that our trusted code will use to access some third-party code. While in reality this third-party code would be in a separate assembly, to keep the example simple, we put everything in the same assembly.

 Public Interface IUserCode    Function  PotentialRogueCode  () As Integer End Interface Public Class ThirdParty    Implements IUserCode    Public Function  PotentialRogueCode  () As Integer _       Implements IUserCode.PotentialRogueCode       Try          Dim filename As String = "..\read.txt"          Dim file As FileInfo = New FileInfo(filename)          Dim sr As StreamReader = file.OpenText()          Dim text As String          text = sr.ReadLine()          While text <> Nothing             Console.WriteLine(text)             text = sr.ReadLine()          End While          sr.Close()       Catch e As Exception          Console.WriteLine(e.Message)       End Try       Return 0    End Function End Class 

Our code will create a new instance of the third party, which would cause the code to be loaded into our assembly. We then invoke the OurCode method passing it the third-party code.

 ... Public Module PSTest     Public Sub Main()         Dim thirdParty As ThirdParty = New ThirdParty()         Dim ourClass As OurClass = New OurClass()         ourClass.  OurCode  (thirdParty)     End Sub End Module ... 

Now let us look at the OurCode method. It creates a permission set consisting of unrestricted user interface and file access permissions. It then denies the permissions in the permission set. The third-party code is then called. After it returns, the permission denial is revoked and the third-party code is called again.

 Public Class OurClass    Public Sub  OurCode  (ByVal code As IUserCode)       Dim uiPerm As UIPermission = _          New UIPermission(PermissionState.Unrestricted)       Dim fileIOPerm As FileIOPermission = _          New FileIOPermission(PermissionState.Unrestricted)       Dim ps As PermissionSet = _          New  PermissionSet  (_             PermissionState.None)       ps.  AddPermission(uiPerm)  ps.  AddPermission(fileIOPerm)  ps.  Deny()  Console.WriteLine("Permissions denied.")       Dim v As Integer = code.  PotentialRogueCode()  CodeAccessPermission.  RevertDeny()  Console.WriteLine("Permissions allowed.")       v = code.  PotentialRogueCode()  End Sub End Class 

The first time the PotentialRogueCode method is called, it fails, and the second time, it succeeds. Each stack frame can have only one permission set for denial of permissions. If you call Deny on a permission set, it overrides any other calls to Deny on a permission set in that stack frame.


Team-Fly    
Top
 


Application Development Using Visual BasicR and .NET
Application Development Using Visual BasicR and .NET
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 190

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