java.security.AccessController
API: the inability to
enforce proper bracketing of begin/endPrivileged
calls. We are converting to a closure-based API that guarantees bracketing
and is easy to implement in the VM.
The next section provides background information about what "privileged" code is and what it is used for. Subsequent discussion illustrates the use of the new API, with special attention to issues of:
The policy for a JDK installation specifies what permissions -- which types of system resource accesses -- are allowed for code from specified code sources. A "code source" (of type CodeSource) essentially consists of the code location (URL) and a reference to the certificate(s) containing the public key(s) corresponding to the private key(s) used to sign the code (if it was signed).
In order for an applet (or an application running under a Security Manager) to be allowed to perform a secured action (such as reading or writing a file), the applet or application must be granted permission for that particular action.
A "protection domain" encompasses a CodeSource and the permissions granted to code from that CodeSource, as determined by the security policy currently in effect. Thus, classes signed by the same keys and from the same URL are placed in the same domain, and a class belongs to one and only one protection domain. Classes that have the same permissions but are from different code sources belong to different domains.
Today all code shipped as part of the JDK is considered system code and run inside the unique system domain. Each applet or application runs in its appropriate domain, determined by its code source.
System code automatically has all permissions.
Whenever a resource access is attempted, all code
traversed by the execution thread up to that point must have permission
for that resource access, unless some code on the thread has been marked
as "privileged". That is, suppose access control checking occurs in a thread
of execution that has a chain of multiple callers. (Think of this as
multiple method calls that potentially cross the protection domain boundaries.)
When the AccessController
checkPermission
method is
invoked by the most recent caller, the basic algorithm for deciding whether to
allow or deny the requested access is as follows:
If the domain for any caller in the call chain does not have the requested permission, AccessControlException is thrown, unless the following is true - a caller whose domain is granted the said permission has been marked as "privileged" (see below) and all parties subsequently called by this caller (directly or indirectly) all have the said permission.
Marking code as "privileged" enables a piece of trusted code to temporarily enable access to more resources than are available directly to the code that called it. This is necessary in some situations. For example, an application may not be allowed direct access to files that contain fonts, but the system utility to display a document must obtain those fonts, on behalf of the user. In order to do this, the system utility becomes privileged while obtaining the fonts.
Up through JDK 1.2 Beta3, we provided the AccessController
beginPrivileged
and
endPrivileged
methods for the system domain to bracket
privileged code, and these methods were in fact available to all domains.
This document describes the weaknesses of that approach and
why it has been replaced with use of a single method, named
doPrivileged
.
java.security.AccessController
API has been
criticized for certain weaknesses regarding the beginPrivileged()
and endPrivileged()
calls. Recall that these calls must be used in matching pairs to establish
and revoke security privileges for a method.
The API has the following disadvantages.
In the face of asynchronous exceptions, the recommended usage is:
try { AccessController.beginPrivileged(); // privileged code } finally { AccessController.endPrivileged(); }Users may forget to use the
try-finally
statement. Indeed, they
may forget to call endPrivileged()
altogether.
There is no mechanism to enforce the required bracketing or the
recommended usage of try-finally
.
Even if proper usage is observed, there remains the possibility of some
asynchronous exception being thrown in the finally
clause,
prior to the call to endPrivileged()
.
There have been discussions on how to fix this, and the conclusions were that such fixes would require extensive VM changes (spec and implementation), and in the end would be a very complex piece of baggage to add to the VM. The API change, on the other hand, is fairly trivial to implement, and its changes to the VM are localized.
AccessController
methods
begin/endPrivileged
are replaced by a single call
to doPrivileged
:
public static native Object doPrivileged(PrivilegedAction action);
The AccessController.doPrivileged()
method takes an object
of type java.security.PrivilegedAction
and invokes its
run()
method in
privileged mode. Consequently, there is no possibility of a user forgetting
to revoke privileges. The implementation guarantees that privileges will be
revoked after the run()
method is executed, even if execution
of doPrivileged()
is interrupted by an asynchronous exception.
The intent is that the existing pattern of:
try { AccessController.beginPrivileged(); // privileged code } finally { AccessController.endPrivileged(); }gets replaced by the following pattern:
AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // privileged code return null; } });
PrivilegedAction is an interface with a single method, named
run
, that returns an Object.
The above example shows creation of an implementation
of that interface; a concrete implementation of the
run
method is supplied.
When the call to doPrivileged
is made, an
instance of the PrivilegedAction implementation is passed
to it. The doPrivileged
method calls the
run
method from the PrivilegedAction
implementation after enabling privileges, and returns the
run
method's return value as the
doPrivileged
return value (which is
ignored in this example).
Note that depending on what "privileged code" actually consisted of, you might have to make some changes due to the way inner classes work. For example, if "privileged code" throws an exception or attempts to access local variables then you will have to make some changes, as shown below.
Be *very* careful in your use of the "privileged" construct, and
always remember to make the privileged code section as small as possible,
i.e., try and limit the code within the run
method to only the code
that needs to be run with privileges, and do more general things outside
the run method. Also note that the call to doPrivileged
should be made in the code that wants to enable its privileges. Do not
be tempted to write a utility class that itself calls
doPrivileged
as that could lead to security holes.
You can write utility classes for PrivilegedAction
classes though.
The normal use of the "privileged" feature is as follows. If you don't need to return a value from within the "privileged" block, do the following:
somemethod() { ...normal code here... AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // privileged code goes here, for example: System.loadLibrary("awt"); return null; // nothing to return } }); ...normal code here... }
If you are using an anonymous inner class, any local variables you access must be final. For example:
somemethod() { ...normal code here... final String lib = "awt"; AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // privileged code goes here, for example: System.loadLibrary(lib); return null; // nothing to return } }); ...normal code here... }
The variable lib
must be declared final if you intend
to use it within the privileged block. See the
"Inner Classes" spec for more information on this topic.
If there are cases where you can't make an existing variable final
(because it gets set mulitple times), then you can create a new final
variable right before invoking doPrivileged
, and set that
variable equal to the other variable. For example:
somemethod() { ...normal code here... String lib; ... // lib gets set multiple times so we can't make it final ... // create a final String that we can use inside of the run method final String fLib = lib; AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // privileged code goes here, for example: System.loadLibrary(fLib); return null; // nothing to return } }); ...normal code here... }
If you need to return a value, you can do something like the following:
somemethod() { ...normal code here... String user = (String) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return System.getProperty("user.name"); } } ); ...normal code here... }
Note that this usage requires a dynamic cast on the value returned
by doPrivileged
. An alternative is to use a final local
variable:
somemethod() { ...normal code here... final String user[] = {null}; AccessController.doPrivileged( new PrivilegedAction() { public Object run() { user[0] = System.getProperty("user.name"); return null; // still need this } } ); ...normal code here... }And yet another alternative would be to write a non-anonymous class that safely handles types for you:
somemethod() { ...normal code here... GetPropertyAction gpa = new GetPropertyAction("user.name"); AccessController.doPrivileged(gpa); String user = gpa.getValue(); ...normal code here... } class GetPropertyAction implements PrivilegedAction { private String property; private String value; public GetPropertyAction(String prop) { property = prop;} public Object run() { value = System.getProperty(property); return value; } public String getValue() {return value;} }
Note there are now no type-casts involved, although the run
method still returns a value, so you could still have a "one liner" if you
wanted to:
somemethod() { ...normal code here... String user = (String) AccessController.doPrivileged( new GetPropertyAction("user.name")); ...normal code here... }
If the action performed in your run
method could
throw a "checked" exception (one that must be listed in the
throws
clause of a method), then you need to use the
PrivilegedExceptionAction
interface instead of the
PrivilegedAction
interface:
somemethod() throws FileNotFoundException {
...normal code here...
try {
FileInputStream fis = (FileInputStream) AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws FileNotFoundException {
return new FileInputStream("someFile");
}
}
);
} catch (PrivilegedActionException e) {
// e.getException() should be an instance of FileNotFoundException,
// as only "checked" exceptions will be "wrapped" in a
// PrivilegedActionException
.
throw (FileNotFoundException) e.getException();
}
...normal code here...
}
doPrivileged()
method can be invoked reflectively
using java.lang.reflect.Method.invoke()
. In this case, the privileges
granted in privileged mode are not those of Method.invoke()
but
of the non-reflective code that invoked it. Otherwise, system privileges
could erroneously (or maliciously) be conferred on user code. Note that
similar requirements exist when using the existing
API via reflection.