Home   Cover Cover Cover Cover
 

Security

Example "IsSubsetOf - implicit permissions"

When assign rights one must always pay attention not to allow too much. Many permissions include others, i.e. they are a superset of other permissions or - differently stated - they imply other permissions.
In order to determine, if a permission implies another the interface System.Security.IPermission provides a method IsSubsetOf.
In the example we create three permissions and test which permissions implies which. The output shows the actual implication relations.

PermissionSubset.cs
using System;
using System.Security;
using System.Security.Permissions;

public class A {
  public static void Main () {
    CodeAccessPermission
      p1 = new FileIOPermission(FileIOPermissionAccess.Read, "c:\\Temp"),
      p2 = new FileIOPermission(FileIOPermissionAccess.AllAccess, "c:\\Temp"),
      p3 = new FileIOPermission(FileIOPermissionAccess.Read, "c:\\");

    if (p1.IsSubsetOf(p2)) Console.WriteLine("p1 is subset of p2 OR p2 implies p1.");
    if (p1.IsSubsetOf(p3)) Console.WriteLine("p1 is subset of p3 OR p3 implies p1.");
    if (p2.IsSubsetOf(p1)) Console.WriteLine("p2 is subset of p1 OR p1 implies p2.");
    if (p2.IsSubsetOf(p3)) Console.WriteLine("p2 is subset of p3 OR p3 implies p2.");
    if (p3.IsSubsetOf(p1)) Console.WriteLine("p3 is subset of p1 OR p1 implies p3.");
    if (p3.IsSubsetOf(p2)) Console.WriteLine("p3 is subset of p2 OR p2 implies p3.");
  }
}

>csc PermissionSubset.cs /nologo

>PermissionSubset.exe
p1 is subset of p2 OR p2 implies p1.
p1 is subset of p3 OR p3 implies p1.
We see that p1 (= read access to c:\Temp) is a subset of p2 (= full access to c:\Temp) as well as p3 (= read access to c:\).
One should always keep this in mind when granting rights and try assigning only the smallest possible set of permissions.

Example "Demand - checking permissions"

Before a program accesses a resource it has to make sure that all callers actually possess the required permissions.
In the example we want to access a file for reading. With the method Demand (of interface System.Security.IPermission) we check if we have read access permissions for directory holding the file. If the test succeeds we read the text from the file and print it to the console. Depending on whether we could read everything, we return true or false.

SecureAccess0.cs
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

public class SecureAccess {
  const string path = "N:\\book\\samples\\3\\Security\\";
  
  public static bool ReadFile (string filename) {
  
    // check for necessary permissions
    CodeAccessPermission p = null;
    p = new FileIOPermission(FileIOPermissionAccess.Read, path);
    try {
      p.Demand();
      Console.WriteLine("Access granted.");
    } catch (SecurityException) {
      Console.WriteLine("Access denied.");
      return false;
    }
    
    // read the file
    StreamReader reader = null;
    try {
      reader = new StreamReader(File.OpenRead(path + filename));
      string line = reader.ReadLine();
      while (line != null) {
        Console.WriteLine("  '{0}'", line);
        line = reader.ReadLine();
      }
    } catch (Exception) {
      return false;
    } finally {
      if (reader != null) reader.Close();
    }
    
    return true;
  }
}

>csc /target:library SecureAccess0.cs /nologo
SecureAccess0 is only a library class that we use in SecureAccessTester0.

SecureAccessTester0.cs
using System;

class SecureAccessTester {
  public static void Main () {
    bool readOK = SecureAccess.ReadFile("F.txt");
    Console.WriteLine("Reading file F.txt: " + ((readOK) ? "Success" : "Failure"));
  }
}

>csc /reference:SecureAccess0.dll SecureAccessTester0.cs /nologo

>SecureAccessTester0.exe
Access granted.
  'This is a test file.'
  'It contains 3 lines of plain text.'
  'Nothing else.'
Reading file F.txt: Success
Apparently the application possessed all the required permissions (the default settings of the .NET Framework give full rights to all code from the local harddisk). Therefore reading the file was successful.

Example "Deny - refusing permissions"

There is also a way to intervene directly in the granting of rights - or rather in the Security Stack Walk (= checking of the permissions at runtime). For this the .NET platform offers three special sets of permissions:
  • Assert: if the demanded permission is a subset of this set, the stack walk will be terminated immediately and the permission will be granted.
  • Deny: if the demanded permission is a subset of this set, the stack walk will be terminated immediately and the permission will be denied.
  • PermitOnly: if the demanded permission is not a subset of this set, the stack walk will be terminated immediately and the permission will be denied.
In all other cases the security stalk walk will be executed as usual.
In the example we change the SecureAccessTester from the previous example so that it now denies all access the the local hard disk N:.

SecureAccessTester1.cs
using System;
using System.Security;
using System.Security.Permissions;

class SecureAccessTester {
  public static void Main () {
    CodeAccessPermission p1;
    p1 = new FileIOPermission(FileIOPermissionAccess.AllAccess, "N:\\");
    p1.Deny();
    
    bool readOK = SecureAccess.ReadFile("F.txt");
    Console.WriteLine("Reading file F.txt: " + ((readOK) ? "Success" : "Failure"));
  }
}

>csc /reference:SecureAccess0.dll SecureAccessTester1.cs /nologo

>SecureAccessTester1.exe
Access denied.
Reading file F.txt: Failure
If we now run this application, we will not be able to read the file N:\book\samples\3\Security\F.txt anymore, because we are not granted the necessary rights. Still we can see that the handler for the SecurityException from SecureAccess.ReadFile has been executed (output: Access denied.p.Demand() has failed and thrown a SecurityException.
Let us point out that such modifications of the "normal" security stack walk may, of course, only be done by assemblies with corresponding rights.

Example "declarative security"

Besides the imperative variant of expressing aspects of security that we have used so far (see Example "Demand" or Example "imperative security"), there is also the possibility of using attributes. This is then called declarative security.
In the example we modify SecureAccess so that it now declaratively demands read access rights for the directory N:\book\samples for the entire method ReadFile (attribute FileIOPermissionAttribute).

SecureAccess1.cs
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

public class SecureAccess {
  const string path = "N:\\book\\samples\\3\\Security\\";

  [FileIOPermission(SecurityAction.Demand, Read = path)]
  public static bool ReadFile (string filename) {
    
    // read the file
    StreamReader reader = null;
    try {
      reader = new StreamReader(File.OpenRead(path + filename));
      string line = reader.ReadLine();
      while (line != null) {
        Console.WriteLine("  '{0}'", line);
        line = reader.ReadLine();
      }
    } catch (Exception) {
      return false;
    } finally {
      if (reader != null) reader.Close();
    }
    
    return true;
  }
}

>csc /target:library SecureAccess1.cs /nologo
We test this variant with both SecureAccessTester variant ...
>csc /reference:SecureAccess1.dll SecureAccessTester0.cs /nologo

>SecureAccessTester0.exe
  'This is a test file.'
  'It contains 3 lines of plain text.'
  'Nothing else.'
Reading file F.txt: Success

>csc /reference:SecureAccess1.dll SecureAccessTester1.cs /nologo

>SecureAccessTester1.exe

Unhandled Exception: System.Security.SecurityException: Request failed.
   at System.Security.SecurityRuntime.FrameDescSetHelper(FrameSecurityDescriptor secDesc, PermissionSet demandSet, PermissionSet& alteredDemandSet)
   at SecureAccess.ReadFile(String filename)
   at SecureAccessTester.Main()
and see that this work almost like the imperative variant of the two previous examples. The only difference is that we cannot catch and handle the SecurityException in the SecureAccess.ReadFile method anymore. We would have to do this in the Main method of SecureAccessTester.

Example "imperative security"

An advantage of imperative security lies in the fact that security demands have to be specified in detail only at runtime.
In the example we modify the SecureAccess.ReadFile method from Example "Demand" only marginally (line 13). Thus we achieve that only the absolutely necessary set of permissions is requested, i.e. read access for only the very file that is specified at runtime instead of requesting read access rights for the entire directory.

SecureAccess2.cs
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

public class SecureAccess {
  const string path = "N:\\book\\samples\\3\\Security\\";

  public static bool ReadFile (string filename) {
  
    // check for necessary permissions
    CodeAccessPermission p = null;
    p = new FileIOPermission(FileIOPermissionAccess.Read, path + filename);
    try {
      p.Demand();
      Console.WriteLine("Access granted.");
    } catch (SecurityException) {
      Console.WriteLine("Access denied.");
      return false;
    }
    
    // read the file
    StreamReader reader = null;
    try {
      reader = new StreamReader(File.OpenRead(path + filename));
      string line = reader.ReadLine();
      while (line != null) {
        Console.WriteLine("  '{0}'", line);
        line = reader.ReadLine();
      }
    } catch (Exception) {
      return false;
    } finally {
      if (reader != null) reader.Close();
    }
    
    return true;
  }
}

We also test this variant with both SecureAccessTester variant ...
>csc /target:library SecureAccess2.cs /nologo

>csc /reference:SecureAccess2.dll SecureAccessTester0.cs /nologo

>SecureAccessTester0.exe
Access granted.
  'This is a test file.'
  'It contains 3 lines of plain text.'
  'Nothing else.'
Reading file F.txt: Success

>csc /reference:SecureAccess2.dll SecureAccessTester1.cs /nologo

>SecureAccessTester1.exe
Access denied.
Reading file F.txt: Failure
... and find that this works just the same as before. This dynamic adaptation of the demanded permissions is not possible with declarative security.