io7m-jcage 0.1.0
io7m-jcage 0.1.0 Documentation
Package Information
Orientation
Overview
The io7m-jcage package implements a run-time configurable sandboxing system using the standard JVM security components.
Installation
Source compilation
The project can be compiled and installed with Maven:
$ mvn -C clean install
Maven
Regular releases are made to the Central Repository, so it's possible to use the io7m-jcage package in your projects with the following Maven dependency:
<dependency>
  <groupId>com.io7m.jcage</groupId>
  <artifactId>io7m-jcage-core</artifactId>
  <version>0.1.0</version>
</dependency>
All io7m.com packages use Semantic Versioning [0], which implies that it is always safe to use version ranges with an exclusive upper bound equal to the next major version - the API of the package will not change in a backwards-incompatible manner before the next major version.
Platform Specific Issues
There are currently no known platform-specific issues.
License
All files distributed with the io7m-jcage package are placed under the following license:
Copyright © 2015 <code@io7m.com> http://io7m.com

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
        
Design And Implementation
Design And Implementation
JVM Security Model
The Java Virtual Machine (JVM) has a fairly rich security model that can assign sets of permissions to code. All classes loaded by class loaders in the virtual machine have associated code sources. A code source, as represented by the java.security.CodeSource type, consists of a URL that indicates from where the code was loaded, and zero or more cryptographic signatures. The JVM associates a set of Permissions, as represented by subclasses of the java.security.Permission type, with each code source. The permissions are assigned by consulting a policy, as represented by the java.security.Policy type, when a given class is loaded.
In order to actually enforce the security policy, any operation that requires privileges must check if that operation is allowed to proceed by asking the current JVM-global security manager, as represented by the java.lang.SecurityManager type. The SecurityManager is actually a historical leftover and nowadays simply delegates all requests to a JVM-global access controller, as represented by the java.security.AccessController type. All privileged operations have associated java.security.Permission values, and the job of the access controller is simply to determine if a privileged operation that requires permission P being performed by code from code source C is allowed to proceed, given the set of permissions Q granted to C by the current security policy. When an operation that requires privileges is attempted, the access controller walks the call stack and checks that each class on the call stack has the required permissions to perform the operation. If any class does not have the required permissions, a SecurityException is raised. This provides a strong security guarantee: Assume that a class C has permission to execute a privileged operation O, and a method m is defined on C that performs the privileged operation. Assume that a class D exists that does not have permission to perform O. Without the JVM's call-stack-based security checks, if code in D managed to call C.m, then it would effectively have been able to perform O without actually having permission to do so! However, with the JVM's existing call-stack-based permission checks, the JVM will walk the call stack and notice that D does not have permission to perform the operation, and raise SecurityException before the operation can proceed.
However, it is typically necessary to selectively allow privileged operations to be performed on behalf of unprivileged code. The access controller provides a standard method named doPrivileged() that allows exactly this. The details of how this works are given in the JRE documentation. Informally, extending the example above, if C.m were to wrap the privileged operation O in a call to doPrivileged(), then D (or indeed any other class) could call C.m and have the privileged operation successfully performed on their behalf. However, if D tried to perform a privileged operation by itself, even if it wrapped the operation in a call to doPrivileged(), the operation would fail with a SecurityException. Intuitively, the doPrivileged() call can be thought of as being able to temporarily grant one's own privileges to peers, but cannot be used to raise one's own level of privilege.
JCage Security
Given the above description of the security model, it becomes apparent that all one needs to do to place a piece of code in a restrictive sandbox is to assign the code a specific code source and then grant the code source a very limited set of permissions. If the JVM security components are to be trusted, then the code should be sufficiently isolated.
Unfortunately, although the JVM security components are believed to be reasonably correct, the default implementation of the java.security.Policy type is entirely limited to loading a static policy from a policy file on JVM startup, and does not allow for any kind of run-time configuration. Additionally, for historical reasons, the default implementation of the java.lang.SecurityManager type omits a few useful security checks.
The io7m-jcage package provides a user-friendly interface to a trivial run-time configurable java.security.Policy interface. In practical terms, the package places code into user-created sandboxes by loading all classes inside the sandboxes using a custom java.lang.ClassLoader implementation that assigns a code source specific to each sandbox. Each sandbox code source is given a run-time configurable set of permissions. A custom java.lang.SecurityManager re-enables the two checks that were historically omitted. Essentially, the entire package is a very thin wrapper around the existing security mechanisms.
Class Loading
Astute readers will have noticed that it would be very difficult to communicate with code loaded inside a sandbox if absolutely all of the sandboxed classes were loaded with the sandbox-specific ClassLoader. The reason for this is that the JVM effectively considers instances of the same class loaded with different class loaders to be different classes. Therefore, if a user wants to pass a value of class T to a given sandbox, then the sandbox must have access to the class as loaded by the host's class loader. The io7m-jcage package therefore allows sandbox classloaders to delegate loading to another given ClassLoader according to a user-defined policy. For example, it is possible to define a policy that loads all classes using the sandbox ClassLoader except for T, which is delegated to the user-provided ClassLoader. This allows the host and sandbox to share the same loaded version of T.
To facilitate this, each sandbox has a reference to a host classloader HCL, a host classloader policy HCP, a sandbox class resolver SCR, and a sandbox classloader policy SCP. A private sandbox classloader SC is created for the sandbox when the sandbox is created. When the sandbox needs to load a class T, it consults SCP to determine whether or not it is allowed to try to use SC to load T. If it is allowed, then the SCR resolves T and loads it using SC. The loading operation assigns T a sandbox-specific code source and the class instance is distinct from any other loaded instances of the T class in the JVM. However, if the SCP indicates that the sandbox is not allowed to load the class directly, then it attempts to delegate the operation to HCL. The HCP is consulted to see if HCL is allowed to load the class. If it is allowed, the class is loaded. If it is not allowed, a SecurityException is raised.
The end result of this procedure is that it is possible to arrange for the host and a sandbox to share the same instances of one or more loaded classes. Note that this is not a security-critical decision: Even if the sandboxed code obtains a reference to a class that can perform dangerous or privileged operations, the JVM's call-stack-based security checks ensure that the sandboxed code does not gain permissions simply by having access to a class. The class loader policies simply exist to organize class sharing between the host and a sandbox - not to control privileged operations.
Privileged Operations
It is sometimes desirable for sandboxed code to be temporarily granted privileges. For example, it could be desirable to forbid all filesystem access to sandboxed code, and then provide an API to the sandboxed code that provides an extremely limited set of privileged I/O operations. As described earlier, the JVM provides a standard mechanism for this: java.security.AccessController.doPrivileged(). The sandboxed_1/SandboxerMain examples shows how this is used. The SandboxListenerType is loaded on the host (it is automatically loaded by the system classloader simply due to being referenced by the SandboxerMain type). The sandboxed code is given access to the SandboxListenerType by the class loading policy. The onMessageReceived method performs a privileged operation that the sandboxed sandboxed_1/Sandboxed class would not otherwise be able to perform by itself, even if it attempted to call AccessController.doPrivileged() itself. The Sandboxed code also attempts to raise its own privileges and the JVM can be seen to block this operation.
JCage Insecurity
The io7m-jcage package does not protect against anything that the default JVM security model does not protect against. Specifically:
  • There is no protection against sandboxed code consuming vast quantities of CPU time and slowing the rest of the system down. This can be mitigated to some extent by running code in a separate thread and using a watchdog to detect runaway threads. This is considered to be outside of the scope of io7m-jcage.
  • There is no protection against sandboxed code consuming vast quantities of heap space. The JVM currently has no means to protect against this whatsoever.
Types
The io7m-jcage implementation consists of the following basic classes:
TypePurpose
JCClassLoader.javaLoads classes and assigns sandbox-specific code sources.
JCClassLoaderPolicyType.javaThe implementation exposed by class loader policies, for selectively delegating classloading.
JCPolicy.javaA run-time configurable implementation of the standard Policy type.
JCSecurityManager.javaA security manager implementation that re-enables the historically omitted checks
JCSandboxes.javaThe default implementation of the user-visible sandbox creation interface
Usage
Examples
NameDescription
sandboxed_0/Sandboxed.javaA sandboxed class that demonstrates an inability to performed privileged operations.
sandboxed_0/SandboxerMain.javaThe creation of a sandbox.
sandboxed_1/Sandboxed.javaA sandboxed class that demonstrates an inability to performed privileged operations, but can call a host-provided API to perform privileged operations.
sandboxed_1/SandboxerMain.javaThe creation of a sandbox that allows selective privileged operations.
API Reference
Javadoc
API documentation for the package is provided via the included Javadoc.