Testing Any Applied Custom Permissions

for RuBoard

There are many permissions defined in the System.Security.Permissions namespace of the .NET Framework class library, although it is expected that they will not meet all the needs of future secure class libraries. Thus, the security system of the .NET Platform was designed to be extensible so that permissions could be added by .NET developers.

If a secured assembly is going to use a custom permission, it is crucial to test the permission class in addition to the secured methods and properties in the secured assembly. After the permission class is added to the security policy, it becomes a trusted piece of the system. If it is designed poorly, this permission could open serious security holes.

The following are major considerations when testing a custom permission:

  • Testing the key methods of a custom permission that interface with the security system

  • Testing imperative use of a custom permission

  • Testing declarative use of a custom permission

  • Other miscellaneous issues with custom permissions

Testing the Key Methods of a Custom Permission That Interface with the Security System

When a custom permission is written, is it generally written as a class that inherits from System.Security.CodeAccessPermission . There are a few key methods that the custom permission class overrides from the CodeAccessPermission base class. These methods are called by the security system of the CLR to enforce the permission. These methods are as follows :

  • Union Creates a new permission that covers all of the two original permissions. (In Figure 26.2, the union of Permission 1 and Permission 2 is the sum of all areas A, B, C, and D.)

    Figure 26.2. A Venn diagram that represents three permission objects with overlapping states.

    graphics/26fig02.gif

  • Intersect Creates a new permission that covers the common subset of the two original permissions. (In Figure 26.2, the intersection of Permission 1 and Permission 2 is area C.)

  • IsSubsetOf Returns true or false to show if one permission is a subset of another. (In Figure 26.2, Permission 3 is a subset of Permission 2.)

  • Copy Creates a new permission that is identical to the original permission.

  • ToString Prints out a textual representation of a permission.

  • ToXml Creates an XML representation of a permission.

  • FromXml Changes an XML representation of a permission to a permission object itself.

When testing these methods, it is important to use a wide representation of possible permission objects. This means using null (when possible), an empty permission, an unrestricted permission, and a variety of states in between. If all states of a permission can be easily enumerated (for example, the permission state is really just represented by a few Boolean flags, such as SecurityPermission ), go ahead and use all possible states. If the permission has an infinite number of possible states (for example, FileIOPermission ), at least try to get a good representation of different permission states for testing.

Union

There is one primary principle to test with Union . If C is the union of A and B , A.IsSubsetOf(C) and B.IsSubsetOf(C) should both be true . In addition, there are a few secondary principles that ensure precise behavior for boundary conditions.

  • The union of A and B should never return references to the A or B objects themselves . It should always return null or create a new custom permission object to return.

  • The union of A and null should be a copy of A .

  • The union of A with an empty custom permission (for example, one created with PermissionState.None ) should be a copy of A .

  • The union of A with an unrestricted custom permission (for example, one created with PermissionState.Unrestricted ) should be an unrestricted custom permission.

  • If A and B are different types (that is, they are instances of different classes), A.Union(B) should throw an exception unless an explicit design decision was made to allow this for the custom permission.

Intersect

There is one primary principle to test with Intersect . If C is the non- null intersection of A and B , C.IsSubsetOf(A) and C.IsSubsetOf(B) should both be true . Just as with Union , there are a few secondary principles that ensure precise behavior for boundary conditions.

  • The intersection of A and B should never return references to the A or B objects themselves. It should always return null or create a new custom permission object to return.

  • The intersection of A and null should be null .

  • The intersection of A and an empty custom permission (for example, one created with PermissionState.None ) should be an empty custom permission.

  • The intersection of A and an unrestricted custom permission should be a copy of A .

  • If A and B are different types, A.Intersect(B) should throw an exception unless an explicit design decision was made to allow this for the custom permission.

IsSubsetOf

IsSubsetOf is generally tested by testing the other key methods of a custom permission. However, there are some interesting cases to make sure to test directly. The examples use some given custom permission object A and an unrestricted custom permission object B .

  • A.IsSubsetOf(B) should return true .

  • B.IsSubsetOf(A) should return false .

  • A.IsSubsetOf(null) should return true if A is an empty permission (for example, one created with PermissionState.None ).

  • If A and C are different types, A.IsSubsetOf(C) should throw an exception unless an explicit design decision was made to allow this for the custom permission.

Copy

Essentially, testing Copy just requires creating a range of different kinds of permissions, making copies of them via this method, and ensuring the copy is identical to the original. Note that you cannot ensure permission objects are identical by calling the Equals method, because permission objects do not override that method. Instead, you can determine equality of two permissions A and B by calling A.IsSubsetOf(B) and B.IsSubsetOf(A) . If A and B are identical permissions, both calls should return true .

CAUTION

Calling A.Equals(B) doesn't tell you if the internal permission states of A and B are identical. A.Equals(B) tells you if A and B are references to the same object in memory. To see if internal states of A and B are identical, you should call A.IsSubsetOf(B) and B.IsSubsetOf(A) . If both calls are true , A and B are identical permissions.


ToString

This method is less critical than the rest of the methods listed here because no runtime decisions are based on the output. However, ToString is often used in debugging, so it is important to get it right.

The output of this method is highly dependent on the custom permission itself, so there is little guidance to give for testing. Returning anything non- null should be safe, but you should try viewing the output for several different permission objects and ensure that they make sense.

ToXml and FromXml

Guidance for testing ToXml and FromXml is put together because testing them is best done together. There is little need to examine the SecurityElement s returned by ToXml or constructing strange SecurityElement s to pass to FromXml because the custom permission should only have to consume SecurityElement s in FromXml that it produced via ToXml .

Generally, the best way to test these methods is the following:

  1. Create an instance of the custom permission.

  2. Call ToXml on that custom permission object.

  3. Call FromXml on the SecurityElement returned by ToXml .

  4. Compare the original custom permission object to the one resulting from the call to FromXml .

The original custom permission object and the resulting custom permission object should be identical. As in the case of testing Copy , testing equality should be done using IsSubsetOf . So, if B is the result of calling ToXml followed by a call to FromXml , A.IsSubsetOf(B) and B.IsSubsetOf(A) should both return true . The real key to testing ToXml and FromXml is to use a wide variety of different custom permission states.

Testing Imperative Use of a Custom Permission

When you know the key methods of a custom permission work correctly, you have gone a long way to making sure the permission can be used properly. However, a permission used declaratively executes a somewhat different code path compared to when it is used imperatively. Thus, it is important to ensure to test the permission in both cases.

Testing imperative use of a custom permission consists of creating objects of your custom permission class and calling the following methods on those objects:

  • Assert

  • Deny

  • PermitOnly

  • Demand

You should be able to reuse the same permission states for these methods that were used for the previously tested methods. Using a wide variety of permission states will ensure robustness of the custom permission.

Assert

To test that Assert works properly for the custom permission, a good test framework to make is shown in Listing 26.1. Following this paragraph are the main scenarios to try using that framework. The individual scenarios refer to two custom permission states A and B where A.IsSubsetOf(B) is true . Also, a third custom permission, state C , is used where A.Intersect(C) returns an empty custom permission.

  • Assert A and Demand A should not throw a SecurityException .

  • Assert A and Demand B should throw a SecurityException .

  • Assert A and Demand C should throw a SecurityException .

  • Assert B and Demand A should not throw a SecurityException .

Listing 26.1 Test Framework for Imperatively Testing the Assert Method of a Custom Permission
 class AssertTest {   public void TestDriver() {     // Make calls to RunTest per recommendations   }   private bool RunTest(CustomPermission permToAssert,       CustomPermission permToDemand,       Bool fShouldThrowSecurityException) {     new CustomPermission(PermissionState.Unrestricted).Deny();     try {       RunTestSubA(permToAssert, permToDemand);     }     catch (SecurityException) {       if (fShouldThrowSecurityException)         return true;       else         return false;     }     if (fShouldThrowSecurityException)       return false;     else       return true;   }   private void RunTestSubA(CustomPermission permToAssert,       CustomPermission permToDemand) {     permToAssert.Assert();     RunTestSubB(permToDemand);   }   private void RunTestSubB(CustomPermission permToDemand) {     permToDemand.Demand();   } } 

CAUTION

If Assert and Demand are called in the same method, the Assert will not affect the resulting stack walk. After calling Assert , the test must call another method that calls Demand . This is because when a call is made to Demand , the stack walk begins with the frame above the frame containing the Demand. See Chapter 7, "Walking the Stack," for more details on stack walking.

This same principle also applies for Deny and PermitOnly . Calls to Deny and PermitOnly will not affect calls to Demand s in the same method.


Deny

For Deny, see Listing 26.2 for a testing framework. Following this paragraph are the main scenarios to try in that framework. The individual scenarios refer to two custom permission states A and B where A.IsSubsetOf(B) is true . Also, a third custom permission state, C , is used where A.Intersect(C) returns an empty custom permission.

  • Deny A and Demand A should throw a SecurityException .

  • Deny A and Demand B should throw a SecurityException .

  • Deny A and Demand C should not throw a SecurityException .

  • Deny B and Demand A should throw a SecurityException .

Listing 26.2 Test Framework for Imperatively Testing the Demand Method of a Custom Permission
 class DemandTest {   public void TestDriver() {     // Make calls to RunTest per recommendations   }   private bool RunTest(CustomPermission permToDeny,       CustomPermission permToDemand,       Bool fShouldThrowSecurityException) {     permToDeny.Deny();     try {       RunTestSubA(permToDemand);     }     catch (SecurityException) {       if (fShouldThrowSecurityException)         return true;       else         return false;     }     if (fShouldThrowSecurityException)       return false;     else       return true;   }   private void RunTestSubA(CustomPermission permToDemand) {     permToDemand.Demand();   } } 
PermitOnly

For PermitOnly , see Listing 26.3 for a testing framework. Following this paragraph are the main scenarios to try in that framework. The individual scenarios refer to two custom permission states A and B where A.IsSubsetOf(B) is true . Also, a third custom permission state, C , is used where A.Intersect(C) returns an empty custom permission.

  • PermitOnly A and Demand A should not throw a SecurityException .

  • PermitOnly A and Demand B should throw a SecurityException .

  • PermitOnly A and Demand C should throw a SecurityException .

  • PermitOnly B and Demand A should not throw a SecurityException .

Listing 26.3 Test Framework for Imperatively Testing the PermitOnly Method of a Custom Permission
 class PermitOnlyTest {   public void TestDriver() {     // Make calls to RunTest per recommendations   }   private bool RunTest(CustomPermission permToPermitOnly,       CustomPermission permToDemand,       Bool fShouldThrowSecurityException) {     permToPermitOnly.PermitOnly();     try {       RunTestSubA(permToDemand);     }     catch (SecurityException) {       if (fShouldThrowSecurityException)         return true;       else         return false;     }     if (fShouldThrowSecurityException)       return false;     else       return true;   }   private void RunTestSubA(CustomPermission permToDemand) {     permToDemand.Demand();   } } 
Demand

Testing Demand was included while testing Assert , Deny , and PermitOnly , so no additional work should be necessary.

Testing Declarative Use of a Custom Permission

Declarative testing is similar to imperative testing. However, in this case, there is an expanded set of uses to try with the permission:

  • Assert

  • Deny

  • PermitOnly

  • Demand

  • LinkDemand

  • InheritanceDemand

Assert, Deny, PermitOnly, and Demand

Assert , Deny , PermitOnly , and Demand are generally the same imperatively as they are declaratively. Thus, the previous scenarios for Assert , Deny , PermitOnly , and Demand can be used for testing the custom permission declaratively. Also, the test frameworks can be easily modified to change the imperative calls to declarative calls. Listing 26.4 shows how this would be done for Assert . Note that the exact declarative representation of a permission depends on its attribute class, so this example will need to be modified to match your custom permission attribute class. Also, instead of constructing permissions in the TestDriver method and calling RunTest many times, several RunTest methods will be necessary because each one will need a different declarative security attribute.

The reason to test Assert , Deny , PermitOnly , and Demand declaratively in addition to imperatively relates to how the permissions are constructed . For imperative security, the test code creates the custom permission object itself. For declarative security, the custom permission object is created using the custom permission's attribute class. Thus, a bug in the declarative attribute class will only show up when using the custom permission declaratively.

Listing 26.4 Test Framework for Declaratively Testing the Assert Method of a Custom Permission
 class AssertTest {   public void TestDriver() {     // Make calls to RunTestX per recommendations   }   [CustomPermission(SecurityAction.Deny, Unrestricted=true)]   private bool RunTest1(CustomPermission permToAssert,       CustomPermission permToDemand,       Bool fShouldThrowSecurityException) {     try {       RunTestSub1A(permToAssert, permToDemand);     }     catch (SecurityException) {       if (fShouldThrowSecurityException)         return true;       else         return false;     }     if (fShouldThrowSecurityException)       return false;     else       return true;   }   [CustomPermission(SecurityAction.Assert, Flags=SomeFlagsToTest)]   private void RunTestSub1A(CustomPermission permToAssert,       CustomPermission permToDemand) {     RunTestSub1B(permToDemand);   }   [CustomPermission(SecurityAction.Demand, Flags=SomeOtherFlagsToTest)]   private void RunTestSub1B(CustomPermission permToDemand) {     return;   } } 

NOTE

The two exclusive uses of permissions in declarative security, LinkDemand and InheritanceDemand , are described in more detail in Chapter 6, "Permissions: The Workhorse of Code Access Security."


LinkDemand

What follows are the key scenarios to check for a LinkDemand . All the scenarios will refer to method a in class A located in assembly 1, methods b1 and b2 in class B located in assembly 2, and an instance P of your custom permission.

TIP

If you are using default security policy, your secured assembly X.dll should be granted unrestricted use of your custom permission if X.dll is located on your local machine.

If you want to ensure some assembly is not granted some custom permission state P, then use an assembly permission request like the following:

 [assembly:CustomPermission(SecurityAction.RequestRefuse, State=P)] 

You will need to modify the State=P portion of this assembly permission request to fit your specific custom permission. See Chapter 6 for more details of assembly permission requests .


  • A.a() has a LinkDemand for P . Assembly 1 is granted P when it is loaded, and assembly 2 is not granted P when it is loaded. B.b1() calls B.b2() , which calls A.a() . A SecurityException should be thrown inside B.b1() at the point when it calls B.b2() .

  • A has a LinkDemand for P . Assembly 1 is granted P when it is loaded, and assembly 2 is not granted P when it is loaded. B.b1() calls B.b2() , which creates an instance of A and calls A.a() . A SecurityException should be thrown inside B.b1() at the point when it calls B.b2() .

  • A.a() has a LinkDemand for P . Assembly 1 is granted P when it is loaded, and assembly 2 is granted P when it is loaded. B.b1() calls B.b2() , which calls A.a() . No SecurityException should be thrown in any of these calls due to the LinkDemand .

InheritanceDemand

What follows are the key scenarios to check for an InheritanceDemand . All the scenarios will refer to method a in class A located in assembly 1, method b in class B located in assembly 2, methods c1 and c2 in class C in assembly 3, and an instance P of your custom permission. Class B inherits from class A , and method b is an overridden version of method A.a() .

  • A.a() has an InheritanceDemand for P . Assemblies 1 and 3 are granted P when they are loaded, and assembly 2 is not granted P when it is loaded. C.c1() calls C.c2() , and C.c2() creates an instance of B . A SecurityException should be thrown when C.c1() calls C.c2() .

  • A has an InheritanceDemand for P . Assemblies 1 and 3 are granted P when they are loaded, and assembly 2 is not granted P when it is loaded. C.c1() calls C.c2() , and C.c2() creates an instance of B . A SecurityException should be thrown when C.c1() calls C.c2() .

  • A has an InheritanceDemand for P . Assemblies 1, 2, and 3 are granted P when they are loaded. C.c1() calls C.c2() , and C.c2() creates an instance of B and calls B.b() . No SecurityException should be thrown in any of these calls due to the InheritanceDemand .

Other Miscellaneous Issues with Custom Permissions

There are two issues that might be worthwhile investigating while testing a custom permission. The first issue is use of globalized data in the permission. By globalized data, I mean data that is different in different cultures, such as dates and character sets. The second issue is remoting across AppDomain s.

Globalized Data

If the custom permission you need to test consists only of a group of Boolean flags, skip this section. However, if the permission is more complex and uses strings or dates, it is worthwhile to take note here.

When testing a custom permission, it is important to really examine the whole realm of possible inputs to the permission. If the permission stores state in the form of a string (for example, the FileIOPermission ) and that string ever left managed code, there is a chance that there could be a string parsing problem in the unmanaged code. Strings and dates (among other types) are also related to cultures. It is worthwhile to try giving data formatted for other cultures to the permission. A data parsing problem in a permission could easily open up a security hole in the system.

Remoting Across AppDomains

Stack walks will not propagate in remoting across machine boundaries. However, they will propagate within a process across AppDomain s. Because of the complexity of remoting and serialization of permissions, it would be useful to at least try a scenario involving a stack walk across AppDomain s for your custom permission. If your permission is incorrectly serialized or deserialized across the AppDomain boundary, it could turn out to be a problem if complex applications tried to use your custom permission or any secured assemblies using your custom permission. Listing 26.5 shows an example of stack walking across app domains. Note that this requires multiple assemblies, so the example is broken into two source files.

Listing 26.5 Example of Cross-AppDomain Stack Walking
 // This section should be compiled into its own assembly // If the source code is saved to "testhelper.cs", compile it via // "csc /target:library testhelper.cs" using System; using System.Security; public class CrossDomainTest : MarshalByRefObject {   public void DemandPermission(PermissionSet p1) {     p1.Demand();   } } // This section should be compiled into a different assembly // If the source code is saved to "testdriver.cs", compile it via // "csc /r:testhelper.dll testdriver.cs using System; using System.Security; using System.Security.Permissions; using System.Security.Policy; public class CrossDomainTestDriver {   private AppDomain MyTestDomain;   public static void Main(String[] args) {     try {       CrossDomainTestDriver ADtest = new CrossDomainTestDriver();       ADtest.RunTest();     }     catch (Exception e) {       Console.WriteLine("Unexpected error occurred");       Console.WriteLine(e.ToString());     }   }   private void RunTest() {     MyTestDomain = AppDomain.CreateDomain("test", null,new AppDomainSetup());     CrossDomainTest TestObj = (CrossDomainTest)MyTestDomain. CreateInstance("TestHelper", graphics/ccc.gif "CrossDomainTest").Unwrap();     CustomPermission cp = new CustomPermission(PermissionState.Unrestricted);     PermissionSet p1 = new PermissionSet(PermissionState.None);     p1.AddPermission(cp);     try {       p1.Deny();       TestObj.DemandPermission(p1);       Console.WriteLine("Fail: A security exception should have been thrown");     }     catch(SecurityException se) {       Console.WriteLine("Pass: A security exception was received appropriately");       Console.WriteLine(se.ToString());     }   } } 

NOTE

See the .NET Framework SDK documentation for more information on remoting and serialization.


for RuBoard


. NET Framework Security
.NET Framework Security
ISBN: 067232184X
EAN: 2147483647
Year: 2000
Pages: 235

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