Security


Virtual Machine

If you've paid much attention at all to coverage of Java in the technical and business press, you might have encountered discussions on Java security, and read about malicious applets and so on. This is an interesting and complex subject to contemplate, and we'll spend several issues looking at it. There are multiple types and levels of security to consider.

Let's first look at a short C program:
        void main()
        {
                char c;
                int fd;
                char* p = 0;
                char buf[1024];

                fd = open("data", 0);
                while (read(fd, &c, 1) == 1)
                        *p++ = c;
                close(fd);
        }

This is a mixture of C code and UNIX system calls. It opens a data file, reads from it, and places the read characters into a character buffer.

Unfortunately, this program has a bug, in that the statement:
        p = buf;

was omitted. So the program will write via the pointer p, but p is null (0), and the bytes will get written into memory location 0 and succeeding locations. If you're running on a DOS machine, this will probably silently crash your computer. If you are on a UNIX machine with virtual address space starting at 0, with text (the program itself) in low memory, then perhaps you'll get a segmentation violation error.

The point is that a language like C is "close" to the hardware. This is literally true if running with DOS, somewhat less true if using virtual memory and process protection. Direct use of system calls for doing I/O in the example above is another illustration of this point. This feature of C is both a strength and a weakness. For example, C is a good language for writing I/O device drivers, because it's close to the hardware.

Java takes a different tack. It has no user-visible pointers, and no notion of an explicit memory address that you can manipulate or print out. With Java it's still possible to make the dumb mistake found in the example above, but the result would simply be an immediate null pointer exception. There is no way to write a byte to physical or virtual address 0.

Similarly, there is no direct access to system calls. A Java program can certainly do I/O to disk files (applets are restricted in this area), but it can't make system calls itself.

This insulation from the physical machine, via the Java Virtual Machine, means that program execution tends to be safer as far as program crashes and interference with other programs go. The tradeoff is inability to control the actual physical machine, and some loss of efficiency. This implies that Java is suited for a somewhat different set of applications than a language like C, though of course there is much overlap between the two.


Verification

We saw last time how the Java virtual machine model insulates a Java program from hardware, and how this is both desirable and undesirable.

In this issue we'll talk a bit about verification, another aspect of security. A java class is compiled into platform-independent ".class" files containing byte streams. These streams contain the actual program codes (bytecodes, constants, debugging information, and so on).

Suppose that I have the hello program:
        public class hello {

                public static void main(String args[])
                {
                        System.out.println("Hello World");
                }

        }

and I compile it:

        $ javac hello.java

resulting in a "hello.class" file of 461 bytes (in JDK 1.1). And I'm feeling malicious and decide to tweak one of the bytes:
        import java.io.*;

        public class tweak {
                public static void main(String args[])
                {
                        int offset = Integer.parseInt(args[0]);
                        try {
                                RandomAccessFile raf =
                                    new RandomAccessFile("hello.class", "rw");
                                raf.seek(offset);
                                raf.writeByte(97);
                                raf.close();
                        }
                        catch (Throwable e) {
                                System.err.println("*** exception ***");
                        }
                }

        }

by saying:
        $ javac tweak.java
        $ java tweak 0

thereby writing the byte "97" into location 0 in "hello.class". Obviously, this is not how Java was intended to be used. What will happen?

It turns out that the first four bytes of a Java .class file must be the hex value "0xCAFEBABE", and writing 0 into one of these will invalidate the file. If I then try to run the program:
        $ java hello

I will get an immediate error. This particular feature is fairly common in program binaries and often goes by the name of "magic number".

When a Java program is run, the interpreter first invokes the Java Verifier. The Verifier checks the magic number along with other properties of the .class file, including:

There are many other checks done by the Verifier on .class files. This list is merely illustrative. Verifying a class not only improves security, but speeds up actual bytecode interpretation because the checks don't have to be repeated.

An interesting book that goes into detail on Java security is "Java Security - Hostile Applets, Holes, and Antidotes", by Gary McGraw and Edward Felten, published 1997 by Wiley for $20.

We will be discussing security further in future issues.


The Security Manager

In previous issues we've looked at how the Java programming model insulates one from hardware, and how various types of checks are performed before executing a Java program.

In this issue we'll look at the Java security manager. This is a class in java.lang, that can be used to impose a specific security policy on running Java programs (including applets loaded by a Web browser).

The SecurityManager class contains a set of methods, such as checkRead(), that are called by Java core libraries. In a standalone program, typically no security manager is installed, and the program is wide open as to what it's allowed to do. An instance of a SecurityManager class object can be installed exactly once, after which the security policies in force are those of the class instance. By default, nothing is allowed, and so a class derived from SecurityManager needs to override some of the methods.

Let's see how this works in practice. There is a method:
        public void checkRead(String file)
        {
                throw new SecurityException();
        }

found in SecurityManager. We can override this method as follows:
        import java.io.*;

        class test_SecurityManager extends SecurityManager {
                public void checkRead(String file)
                {
                        if (file.charAt(0) == '/')
                                throw new SecurityException();
                }
        }

        public class Security {
                public static void main(String args[])
                {
                        System.setSecurityManager(new test_SecurityManager());
                        try {
                                FileInputStream fis =
                                    new FileInputStream("/xxx");
                                fis.read();
                                fis.close();
                        }
                        catch (SecurityException e) {
                                System.err.println("security exception");
                        }
                        catch (IOException e) {
                                System.err.println("I/O exception");
                        }
                        catch (Throwable e) {
                                System.err.println("some other exception");
                        }
                }
        }

to check the pathnames of files that an application accesses. In this example, we check that the pathname does not start with "/". If it does, we throw a SecurityException.

As we said, a security manager can only be established once in a given session (if this was not so, the old manager could simply be replaced by a more liberal one). By default, the check methods disallow operations, so an overriding method will typically relax some restriction. A Web browser may or may not have mechanisms for changing the default security policies it imposes on applets that are downloaded and executed.

This particular example does not work correctly with JDK 1.1 running on Windows 3.51, and requires JDK 1.1.1.


Class Loaders

In previous issues we've looked at some of the aspects of Java security, namely Java's use of an abstract machine removed somewhat from the hardware, verification of loaded code, and the security manager.

Another aspect of security centers around class loaders. A class loader in Java is responsible for converting a stream of bytes (for example, as found in a .class file) into a class known to the Java runtime system. When you run a Java standalone program, there is a built-in loader that converts .class files into a running program. Similarly, when an applet is downloaded from the Web, there is a loader responsible for installing it on your system.

Other languages such as C also have the notion of loading, where a binary image is turned into an executing program. But with Java there are some security aspects with loading as well.

For example, suppose that I have malicious intentions, and I decide to fool the Java system by trying to override one of the core classes, java.lang.Runtime, supplying my own version. How is this prevented by the class loader?

One way this is done is by keeping local built-in classes distinct from those loaded from a URL somewhere on the Web, that is, keeping classes in separate name spaces, according to where they were loaded from. When a class is looked up, the local name space can be checked first.

There's more we could say about class loaders, but this brief introduction gives an idea of what is going on behind the scenes.


Denial of Service

Another type of security problem in Java, that's a bit different from what we've already looked at in this series, is denial of service. This involves an applet that meets the security requirements previously discussed, but which essentially takes over the user's whole machine and prevents anything else from being done. That is, I as a user am surfing the Web, and come to a page with a Java applet call embedded in the HTML. The applet starts up and prevents me from using my computer for any other function.

A couple of examples of such applets would be one that does some sort of number crunching function, or one that creates a large number of very large windows. McGraw and Felten's "Java Security" book gives some examples of such applets, which we won't reproduce here.

The simplest solution to this type of problem is to by default disable your Web browser's ability to execute Java applets, and only selectively enable this feature for Web sites that you trust. Another possible longer-term approach would involve being able to establish some sort of resource usage limits.