next up previous
Next: Second Approach: Extended Stack Up: Approaches Previous: Common Underpinnings

Subsections

First Approach: Capabilities

  In many respects, Java provides an ideal environment to build a traditional capability system [11,27]. Electric Communities [10] and JavaSoft [14][*] have implemented such systems. This section discusses general issues for capabilities in Java, rather than specifics of the Electric Communities or JavaSoft systems.

Dating back to the 1960's, hardware and software-based capability systems have often been seen as a good way to structure a secure operating system [19,34,44]. Fundamentally, a capability is an unforgeable pointer to a controlled system resource. To use a capability, a program must have been first explicitly given that capability, either as part of its initialization or as the result of calling another capability. Once a capability has been given to a program, the program may then use the capability as much as it wishes and (in some systems) may even pass the capability to other programs. This leads to a basic property of capabilities: any program which has a capability must have been permitted to use it[*].

Capabilities in Java

In early machines, capabilities were stored in tagged memory. A user program could load, store, and execute capabilities, but only the kernel could create a capability [27]. In Java, a capability is simply a reference to an object. Java's type safety prevents object references from being forged. It likewise blocks access to methods or member variables which are not labeled public.

The current Java class libraries already use a capability-style interface to represent open files and network connections. However, static method calls are used to acquire these capabilities. In a more strongly capability-based system, all system resources (including the ability to open a file in the first place) would be represented by capabilities. In such a system, an applet's top-level class would be passed an array of capabilities when initialized. In a flexible security model, the system would evaluate its security policy before starting the applet, then pass it the capabilities for whatever resources it was allowed. If an applet is to be denied all file system access, for example, it need only not receive a file system capability. Alternately, a centralized ``broker'' could give capabilities upon request, as a function of the caller's identity.

Interposition

Java capabilities would implement interposition by providing the exclusive interface to system resources. Rather than using the File class, or the public constructor of FileInputStream, a program would be required to use a file system capability which it acquired on startup or through a capability broker. If a program did not receive such a capability, it would have no other way to open a file.

Since a capability is just a reference to a Java object, the object can implement its own security policy by checking arguments before passing them to its private, internal methods. In fact, one capability could contain a reference to another capability inside itself. As long as both objects implement the same Java interface, the capabilities could be indistinguishable from one another.


 
Figure 2: Interposition of a restricted file system root in a capability-based Java system. Both FS and SubFS implement FileSystem, the interface exported to all code which reads files. 
// In this example, any code wishing to open a file must first obtain
// an object which implements FileSystem.  

interface FileSystem { 
    public FileInputStream getInputStream(String path); 
}

// This is the primitive class for accessing the file system. Note that
// the constructor is not public -- this restricts creation of these
// objects to other code in the same package.

public class FS implements FileSystem {
    FS() {}
    
    public FileInputStream getInputStream(String path) {
        return internalOpen(path);
    }
    
    private native FileInputStream internalOpen(path);
}

// This class allows anyone holding a FileSystem to export a subset of
// that file system.  Calls to getInputStream() are prepended with the
// desired file system root, then passed on to the internal FileSystem
// capability.

public class SubFS implements FileSystem {
    private FileSystem fs;
    private String rootPath;
    
    public SubFS(String rootPath, FileSystem fs) {
        this.rootPath = rootPath;
        this.fs = fs;
    }
    
    public FileInputStream getInputStream(String path) {
        // to work safely, this would need to properly handle `..' in path
        return fs.getInputStream(rootPath + "/" +  path);
    }
}

For example, imagine we wish to provide access to a subset of the file system -- only files below a given subdirectory. One possible implementation is presented in figure 2. A SubFS represents a capability to access a subtree of the file system. Hidden inside each SubFS is a FileSystem capability; the SubFS prepends a fixed string to all pathnames before accessing the hidden FileSystem. Any code which already possesses a handle to a FileSystem can create a SubFS, which can then be passed to an untrusted subsystem. Note that a SubFS can also wrap another SubFS, since SubFS implements the same interface as FS.

Also note that with the current Java class libraries, a program wishing to open a file can directly construct its own FileInputStream. To prevent this, either FileInputStream must have non- public constructors, or the FileInputStream class must be hidden from programs. This restriction would also apply to other file-related Java classes, such as RandomAccessFile. While the Java language can support capabilities in a straightforward manner, the Java runtime libraries (and all code depending on them) would require significant changes.


next up previous
Next: Second Approach: Extended Stack Up: Approaches Previous: Common Underpinnings
Dan Wallach
7/26/1997