dcsimg
December 11, 2016
Hot Topics:

Privileged code in Java: Why the API changed from JDK1.2beta3 to JDK1.2beta4

  • August 31, 1998
  • By Gary McGraw
  • Send Email »
  • More Articles »

August 31, 1998
Privileged code in Java: Why the API changed from JDK1.2beta3 to JDK1.2beta4

by Gary McGraw and John Viega

See the sidebar Why not to use inner classes

The main purpose of this article is to shed some light on a major change that was recently made to the JDK 1.2 API for privileged code blocks.

We begin by explaining the evolution of Java's security model from a "sandbox" architecture to a trust model. We also briefly touch on the notion of stack inspection, which is the way JDK 1.2 actually makes access control decisions behind the scenes. With these two things under our belt, we'll be ready to take on the new API.

The evolution of Java security

There are two major approaches to addressing the security concerns raised by mobile code systems: sandboxing and code signing. The first of these approaches, sandboxing, is an idea embraced by early implementations of Java (JDK 1.0.2). The idea is simple: make untrusted code run inside a box and limit its ability to do risky things. In the second approach, code signing, binary objects such as Java class files can be digitally signed by someone who "vouches" for the code. If you know and trust that person or organization, you may choose to trust the code they vouch for.

JDK 1.1 introduced the notion of signed applets to Java. With the addition of signed applets, Java's sandbox model underwent a state transition from a required model applied equally to all Java applets to a malleable system that could be expanded and personalized on an applet-by-applet basis. In fact, the distinction between applets and applications no longer applies in Java. The new way of thinking about mobile code is in terms of trust. Untrusted code needs to be restricted. Completely trusted code does not.

The binary trust model designed into JDK 1.1 is not sufficiently powerful for many needs. In JDK 1.1, applet code signed by a trusted party can be treated as trusted local code, but not as partially trusted code. There is no notion of access control beyond the one and only trust decision made per class. That means JDK 1.1 offers a black-and-white trust model.

When combined with access control, code-signing allows Java applets to step outside the security sandbox gradually. In fact, the entire meaning of the sandbox becomes a bit vague. JDK 1.2 implements a configurable sandbox model that can be adjusted to reflect fine-grained security policies based on signed code. As an example of how Java code-signing might work, an applet designed for use in an intranet setting could be allowed to read and write to a particular company database as long as it was signed by the system administrator. Such a relaxation of the security model is important for developers who are champing at the bit for their applets to do more. Writing code that works within the tight restrictions of the sandbox is a pain, and the original sandbox is very restrictive.

JDK 1.2 code running on the new Java VMs can be granted permissions and have its access checked against policy when it runs. The cornerstone of the system is policy. Policy can be set by the user (usually a bad idea) or by the system administrator and is represented in the class java.security.Policy. Herein rests the Achilles' Heel of JDK 1.2 security. Setting up a coherent policy at a fine-grained level takes experience and security expertise. Today's harried system administrators are not likely to enjoy this added responsibility. On the other hand, if policy management is left up to users, mistakes are bound to be made. Users have a tendency to prefer "cool" to "secure."

Access control and stack inspection

The idea of access control is not a new one in computer security. For decades, researchers have built on the fundamental concept of grouping and permissions. The idea is to define a logical system in which entities known as principals (often corresponding one-to-one with code owned by users or groups of users) are authorized to access a number of particular protected objects (often system resources such as files). To make this less esoteric, consider that the familiar JDK 1.0.2 Java sandbox is a primitive kind of access control. In the default case, applets (which serve as principals in our example) are allowed to access all objects inside the sandbox but none outside it.

Sometimes a Java application (say, a Web browser) needs to run untrusted code within itself. In this case, Java system libraries need some way of distinguishing between calls originating in untrusted code and calls originating from the trusted application itself. Clearly, the calls originating in untrusted code need to be restricted to prevent hostile activities. By contrast, calls originating in the application itself should be allowed to proceed, as long as they follow any security rules that the operating system mandates. The question is, how can we implement a system that does this?

Java implements such a system by allowing access-control checking code to examine the runtime stack for frames executing untrusted code and compare what is happening against policy. Each thread of execution has its own runtime stack. Security decisions can be made with reference to a stack inspection [Wallach et al., 1997]. All the major vendors have adopted stack inspection to meet the demand for more-flexible security policies than those originally allowed under the old sandbox model. Stack inspection is used by Netscape Navigator 4.0, Microsoft Internet Explorer 4.0 and Sun Microsystems' JDK 1.2 beta.

For much more on stack inspection, see [McGraw and Felten, 1998].

Privileged code

From the introduction of JDK 1.2 beta up through JDK 1.2beta3, Sun's JDK used the primitives
beginPrivileged
and
endPrivileged
to demarcate sections of code that are privileged. These are the calls that a piece of privileged system code -- which is allowed to do things like perform file access -- were supposed to use to grant temporary permission to less trusted code. These calls were featured in a number of technical publications from Sun, such as [Gong and Schemers, 1998]. The calls themselves mark the stack in such a way to make stack inspection work.

The idea is to encapsulate potentially dangerous operations that require extra privilege into the smallest possible self-contained code blocks. The Java libraries make extensive use of these calls internally, but partially trusted application code written using the JDK 1.2 model will be required to make use of them too.

Correct use of the JDK primitives required using a standard try/finally block is as follows:


try {
	AccessController.beginPrivileged();
	
} finally {
	AccessController.endPrivileged();
}

This usage was required to address the problem of asynchronous exceptions (though there was still some possibility of an asynchronous exception being thrown in the finally clause sometime before the

endPrivileged()
call).

Wallach and Felten first explained a particularly efficient way to implement stack inspection algorithms using multiple primitives in [Wallach and Felten, 1998]. Unfortunately, Sun decided to abandon the multi-primitive approach to stack inspection (which could benefit from the Princeton researcher's implementation). In fact, JDK 1.2beta4 introduced a completely new API for privileged blocks. The new API removes the need for a developer to: 1) make sure to use try/finally properly and 2) remember to call

endPrivileged()
. The try/finally usage was symptomatic of a problem that could only really be fixed with some changes to the VM specification and its resulting implementations.

In order to properly implement the early API, VMs would have been forced to keep track of the

beginPrivileged()
call (unless they adopted the Princeton approach). This requires tracking a stack frame, the one where the
beginPrivilege
is called, and matching the beginning of a privileged block to its corresponding end -- every time a privileged block is used. Doing all this bookkeeping is inefficient and thwarts optimization tricks that compilers like to play. For example, just-in-time (JIT) compilation approaches are hard to adapt to this model. Plus, it turns out that privilege boundaries are crossed many thousands of times a second, so even a slight delay gets magnified quickly.

Bookkeeping would slow down the VM, which is about the last thing Java VMs need now as they near native C speeds. Sun has a document explaining the change (from which some of the material here was drawn) calledNew API for Privileged Blocks on the Web.

The new API interface wraps the complete enable-disable cycle in a single interface accessed through a new AccessController method called

doPrivileged()
. That means the VM can efficiently guarantee that privileges are revoked once the method has completed, even in the face of asynchronous exceptions.

Here's what the new usage looks like. Note the use of Java's inner classes capability:

somemethod() {
	
	AccessController.doPrivileged(new PrivilegedAction() {
			public Object run() {
		
		return null;
		}
	});
	
}

Ironically, in Securing Java, McGraw and Felten recommend that Java developers avoid the use of inner classes to make their code more secure (see the sidebar in this article and Chapter 7 of Securing Java: Getting down to business with mobile code.) But if you want to include privileged blocks in your JDK 1.2 code, you are strongly encouraged by Sun to use them. In addition to the inner-class problem, verbosity is also a problem with the new API.

It turns out that using the new API is not always straightforward. That's because anonymous inner classes require any local variables that are accessed to be final. A small diversion can help explain why this is.

Closures

The new API is doing its best to simulate what programming language researchers call closures. The problem is, Java doesn't have closures. So what are they anyway? And why are they useful?

Functions in most programming language use variables. For example, the function f(x)=x+y adds the value of the formal parameter x to the value of variable y. The function f has one free variable, y. That means f may be evaluated (run) in different environments where the variable y takes on different values. In one environment, E1, y could be bound to 2. In another environment, E2, y could be bound to 40. If we evaluate f(2) in E1, we get the answer 4. If we evaluate f(2) in E2, we get the answer 42.

Sometimes we want a function to retain certain bindings that its free variables had when it was created. That way we can always get the same answer from the expression. In terms of our example, we need to make sure y always takes on a certain value. What we want is a closed package that can be used independently of the environment in which it is eventually used. That is what a closure is. In order to be self-contained, a closure must contain a function body, a list of variables, and the bindings of its variables. A closure for our second example from above might look like this:

[{y=40;} f(2)=2+y]
Closure is particularly useful in languages with first-class functions (like Scheme and ML). In these and other related languages, functions can be passed to and returned from other functions, as well as stored in data structures. Closure makes it possible to evaluate a function in a location and external environment that may differ from where it was created. For more on this issue, see [Friedman et al., 1992].

As we said before, Java does not have closures. Java's anonymous inner classes come as close to being closures as Java gets. A real closure might include bindings for the variables that can be evaluated sometime in the future. But in an anonymous inner class, all state must be made final (frozen) before it is passed in. That is, the final state is the only visible state inside the inner class. This is one reason anonymous inner classes are not true closures. The problem of making everything final turns out to have strong implications for the use of the new privileged block API.

Local variables

For example, in the following code, the variable
lib
must be declared final if it is to be used inside the privileged block:

randommethod() {
	
	final String lib = "awt";
	AccessController.doPrivileged(new PrivilegedAction() {
		public Object run() {
		System.loadLibrary(lib);
		return null;
		}
	});
	
}

Making all local variables that are to be accessed in the block final is a pain, especially if an existing variable can't be made final. In the latter case, the trick is to create a new final variable and set it to the non-final variable just before the call to

doPrivileged
. We predict this will be a source of both headaches and errors, potentially leading to security problems.

What comes out of
doPrivileged()

Another problematic issue with the new interface is the fact that the inner class always returns an object. That means if a call to a piece of privileged code, for example a call to
System.getProperty()
, usually returns something other than an object, for example a string, it will have to be dynamically cast to the usual type. Using a final variable to pass types out is possible too. Unfortunately, both of these operations will incur a runtime performance hit -- especially casting. The returns-only-object problem is another source of potential errors (unfortunately common throughout the entire Java programming paradigm).

Whence the change

It is good that VM vendors want their machines to be fast and efficient. But purely in terms of security, it is unclear whether the decision to change the API was a good one. Not that the previous API was perfect, but the new one seems to introduce several places where errors are bound to be made by developers charged with actually using VMs. The real answer to the problem is introducing closures to Java. Closures are something to look for in future JDK versions.

References

Related links

Gary McGraw, Ph.D., is vice President of Reliable Software Technologies. Dr. McGraw is a noted authority on Java security and co-authored Java Security: Hostile Applets, Holes & Antidotes (Wiley, 1996) with Prof. Ed Felten of Princeton. They are currently writing a second book, Securing Java: Getting Down to Business with Mobile Code, available in late 1998. Along with RST's Dr. Jeff Voas, McGraw has written Software Fault Injection: Inoculating Programs Against Errors (Wiley, 1998). Dr. McGraw also has over 50 peer-reviewed publications, consults with major e-commerce vendors such as Visa, and is principal investigator on grants from the U.S. Air Force Research Labs, DARPA and NIST's Advanced Technology Program.

John Viega is a research associate at Reliable Software Technologies. He holds a M.S. in computer science from the University of Virginia. Viega developed and maintains mailman, the gnu mailing list manager. His research interests include software assurance, programming languages and object-oriented systems.

Portions of this article are taken by permission from Securing Java: Getting Down to Business with Mobile Code (John Wiley & Sons, 1998), the second edition of McGraw and Felten's book Java Security: Hostile Applets, Holes, & Antidotes.






Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.


Thanks for your registration, follow us on our social networks to keep up-to-date
Rocket Fuel