Check out the new USENIX Web site.
Using JavaUSENIX

  rao_prithvi

by Prithvi Rao
<prithvi@kiwilabs.com>

Prithvi Rao is the founder of Kiwilabs, which specializes in software engineering methodology and Java training. He has worked on the development of the MACH OS and a real-time version of MACH, and he holds two patents resulting from his work on mobile robots.

Is Java Secure?

Since its inception, the Java programming environment has been scrutinized widely with regard to its suitability as a secure environment. The result of this speculative approach (which it was in many cases) was that Java received less than a complimentary review in this context. Consequently, many corporate decisions were made based on the crystal ball approach, which was that Java was "insecure" and so was unsuited to applications such as electronic commerce. I have personally experienced this way of thinking.

The purpose of this article is not to venture unsolicited opinions on the decisions made in the corporate world regarding Java and its "secureness," but to objectively investigate the security model in Java. We will look at the "sandbox" model of security and look at some examples of code that will assist the reader to better understand how to translate a security "policy" into an actual "mechanism" to exercise that policy. Java security is an area of great interest to those developing virtual machines to third-party developers who write applications. I hope this article will shed some light on the Java security issues for all interested parties.

Java security comprises two parts: security inside the Java Virtual Machine (JVM) and security outside the JVM. We will look at security issues inside the JVM in this article. In subsequent articles, we will examine the security issues outside the JVM.

The work presented here is largely the research efforts of my brother, Ravi Rao, who also is the co-founder of Kiwilabs.

Security inside the JVM concerns itself with bytecode running on a JVM. The target of a Java compiler is the JVM and all bytecode is ultimately interpreted inside the JVM. The Java programming environment provides features that will supply mechanisms to ensure secure environments for running Java programs. In specific, we will examine the basic operation of the JVM, the classloader, and the security manager.

Examples of Java Security Violation

Java platforms have been successfully attacked in the past. The work by David Hopwood showed that the integrity of the JVM could be compromised by placing files in the cache by loading them as though they were from a local filesystem. Specifically Hopwood showed that, in early implementations of Java, when class names began with a "/", this permitted anyone who had access to a local filesystem to place "attack files" in those places. Also, it was shown that allowing access to a public FTP directory assisted in compromising the integrity of the JVM.

Another form of attack was applet based. In this scenario, a classloader was installed from an applet, which in turn installed Java classes as trusted classes. This was possible because of a bug in the Java bytecode verifier.

Language Support for Security

There are no pointers in Java but only references. The main difference is that with pointers you can do arithmetic that you cannot do with references. So it is not possible to scan memory.

Changing the cast of an object invokes a check at compile time or runtime. Any casting not done at compile time is done at runtime. Access to methods and fields are checked at runtime. If an application tries to access a method of a class that is declared as "private," a runtime exception is generated (IllegalAccessException).

The Java compiler produces a .class file that is neutral to architecture. It is therefore not possible to discover how a program is laid out in memory. This means that it is not possible to take advantage of any information about memory maps to devise attacks.

The Sandbox Model of Java Security

Java provides support to deal with two main problems, namely, unknown sources and immediate execution of bytecode. The term "sandbox" refers to the combination of classloader, bytecode verifier, and security manager to create a secure environment in which Java applications can run.

The bytecode is the part of the JVM that performs checks on the incoming bytecode. This process is transparent to users and it is not under their direct control.

The security manager enforces security policies for executable content. In other words, it controls the actions that a particular Java class can perform based on some policy.

The classloader is the connection between the JVM and the outside world.

How the Bytecode Verifier Works

Before a Java program can run, it must be loaded into the JVM. The loading process begins by reading a file that contains the bytecode (has a .class name) and storing this in a buffer. The verifier acts on the information in this buffer. It uses the fact that Java bytecode conforms to a well-known format (8-bit bytes) and that each Java .class file contains information about one Java class.

A .class file is a stream of 8-bit bytes, and larger quantities are represented in terms of 8-bit bytes. This information is also stored in big endian format (the most significant bits are leftmost). The first four bytes are the "magic number," which acts as an identifier. The other information contained refers to the major and minor number of the compiler that produced the .class file. The JVM is supposed to support classes that originate from a compiler with a fixed major number and a minor number that has a predefined upper limit.

The constant pool is an array of structures representing the names of classes, interfaces, fields, and methods. Debugging information is also available.

Verification is therefore part of the larger process undertaken by the JVM in order to execute bytecode. For instance, suppose we want to execute a "hello world" program. The following happens:

 1. Load .class file into JVM.
 2. Link bytecode with Java runtime.
 3. Verify bytecode.
 4. Prepare runtime (allocate resources).
 5. Resolve references.
 6. Initialize classes.
 7. Invoke "main" method.

In all of this the main accomplishments of the bytecode verifier are:

 1. checking of.class file format
 2. protection against version skew
 3. checking for stack overflow
 4. checking for illegal data conversions
 5. checking that instructions have proper parameters on the stack

The Classloader

The classloader is the link between the outside world and the JVM. All bytecode brought into the JVM must be done so under the auspices of a classloader. There is a default classloader as part of the JDK. Users who write their own may eventually call the default classloader if they are unable to load a class using other classloaders.

One of the requirements of writing a Java program is that users set an environment variable known as CLASSPATH. This variable is used by the default classloader to load "trusted" classes. The logic is that if there are classes found under CLASSPATH, then they must have been put there by the person who set this variable, so the default classloader can "trust" the class.

The alternative is that if there is a class not in CLASSPATH, a separate classloader must be provided to load it. The implication here is that the classloader is part of the identity of the class. For instance, browsers often use different classloaders to load classes from different sources. Given that classloaders play a vital role in the loading of classes, the security manager must check to see if a class is allowed to create a classloader.

In summary, two classes are of the same type only if they have the same fully qualified name (FQN) and they are loaded by the same classloader.

The class java.lang.classloader is an abstract class from which other classloaders can be subclassed.

protected abstract class loadClass(String name, boolean resolve) throws ClassNotFoundException;

The following is an example of a classloader:

import Java.io.*;
import Java.net.*;

public final class URLClassLoader extends Classloader
{
Extend Java.lang.ClassLoader which is an abstract class

private String urlAsString;

This is a string containing the location from which this classloader will load files. It could be a URL such as <https://www.foobar.edu>. It is set once at the time this class is instantiated.

protected URLClassLoader() throws MalformedException
{
 this(null);
}

The constructor for this class takes no arguments.

public URLClassLoader(String urlStr) throws MalformedURLException
{
 if (urlStr == null || urlStr.length() == 0)
  throw MalformedURLException("No url provided.");
 urlAsString = urlStr;
}

This constructor just checks that there is a string and it has nonzero length. If not, then it throws a MalformedURLException.

public synchronized Class loadClass(String name, boolean resolve)
 throws Java.lang.ClassNotFoundException
{
}

Loadclass is the abstract method that must be implemented.

private byte[] readClassFile(String classFileName) throws
 FileNotFoundException, IOException
{
}

Note: It is beyond the scope of this article to provide the bodies for these methods. You can send mail to <prithvi@kiwilabs.com> for the code examples.

In summary the classloader does the following:

 1. takes a name and produces a Class object
 2. subclasses from Java.lang.Classloader (an abstract class)
 3. defines method loadClass after extending ClassLoader
 4. maintains separate namespaces

The Security Manager

The security manager is responsible for enforcing the security policies on Java programs. It controls access to resources on the platform such as filesystems and networks, environments on the host, and such things as the creation of processes. Most important, it is used to control the creation of classloaders.

The method System.setSecurityManager() is used to accomplish this goal. The class Java.lang.SecurityManager is an abstract class. The user must extend this class to implement a security policy. An example of part of a security manager follows.

public class URLMain throws MalformedURLException,
     ClassNotFoundException
{
 SampleSM sem;

The class name we will use for our security manager is SampleSM. So we declare it to be of type SampleSM.

 Hashtable clHashtable = new Hashtable();
 URLClassLoader urlcl;
 Class cl;
 StringBuffer urlSB;
 StringBuffer classFileSB;
 int ch;
 String urlString;
 String classFileName;

Hash table to keep track of all classloaders and string buffers for reading URLs and class files.

 ssm = new SampleSM(true);
 System.setSecurityManager(ssm);
 ...
}

NOTE: The rest is beyond the scope of this article. If you wish to see the rest of this example, send mail to <prithvi@kiwilabs.com>

In summary:

 1. Use Java.lang.SecurityManager to set security policy.
 2. For each resource a check is performed to grant access.
 3. Security manager controls the creation of class loaders.
 4. Applications set security manager once.
 5. Security manager first is defined and then implemented in code.

Conclusion

We have seen that there are two basic aspects to security in the Java programming environment. The first is security inside the JVM, and the second is security outside the JVM. In this article, we have concentrated on the first part of Java security, known as the "sandbox."

In the sandbox model, the bytecode verifier, classloader, and security manager all work in a cooperative manner to strengthen the security features of Java.

It has been said that the only really secure machine is one which is enclosed in a room with no entry capability, no network connectivity, and no console. Perhaps in time that requirement will be extended to no power also.

However, in the real world, where Java is clearly making its presence known, we can be assured that its developers had a strong security model in mind during its design.

As with all new technologies, there is a period of time during its evolution when weaknesses will be exposed, and there have been a few in Java's security model. The security features in the JDK1.2beta3 release is likely to provide interested parties sufficient evidence that Java has evolved to satisfy stringent security requirements.

The time has arrived when we can put away the crystal ball and make informed decisions regarding the use of Java in applications where security is an important consideration.

 

?Need help? Use our Contacts page.
First posted: 17th September 1998 efc
Last changed: 17th September 1998 efc
Issue index
;login: index
USENIX home