next up previous
Next: Third Approach: Name Space Up: Approaches Previous: First Approach: Capabilities


Second Approach: Extended Stack Introspection


This section presents an extension to Java's current stack-introspection mechanism[*]. This approach is taken by both Netscape and Microsoft in version 4.0 of their respective browsers. JavaSoft is working on a similar design [17]. Although the designs of Netscape and Microsoft differ in many ways, there is a core of similarity. Accordingly, we will first describe the common elements and then discuss the differences.

As in the other approaches, extended stack introspection uses digital signatures to match pieces of incoming byte code to principals, and a policy engine is consulted to determine which code has permission for which targets.

Basic Design

Three fundamental primitives are necessary to use extended stack introspection:

When a dangerous resource (such as the file system) needs to be protected, two steps are necessary: a target must be defined for the resource, and the system must call checkPrivilege() on the target before accessing the protected resource.

When code wishes to use the protected resource, it must first call enablePrivilege() on the target associated with that resource. This will consult the policy engine to see whether the principal of the caller is permitted to use the resource. If permitted, it will create an enabled privilege. After accessing the resource, the code will call disablePrivilege() to discard the enabled privilege.

checkPrivilege(target) searches for an enabled privilege to the given target. If one is not found, an exception is thrown.

Improved Design

The design as described so far is simple, but it requires a few refinements to make it practical. First, it is clear that enabled privileges must apply only to the thread that created them. Otherwise, an unfortunately timed thread switch could leak privileges to untrusted code.

Second, the basic design is flawed in that an enabled privilege could live forever if the programmer neglected to disable it on all paths out of the method that created it. This error is easy to make, especially in the presence of exceptions, so the design is refined to attach enabled privileges to a hidden and protected field of the stack frame of the method that created them. This would cause the privileges to be discarded automatically when the method that created them exits.

The design has one more serious weakness: it is subject to luring attacks in which trusted code that has some privileges enabled is tricked into calling into untrusted code. This would have the effect of delegating the enabled privileges to the untrusted code, which could exploit them to do damage. Calls from trusted to untrusted code are common, for example in callbacks associated with Java's system library functions such as the Abstract Windowing Toolkit.

The solution to luring attacks is to use a more restricted stack-frame searching algorithm in checkPrivilege(). This algorithm, which both Netscape and Microsoft use, is shown in figure 3. The algorithm searches the frames on the caller's stack in sequence, from newest to oldest. The search terminates, allowing access, upon finding a stack frame that has an appropriate enabled privilege. The search terminates, forbidding access (and throwing an exception), upon finding a stack frame which is forbidden by the policy engine from accessing the target.

This neatly eliminates luring attacks: untrusted code cannot exploit the privileges of its caller, because the stack search will terminate upon finding the untrusted stack frame. A callee can sometimes use the privileges of its caller, but only those privileges that the callee could have requested for itself.

We note that Netscape and Microsoft take different actions when the search reaches the end of the stack uneventfully: Netscape denies permission and Microsoft allows it. The Netscape approach follows the principle of least privilege, since it requires that privileges be explicitly enabled before they can be used. The Microsoft approach, on the other hand, may be easier for developers, since no calls to enablePrivilege() are required in the common case where independent untrusted or semi-trusted applets are using more-trusted system libraries. It also allows local, trusted Java applications to use the same JVM without modification: they run as a trusted principal so all of their accesses are allowed by default[*].

Figure 3: The stack walking algorithm used by Netscape and Microsoft.
checkPrivilege(Target target) {
    // loop, newest to oldest stack frame
    foreach stackFrame {
        if(stackFrame has enabled privilege for target) return Allow;

        if(policy engine forbids access to target by class executing 
           in stackFrame) return Forbid;
    // if we get here, we fell off the end of the stack
    if(Netscape)    return Forbid;
    if(Microsoft)   return Allow;

Example: Creating a Trusted Subsystem

 A trusted subsystem can be implemented as a class that enables some privileges before calling a system method to access the protected resource. Untrusted code would be unable to directly access the protected resource, since it would be unable to create the necessary enabled privilege. Figure 4 demonstrates how code for a trusted subsystem may be written.

Several things are notable about this example. The classes in figure 4 do not need to be signed by the system principal (the all-powerful principal whose privileges are hard-wired into the JVM). They could be signed by any trusted principal. This allows third parties to gradually extend the security of the JVM without opening the entire system to attack.

With the trusted subsystem, an applet has two ways to open a file: call through TrustedService, which will restrict it to a subset of the file system, or request UniversalFileRead privileges for itself. There are no other ways to enable privileges for the UniversalFileRead target, so there are no other ways to open a file. Note that, even should the applet try to create an instance of FS and then call getInputStream() directly, the low-level file system call (inside will still fail.

Figure 4: This example shows how to build a FileSystem capability (see figure 2) as an example of a protected subsystem using extended stack introspection. Note that figure 2's SubFS can work unmodified with this example.
// this class shows how to implement a trusted subsystem with 
// stack introspection

public class FS implements FileSystem {
    private boolean usePrivs = false;
    public FS() {
        try {
            usePrivs = true;
        } catch (ForbiddenTargetException e) {
            usePrivs = false;
    public FileInputStream getInputStream(String path) {
        // only enable privileges if they were there when we were constructed

        return new;

// this class shows how a privilege is enabled before a potentially
// dangerous operation

public class TrustedService {
    public static FileSystem getScratchSpace() {
        // SubFS class from the previous example
        return new SubFS("/tmp/TrustedService", new FS());

Figure 5: This figure shows the call stack (growing downward) for a file system access by an applet using the code in figure 4. The grey areas represent stack frames running with the UniversalFileRead privilege enabled. Note that the top system frames on the stack can run unprivileged, even though those classes have the system principal.

\includegraphics [width=3.00in]{fs-cap.eps}


  The Netscape and Microsoft implementations have many additional features beyond those described above. We will attempt to describe a few of these enhancements and features here.

``Smart Targets''

Sometimes a security decision depends not only on which resource (the file system, network, etc.) is being accessed, but on which specific part of the resource is involved, for example, on exactly which file is being accessed. Since there are too many files to create targets for each one, both Microsoft and Netscape have a form of ``smart targets'' which have internal parameters and can be queried dynamically for access decisions. Details are beyond the scope of this paper.

Who can define targets?

The Netscape and Microsoft systems both offer a set of predefined targets that represent resources that the JVM implementation wants to protect; they also offer ways to define new targets. The Microsoft system allows only fully trusted code to define new targets, while the Netscape system allows anyone to define new targets. In the Netscape system, targets are named with a (principal, string) pair, and the system requires that the principal field match the principal who signed the code that created the target. Built-in targets belong to the predefined principal System.

Allowing anyone to define new targets, as Netscape does, allows third-party library developers to define their own protected resources and use the system's stack-introspection mechanisms to protect them. The drawback is that users may be asked questions regarding targets that the Netscape designers did not know about. This makes it harder to give the user guidance. Guidance from the principal that defined the target is probably safe to use, since the defining principal is the one whose security depends on proper use of the target. Still, it may be questionable to rely on third-party developers for part of the security user interface.

Signing Details

Microsoft and Netscape also differ over their handling of digital signatures. In the Microsoft system, each bundle of code carries at most one signature[*], and the signature contains a list of targets that the signer thinks the code should be given access to. Before the code is loaded, the browser asks the policy engine whether the signer is authorized to access the requested targets; if so, the code is loaded and given the requested permissions.

In the Netscape system, code may have several signers, and signatures mention no targets. Instead, once the code is loaded, the code itself must tell the policy engine which targets it will want access to. The decision whether to grant access to those targets is made by the policy engine, based on the identities of the signers. Applets are strongly advised to request their privileges at startup, though this rule is not enforced. Failure to request privileges on startup might lead, for example, to the user working with a document-editing applet and then discovering that he cannot save his work without violating his security policy.

Since the Netscape system allows a class to be signed by several principals, there must be a way to combine the policy engine's opinions about each of the signers into a single policy decision. This is done by using a ``consensus voting'' rule. Intuitively, consensus voting means that one negative vote can force access to be forbidden, while at least one positive vote (and no negative votes) is required in order to allow access. Since the local user and site administrator may not know all of the signers, the policy engine can be told to vote ``abstain'' for an unknown signer.

Allowing multiple signers, as Netscape does, seems better than requiring a single singer. Since different users and sites may trust different signers, multiple signatures are required in order to produce a single signed object that everyone will accept. With a single-signature scheme, the server has to have multiple versions of the code (one for each signer) and must somehow figure out which version to send to which client. Also, one signer might sign a subset of the classes, creating a trusted subsystem within a potentially untrusted applet.

Putting the target-access requests in the signature, as Microsoft does, provides superior flexibility in another dimension. It allows a signature to be a partial endorsement of a piece of code. For example, a site administrator might sign an outside applet, saying (in effect), ``The local security policy allows this applet to access outside network addresses, but nothing more.''

Many other details of the Microsoft and Netscape designs are omitted here for brevity.

next up previous
Dan Wallach