< Day Day Up > |
The Java 2 access-control model is very strict. By granting a thread of execution only to the intersection of the sets of Permission s implied by the ProtectionDomain s the thread traverses, this architecture guarantees that less trusted ProtectionDomain s do not get extra Permission s that had not been granted to them. Therefore, it is not possible for an application domain to steal Permission s from the system domain by making calls into it (see Figure 8.11 on page 269) or by being called by it (see Figure 8.12 on page 270). However, there would be an intrinsic limitation in the model that we have just described if library code did not have the possibility of exempting its callers from requiring certain Permission s. Consider again the scenario depicted in Section 8.1.1 on page 254. There may be cases in which a trusted class, typically in a library, implements a security-sensitive operation but does not care about whether its callers have the Permission s to perform that operation. This problem could be solved by granting application code the additional Permission s needed by the library. This solution, however, is not recommended, because it would violate the principle of least privilege , [2] which dictates that an entity be given no more privilege than necessary to perform an operation. Violating this principle would be unwise because (1) malicious application code could misuse the additional Permission and (2) the portability and flexibility of the library code would be limited if its callers were forced to be granted a Permission that they did not directly require.
To address the issue of trusted library code needing to temporarily extend some of its Permission s to its callers, the Java 2 language introduces the concept of privileged code. When a portion of code is wrapped into a call to AccessController.doPrivileged() , an annotation is made on the thread's stack frame, indicating that when the AccessController.checkPermission() method searches for ProtectionDomain s to see whether they imply the Permission being checked, the search stops at this stack frame (see Figure 8.14). As Figure 8.14 shows, the code that calls doPrivileged() and the code called from the privileged block downward have to have the Permission in order for the security-sensitive operation to succeed. However, the callers are exempted from being granted that particular Permission . Figure 8.14. Effects of Calling doPrivileged() on the Thread's Stack
For example, the library code in Figure 8.2 on page 255 will need a SocketPermission and a FilePermission . However, if the log operation code is wrapped into a call to doPrivileged() , the calling application code will still need the SocketPermission but will be exempted from being granted the FilePermission to write to the log file. To summarize, the general rule is that when a Permission checking is performed, all the ProtectionDomain s traversed by the thread of execution must have been granted the Permission being checked (see point 1 in Figure 8.15). However, if the code that effectively performs the restricted operation wraps the security-sensitive code into a call to doPrivileged() , the callers of that code are exempted from the Permission requirement (see point 2 in Figure 8.15). Figure 8.15. Nonprivileged versus Privileged Code Scenario
8.7.1 Security Recommendations on Making Code PrivilegedInappropriate use of the privileged-code construct can create serious security holes.
8.7.2 How to Write Privileged CodePrivileged code needs to wrapped into a call to AccessController.doPrivileged() . This method can take as an argument
Besides these two versions of doPrivileged() , two additional versions of this method take an AccessControlContext object as an additional parameter. An AccessControlContext encapsulates an array of ProtectionDomain s. Passing an AccessControlContext to doPrivileged() besides the PrivilegedAction or PrivilegedExceptionAction object further restricts the privileges granted to a thread of execution. In fact, when an AccessControlContext is specified, the restricted action is performed with the intersection of the set of the Permission s implied by the caller's ProtectionDomain and the set of the Permission s implied by the ProtectionDomain s encapsulated in the AccessControlContext . PrivilegedAction and PrivilegedExceptionAction are two interfaces. They have only one method, run() , which when implemented will contain the security-sensitive code. This method returns a java.lang.Object . AccessController.doPrivileged() invokes the run() method on the PrivilegedAction or PrivilegedExceptionAction that was passed to it as a parameter and returns the run() method's return value, which could be null if there is nothing to return. If the return value is not null , an explicit casting may be necessary. Listing 8.4 is an example of PrivilegedAction use when the run() method's return value is null . Listing 8.4. Use of PrivilegedAction within a Privileged Block with no Return ValuesomeMethod() { // unprivileged code here... AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // privileged code goes here, for example: System.loadLibrary("awt"); return null; // nothing to return } }); // unprivileged code here... } If a return value is required, the code should be written as in Listing 8.5. Listing 8.5. Use of PrivilegedAction within a Privileged Block with a Return ValuesomeMethod() { // unprivileged code here... String user = (String) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // privileged code goes here, for example: return System.getProperty("user.name"); } }); // unprivileged code here... } When using the PrivilegedExceptionAction interface, a java.security.PrivilegedActionException must be caught in a try{} catch(){} block, as in Listing 8.6. Listing 8.6. Use of PrivilegedExceptionAction and PrivilegedActionExceptionsomeMethod() throws FileNotFoundException { // unprivileged code here... try { FileInputStream fis = (FileInputStream) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws FileNotFoundException { // privileged code goes here, for example: return new FileInputStream("someFile"); } }); } catch(PrivilegedActionException e) { throw (FileNotFoundException) e.getException(); } // unprivileged code here... } Note that the getException() method for PrivilegedActionException returns an Exception object. Therefore, you must catch this Exception to the type of the specific Exception to be thrown, as only a checked exception will be wrapped in a PrivilegedActionException . In fact, PrivilegedActionException is a wrapper for an Exception thrown by a PrivilegedExceptionAction . In Listing 8.6, the Exception that needs to be thrown is a FileNotFoundException . 8.7.3 Privileged-Code ScenarioLet us consider the scenario shown in Figure 8.16. Here, we have two applications, CountFileCaller1 and CountFileCaller2, attempting to get indirect read access to a local file through the use of library code. The purpose of these applications is to count the number of characters in that file. CountFileCaller1 invokes the CountFile1 API to access the file, whereas CountFileCaller2 uses the CountFile2 API. Both CountFile1 and CountFile2 are granted the FilePermission necessary to read the file. However, CountFile1 does that through a call to doPrivileged() ; CountFile2 does not. Therefore, CountFileCaller1 is exempted from being granted the FilePermission to read the file; CountFileCaller2 is not. Figure 8.16. Graphical Representation of the Privileged-Code Scenario
Listing 8.7 shows the CountFile1 API code. Note that CountFile1 uses the privileged-code mechanism to exempt its callers from the FilePermission requirement. Listing 8.7. CountFile1.javaimport java.io.FileInputStream; import java.io.FileNotFoundException; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; import java.security.AccessController; class MyPrivilegedExceptionAction implements PrivilegedExceptionAction { public Object run() throws FileNotFoundException { FileInputStream fis = new FileInputStream("C:\AUTOEXEC.BAT"); try { int count = 0; while (fis.read() != -1) count++; System.out.println("Counted " + count + " chars."); } catch (Exception e) { System.out.println("Exception " + e); } return null; } } public class CountFile1 { public CountFile1() throws FileNotFoundException { try { AccessController.doPrivileged(new MyPrivilegedExceptionAction()); } catch (PrivilegedActionException e) { throw (FileNotFoundException) e.getException(); } } } CountFile1 is invoked by CountFileCaller1, whose code is given in Listing 8.8. Listing 8.8. CountFileCaller1.javapublic class CountFileCaller1 { public static void main(String[] args) { try { System.out.println("Instantiating CountFile..."); CountFile1 cf = new CountFile1(); } catch(Exception e) { System.out.println("Exception " + e.toString()); e.printStackTrace(); } } } The CountFileCaller2 API gets access to the local file system directly, without using privileged code. The source code is shown in Listing 8.9. Listing 8.9. CountFile2.javaimport java.io.FileInputStream; public class CountFile2 { int count = 0; public void countChars() throws Exception { FileInputStream fis = new FileInputStream("C:\AUTOEXEC.BAT"); try { while (fis.read() != -1) count++; System.out.println("Counted " + count + " chars."); } catch (Exception e) { System.out.println("No characters counted"); System.out.println("Exception " + e); } } } CountFile2 is invoked by the CountFileCaller2 application, whose code is shown in Listing 8.10. Listing 8.10. CountFileCaller2.javapublic class CountFileCaller2 { public static void main(String[] args) { try { System.out.println("Instantiating CountFile2..."); CountFile2 cf = new CountFile2(); cf.countChars(); } catch(Exception e) { System.out.println("" + e.toString()); e.printStackTrace(); } } } To apply the Java 2 access-control mechanism, the CountFileCaller1 application must be invoked with an active SecurityManager . Section 8.6.1 on page 271 shows how to invoke an application with the default SecurityManager and, in addition, how to activate the Java debugger, which gives details on how AccessController works behind the scenes. Here are the details of the access-control flow as they are revealed by the Java debugger.
To examine in detail the steps performed by AccessController when it walks back through the stack frames of the current thread, let us consider Figure 8.17, which shows the thread's stack during the execution of the CountFileCaller1 application. Figure 8.17. Thread's Stack for the CountFileCaller1 Application
When it examines the thread's stack, AccessController.checkPermission() starts with the last method call in the calling sequence and proceeds back ward up to the top of the stack. For each frame on the stack, AccessController.checkPermission() verifies that the ProtectionDomain of the class containing the method implies the FilePermission to read the local file C:\AUTOEXEC.BAT . However, not all the ProtectionDomain s are checked. As soon as a call to doPrivileged() is encountered , AccessController.checkPermission() stops the ProtectionDomain examination with the caller to doPrivileged() . The details of the process are as follows .
The ProtectionDomain for CountFileCaller1 is not checked. The restricted action is authorized even if CountFileCaller1 does not have the FilePermission to read the file C:\AUTOEXEC.BAT . |
< Day Day Up > |