ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Java vs. .NET Security, Part 3
Pages: 1, 2, 3, 4

Code Access Security: Access Checks

Code access checks are performed explicitly; the code (either an application, or a system library acting on its behalf) calls the appropriate Security Manager to verify that the application has the required permissions to perform an operation. This check results in an operation known as a stack walk: the Runtime verifies that each caller up the call tree has the required permissions to execute the requested operation. This operation is aimed to protect against a luring attack, where a privileged component is misled by a caller into executing dangerous operations on its behalf. When a stack walk is performed prior to executing an operation, the system can detect that the caller is not allowed to do what it is requesting, and abort the execution with an exception.



Privileged code may be used to deal with luring attacks without compromising overall system security, and yet provide useful functionality. Normally, the most restrictive set of permissions for all of the code on the current thread stack determines the effective permission set. To bypass this restriction, a special permission can be assigned to a small portion of code to perform a reduced set of restricted actions on behalf of under-trusted callers. All of the clients can now access the protected resource in a safe manner using that privileged component, without compromising security. For instance, an application may be using fonts, which requires opening font files in protected system areas. Only trusted code has to be given permissions for file I/O, but any caller, even without this permission, can safely access the component itself and use fonts.

Finally, one has to keep in mind that code access security mechanisms of both platforms sit on top of the corresponding OS access controls, which are usually role or identity-based. So, for example, even if Java/.NET's access control allows a particular component to read all of the files on the system drive, the requests might still be denied at the OS level.

A .NET assembly has a choice of using either imperative or declarative checks (demands) for individual permissions. Declarative (attribute) checks have the added benefit of being stored in metadata, and thus are available for analyzing and reporting by .NET tools like permview.exe. In either case, the check results in a stack walk. Declarative checks can be used from an assembly down to an individual properties level.

//this declaration demands FullTrust 
//from the caller of this assembly 

[assembly:PermissionSetAttribute(
  SecurityAction.RequestMinimum, 
  Name = "FullTrust")]


//An example of a declarative permission 
//demand on a method

[CustomPermissionAttribute(SecurityAction.Demand, 
  Unrestricted = true)] 
public static string ReadData() 
{ //Read from a custom resource. }


//Performing the same check imperatively

public static void ReadData() 
{ 
  CustomPermission MyPermission = new 
    CustomPermission(PermissionState.Unrestricted); 
  MyPermission.Demand(); 
  //Read from a custom resource. 
}

In addition to ordinary code access checks, an application can declaratively specify LinkDemand or InheritanceDemand actions, which allow a type to require that anybody trying to reference it or inherit from it possess particular permission(s). The former applies to the immediate requestor only, while the latter applies to all inheritance chain. Presence of those demands in the managed code triggers a check for the appropriate permission(s) at JIT time.

LinkDemand has a special application with strong-named assemblies in .NET, because such assemblies may have a higher level of trust from the user. To avoid their unintended malicious usage, .NET places an implicit LinkDemand for their callers to have been granted FullTrust; otherwise, a SecurityException is thrown during JIT compilation, when an under-privileged assembly tries to reference the strong-named assembly. The following implicit declarations are inserted by CLR:

[PermissionSet(SecurityAction.LinkDemand, 
   Name="FullTrust")]

[PermissionSet(SecurityAction.InheritanceDemand, 
   Name="FullTrust")]

Consequently, if a strong-named assembly is intended for use by partially trusted assemblies (i.e., from code without FullTrust), it has to be marked by a special attribute -- [assembly:AllowPartiallyTrustedCallers], which effectively removes implicit LinkDemand checks for FullTrust. All other assembly/class/method level security checks are still in place and enforceable, so it is possible that a caller may still not possess enough privileges to utilize a strong-named assembly decorated with this attribute.

.NET assemblies have an option to specify their security requirements at the assembly load time. Here, in addition to individual permissions, they can operate on one of the built-in non-modifiable PermissionSets. There are three options for those requirements: RequestMinimum, RequestOptional, and RequestRefuse.

If the Minimum requirement cannot be satisfied, the assembly will not load at all. Optional permissions may enable certain features. Application of the RequestOptional modifier limits the permission set granted to the assembly to only optional and minimal permissions (see the formula in the following paragraph). RequestRefuse explicitly deprives the assembly of certain permissions (in case they were granted) in order to minimize chances that an assembly can be tricked into doing something harmful.

//Requesting minimum assembly permissions
//The request is placed on the assembly level.

using System.Security.Permissions; 
[assembly:SecurityPermissionAttribute(
   SecurityAction.RequestMinimum, 
   Flags = SecurityPermissionFlag.UnmanagedCode)] 
namespace MyNamespace 
{ 
	...
}

CLR determines the final set of assembly permissions using the granted permissions, as specified in .NET CAS policy, plus the load-time modifiers described earlier. The formula applied is (P - Policy-derived permissions): G = M + (O<<P) - R, where M and O default to P, and R to Nothing.

Applications on .NET platform may affect the stack-walking process by executing special operations on individual permissions or permission sets: Assert, Deny, PermitOnly. The application itself should be granted the affected permissions, as well as the SecurityPermission that grants the rights to make assertions.

The Assert option explicitly succeeds the stack walk (for the given PermissionSet or any subset of it, as determined by the Intersect function), even if the upstream callers do not have the required permissions (it fails if sets intersections are not empty). Deny and PermitOnly effectively restrict the available permission sets for the downstream callers.

This demo application demonstrates the effects of applying stack-walk modifications. Figure 5 represents an overview of the Code Access Security permission grants and checks in .NET:

Figure 5
Figure 5. NET CAS Operation

In Java, permissions are normally checked by the SecurityManager (or installed derivative), by using the checkPermission function. It defines a helper for each major group of permissions, such as checkWrite for the write action of FilePermission. All checks are imperative; there are no declarative code access checks in Java language. Each JVM can have at most one SecurityManager (or derivative) installed -- once set, they cannot be replaced, for security reasons. Browsers always start SecurityManager, so any Internet Java application executes with enabled security. Locally started JVMs have to install a SecurityManager before exercising the first sensitive operation; this can also be done programmatically:

System.setSecurityManager(new SecurityManager());

or using a command-line option:

java -Djava.security.manager MyClass

In Java 2, when determining application permissions, SecurityManager delegates the call to java.security.AccessController, which obtains current snapshot of AccessControllerContext to determine which permissions are present. SecurityManager's operations may be influenced by the java.security.DomainController implementation, if one exists. It instructs an existing SecurityManager to perform additional operations before security checks, thus allowing security system extensibility without re-implementing its core classes. JAAS uses this functionality to add principal-based security checks to the original code-based Java security (see section "User Access Security" in Part 4).

When making access control decisions, the checkPermission method stops checking if it reaches a caller that was marked as "privileged" via a doPrivileged call without a context argument. If that caller's domain has the specified permission, no further checking is done and checkPermission returns quietly, indicating that the requested access is allowed. If that domain does not have the specified permission, an exception is thrown, as usual.

Writing privileged code in Java is achieved by implementing the java.security.PrivilegedAction or PrivilegedExceptionAction interfaces. This approach is somewhat limiting, as it does not allow specifying the exact permissions to be asserted, while still requiring the callers to possess others -- it is an "all or nothing" proposition.

public class PrivilegedClass implements PrivilegedAction {
    public Object run() {
        //perform privileged operation
        ...
        return null;
    }
}

Suppose the current thread traversed m callers, in the order of caller 1 to caller 2 to caller M, which invoked the checkPermission method. This method determines whether access is granted or denied based on the following algorithm:

i = m;
while (i > 0) {

  if (caller i's domain 
      does not have the permission)
          throw AccessControlException

  else if (caller i is marked as privileged) {
          if (a context was specified 
              in the call to doPrivileged) 
             context.checkPermission(permission)
          return;
  }
  i = i - 1;
}

// Next, check the context inherited when
// the thread was created. Whenever a new thread 
// is created, the AccessControlContext at that 
// time is stored and associated with the new 
// thread, as the "inherited" context.

inheritedContext.checkPermission(permission);

A complete application demonstrating privileged code in Java can be found here.

Note: .NET arms developers with an impressive arsenal of various features for access checks, easily surpassing Java in this respect.

Conclusions

In this article, code protection and Code Access Security features of Java and .NET platforms were reviewed. While code protection came out more or less even, CAS features in .NET are significantly better than the ones Java can offer, with a single exception -- flexibility. Java, as it is often the case, offers ease and configurability in policy handling that .NET cannot match.

The final article of this series, Part 4, is going to cover authentication and User Access Security (UAS) on both platforms, as well as provide overall conclusions to the series.

Demo Applications

Denis Piliptchouk is a senior technical member of BEA's AquaLogic Enterprise Security group, participates in OASIS WSS and WS-I BSP standards committees, and regularly contributes to industry publications.


Return to ONJava.com.