USING PRIVILEGED SCOPES
This tip was developed using Java(tm) 2 SDK, Standard Edition,
v 1.3.
This issue is written by Stuart Halloway,
a Java specialist at DevelopMentor (http://www.develop.com/java).
The core Java security architecture is based on granting
permissions to code based on where the code is located. In the
Java(tm) 2 SDK, Standard Edition, starting with version 1.2,
these permissions are configured by editing a text file called
the policy file. For example, if you wanted to grant permission
to read and write files in a "temp" subdirectory off the root,
you might use a policy file like this:
//file my.policy
grant {
permission java.io.FilePermission "${/}temp${/}-", "read,write";
};
The grant block begins by specifying the location of the code that
should be granted permissions. If there were such a specification
in the example, it would go before the ${/}. But there is no
location specified. So the grant applies to all code. Inside the
braces are a list of permissions. In the example, the
FilePermission syntax gives permission to read and write files in
the temp subdirectory and all its subdirectories. The special ${/}
syntax will be replaced by the path separator on the local platform.
To verify that this policy file works correctly, compile and
execute the following java class:
import java.io.*;
public class TestPolicy {
public static void main(String [] args) {
tryToRead("/temp/foo.txt");
tryToRead("/qwyjibo/foo.txt");
}
public static void tryToRead(String fileName) {
try {
FileInputStream fis = new FileInputStream(fileName);
}
catch (SecurityException se) {
System.out.println("Didn't have permission to read " + fileName);
se.printStackTrace();
return;
}
catch (Exception e) {
//don't really care if the file was there, just checking
//if we would have been allowed to read it
}
System.out.println("Granted permission to read " + fileName);
}
}
To execute the program with security active and referencing your
policy file, you will need to use the command line
java -Djava.security.manager -Djava.security.policy=my.policy TestPolicy
If everything works as expected, you should see output similar to
this:
Granted permission to read /temp/foo.txt
Didn't have permission to read /qwyjibo/foo.txt
java.security.AccessControlException: access denied
(java.io.FilePermission qwyjibo/foo.txt read)
at java.security.AccessControlContext.checkPermission(Unknown Source)
at java.security.AccessController.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkRead(Unknown Source)
at java.io.FileInputStream.(Unknown Source)
at TestPolicy.tryToRead(TestPolicy.java:10)
at TestPolicy.main(TestPolicy.java:6)
Permission checks work by checking the entire call stack. Every
class on the call stack must have the requisite permission, or
the security check fails. This is based on the assumption that
the security manager has no special knowledge of your code, and
has to assume that any untrusted code, anywhere on the stack,
might be a threat. In the exception output above, all of the
classes that begin with "java" are part of the core API and pass
all security checks. The only problem is the TestPolicy class,
which does not have permission to access files in the "/qwyjibo"
directory.
Now, imagine that you wanted to keep an audit log of all failed
file reads. To do this, you might extend the normal
SecurityManager as follows:
//ATTENTION: compile this into a subdirectory named 'boot'
import java.io.*;
import java.security.*;
public class LoggingSM extends SecurityManager {
public void checkRead(String name) {
try {
super.checkRead(name);
}
catch(SecurityException se) {
log(name, se);
throw se;
}
}
public void log(String name, Exception se) {
try {
FileOutputStream fos = new FileOutputStream("security.log");
PrintStream ps = new PrintStream(fos);
ps.println("failed attempt to read " + name);
se.printStackTrace(ps);
}
catch (Exception e) {
System.out.println("uh-oh, the log is busted somehow");
e.printStackTrace();
}
}
}
This subclass of SecurityManager calls the default
implementation's checkRead method, but catches the exception and
logs it before throwing it back to the client.
As the comment in LoggingSM states, compile the class into a
subdirectory named "boot." After you compile the class, you can use
it as your SecurityManager by specifying its name on the command
line like this:
java -Xbootclasspath/a:boot/ -Djava.security.manager=LoggingSM\
-Djava.security.policy=my.policy TestPolicy
The addition of the -Xbootclasspath/a: flag appends the "boot"
subdirectory to the bootstrap class path. This causes the LoggingSM
class to be loaded by the bootstrap class loader, so that the
class will not fail security checks. When you run this command,
you would like to see the failed file read appear in the
security.log file. Unfortunately, this doesn't happen. Instead,
you get a console report that notes the expected security failure,
and indicates that the log failed to work. You should see something
similar to this:
uh-oh, the log is busted somehow
java.security.AccessControlException: access denied
(java.io.FilePermission security.log write)
at java.security.AccessControlContext.checkPermission(Unknown Source)
at java.security.AccessController.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkWrite(Unknown Source)
at java.io.FileOutputStream.(Unknown Source)
at java.io.FileOutputStream.(Unknown Source)
at LoggingSM.log(LoggingSM.java:16)
at LoggingSM.checkRead(LoggingSM.java:10)
at java.io.FileInputStream.(Unknown Source)
at TestPolicy.tryToRead(TestPolicy.java:9)
at TestPolicy.main(TestPolicy.java:5)
The call stack clearly illustrates the problem. Because the
untrusted TestPolicy class was on the call stack, the attempt to
open the FileInputStream throws a SecurityException. But, when
the LoggingSM class attempts to write to the log, the mischievous
TestPolicy class is still on the stack. So, the SecurityManager
blindly rejects the attempt to write the log. What this situation
calls for is some way for LoggingSM to insist "I know what I am
doing when I open the log file, so there is no need to check the
call stack any further."
The AccessController.doPrivileged() method neatly solves the
problem. When you place a block of code inside a doPrivileged
method, you are asserting that, based on your knowledge of the
code, you are confident that it is safe for the operation to
proceed without any additional security checks. Note that you are
not turning off security entirely -- the code that calls the
AccessController must still pass its own security check. (This is
why you added LoggingSM to the boot class path.) To fix the log so
that it uses a privileged block, replace the log method as
follows:
private void log(String name, Exception se) {
try {
FileOutputStream fos = (FileOutputStream)
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws PrivilegedActionException {
try {
return new FileOutputStream("security.log");
} catch (IOException ioe) {
throw new PrivilegedActionException(ioe);
}
}
});
PrintStream ps = new PrintStream(fos);
ps.println("failed attempt to read " + name);
se.printStackTrace(ps);
}
catch (Exception e) {
System.out.println("uh-oh, the log is busted somehow");
e.printStackTrace();
}
}
The doPrivileged method executes the run method of the anonymous
inner subclass of PrivilegedExceptionAction. When a security check
is necessary, it stops walking back up the call stack after it hits
this block of code.
Recompile LoggingSM into the "boot" subdirectory.
Now you can run the application with the command line
java -Djava.security.manager=LoggingSM -Djava.security.policy=my.policy\
-Xbootclasspath/a:boot/ TestPolicy
This time, the LoggingSM will be able to write to the file
system, so after the program runs the security.log file will
have a correct report of security failures that occurred. If you
have trouble getting the example to work, try adding the
"-Djava.security.debug=all" flag on the command line. This flag
produces exhaustive trace output of the security system.
For more information about privileged scopes, see "API for
Privileged Blocks" at
http://java.sun.com/j2se/1.3/docs/guide/security/doprivileged.html
|