Naccio Department of Computer Science
University of Virginia

Transforming Applications

The application transformer reads a policy description file and performs the transformations necessary to enforce a particular safety policy. In addition, it must ensure that the necessary low-level code safety properties are enforced. The application transformer is mostly platform dependent, since it deals with low-level issues of transforming an object file.

For Naccio/JavaVM, the application transformer examines an application class to determine which classes it uses, and recursively examines those classes, to determine all class dependencies. Classes that are not part of the Java API are added to the list of classes to be transformed.

The main change the application transformer must perform is to ensure the correct policy-specific library classes are used. One possibility is to do this at run-time using namespace management. [Wallach97] describes how a Java ClassLoader could be modified to use this technique to hide system classes or interpose implementations with extra security checking. Instead, we modify the class file at load time by replacing class names directly. The Java class file format makes renaming classes simple and efficient. All class names are given in the constant table found at the beginning of the class file. We replace class names of library files with the corresponding policy-specific library class name.

Renaming classes statically has the advantage over load-time renaming, in that once the application classes have been modified they can be run repeatedly without further modification. Further, it means we are not tied to a particular Java environment. The disadvantage is that we need to be careful to make sure dynamic class loading loads the correct policy-specific library classes or transformed application classes. We do this by writing platform interface wrappers for the API methods that load classes dynamically. Similarly, we use platform interface wrappers to restrict how reflection classes can be used in order to prevent direct access to methods and fields of platform library and resource implementation classes.

A further optimization is possible in the common situation where all application classes in a virtual machine are using the same safety policy. Instead of using the renamed classes, we can simply set the CLASSPATH to find the directories containing the non-renamed version of the policy-specific file (essentially doing a simplified version of namespace management). This eliminates the need to examine or alter the application class files, and means there is no load-time cost associated with Naccio at all. The only run-time cost is the overhead required to do the actual safety checking for a particular policy.

Ensuring the low-level code safety properties requires no extra work, since the Java byte code verifier guarantees the low-level code safety properties we need. By verifying the application classes before they are transformed, we can ensure that an application is not able to indirectly call the unwrapped methods or manipulate resource implementations since these actions would be detected as violations by the byte code verifier.

For the Win32 Alpha implementation, the DLL interface provides a convenient point at which to perform the redirection of platform calls to their respective policy-specific wrapper. For DLLs linked implicitly at load time, a simple change to the DLL name in an application's import table will redirect all calls to a policy-specific version of the DLL. For DLLs loaded explicitly at run-time, a wrapper for the LoadLibrary API function (which is always linked implicitly) can transparently substitute the appropriate policy-specific version of the DLL.

Naccio/Win32 must enforce the necessary low-level code safety properties to prevent self-modifying code (which could be used to jump directly to system DLL routines and avoid the safety checks) and access to protected memory (such as storage used to implement resource state). The mechanisms used to implement these protections include a limited form of Software-Based Fault Isolation [Wah93] (to ensure that all jumps remain within the code segment) and wrapper-based limitations on a program's ability to change access permissions on its memory (allowing code segments and Naccio data segments to remain read-only when untrusted code is running).

Since these protections must be applied at the level of machine instructions, the implementation of low-level code safety is thus dependent on the processor architecture being used. For our current implementation, we chose the DEC Alpha architecture, due to the public availability of tools for binary code modification on that architecture, including ATOM [Srivastava94] and SPIKE [Cohn97]. The rest of the Win32 Alpha implementation, in particular the platform interface and policy generator, is portable across different Win32 processors.

Return to Policy Generation Overview

Naccio Home Page
David Evans
University of Virginia, Computer Science