Menu

Java-Sandbox 0.3 Documentation Log in to Edit

Thorsten J. Krause

Java-Sandbox 0.3 Documentation

License

The java-sandbox is open source. The java-sandbox is available under the [LGPL 3.0].

Download

New in Version 0.3

In this version we have added support for the [reflective-sandcastle library (version 0.1)]. The reflective-sandcastle is an enhancement to java's permission checking when using reflection. Using the reflective-sandcastle together with the sandbox better protects against breakout attempts via reflection.

New in Version 0.2

In version 0.2 we have added many new features to the sandbox the most prominent of which are the wrapping of sandboxes into threads and running sandboxes on remote jvm agents (i.e., executing the sandboxed code on a completely separated java virtual machine).

Other new features include:

  • Automatic removal of finalizers
  • Subloaders to implement the double loader pattern (see below)
  • Allowing to run the java-sandbox in conjunction with the traditional codebased SecurityManager 
  • Protection against wild running scripts (such as infinite loops)

Structure of this Documentation

We start with a basic description on how the java-sandbox works. We then go through the various possibilities of creating sandboxes step by step. Besides this documentatin have a look at the [API documentation] and the accompanying tutorials:

How the Java Sandbox works - The basics

Before we get into using the java-sandbox we need some background on how the java-sandbox works. For this it helps to understand the basics of java's SecurityManager and permission concept but it is not really necessary. However, to properly use the java-sandbox some knowledge about class loading in java is important.

The java-sandbox basically consists of two components (an implementation of SecurityManager and a custom ClassLoader called SandboxLoader) and a service class which allows to access the functionality. A sandbox can be initiated only using the security manager or using the classloader together with the security manager. We call the refer to the first possibility (security manager only) as simple sandbox and to the second way as regular sandbox or simply sandbox. We will briefly describe the difference between the two modes. For further information on how the java sandbox works internally have a look at the examples and explanations further down.
Assume service is an instance of the java-sandbox's service class. Then the easiest way to create a simple sandbox is to call

String pw = service.restrict(context);
try{
    /* put untrusted code here */
} finally {
    service.releaseRestriction(pw);
}

With this setup only the security manager is enabled to supervise the execution of the untrusted code. The security manager is asked before certain system resources can be used or, for example, whether or not the System.exit() may be called (more information on the capabilities of security managers and the permission concept can be found here, and here). The context object of type SandboxContext allows to configure which permissions are given.
One problem with the above setup is that it is hardly possible to restrict access to classes and packages. Although security managers have a concept of being asked whether or not classes from certain packages may be loaded the security manager is asked exactly once by the classloader when loading the class. Thus, if a package has been accessed outside the sandbox it is also cleared for use within the sandbox (at least in the eyes of the corresponding classloader).  
To solve this problem we load the untrusted code with a custom classloader. The java-sandbox provides several easily accessible methods to do this. The most common is probably to run the untrusted code within a SandboxedEnvironment object (which is similar to a standard Callable object).

SandboxedEnvironment<Object> c = new SandboxedEnvironment<Object>() {
   @Override
   public Object execute() {
      /* untrusted code */
      return null;
    }
};

service.runSandboxed(c, context);

The runSandboxed methods accept a SandboxedEnvironment and a SandboxContext as input. The context defines the permissions and configuration of the sandbox. To securely execute the code wrapped in the environment the environment class is reloaded with a custom classloader. It then creates a sandbox and calls the environment's execute method. By loading the environment in a custom classloader we have full control over which classes are loaded. The custom classloader also not only informs the security manager on packages but on classes that are to be loaded thus allowing to fine tune the sandbox to the specific needs. 

Usage

In the following we give several examples of how to use the java-sandbox. For further information also see the [API-specification] and tutorials.

Basics

The first step is to enable the sandbox service and install the security manager. For this it is sufficient to instantiate the SandboxService

SandboxService service = new SandboxServiceImpl();

located in package net.datenwerke.sandbox. This will install the security manager with default parameters. Note that the service should be used as a singleton. You can also access the service by calling its static method getInstance(). By default the sandbox service starts two so called remote agents, that is two extra java virtual machines that can be used for remote sandboxing. To not create these processes either provide the necessary parameters to the constructor or use the static initialization method

SandboxServiceImpl.initLocalSandboxService();

The SandboxService object provides access to all necessary functionality. To create a simple sandbox (that is one which uses only the security manager and not a custom class loader) it provides methods restrict() and releaseRestriction(String). Restriction generates (or takes) a password, which is needed to, again, deactivate the sandbox. To create proper sandboxes the SandboxService provides the methods runSandboxed() methods.  To only use the SandboxLoader (the sandbox's class loader) but not to directly invoke the sandbox use the methods runInContext().

Configuring the Sandbox

The entire configuration of a sandbox is given by a SandboxContext object. For the configuration we distinguish several parts:

  1. Class-level permissions
  2. Package-level permissions
  3. Filesystem permissions
  4. Security permissions
  5. ClassLoader guidance
  6. Sandbox specific options

We will discuss these in turn. Some advanced options are later touched upon in the examples. Also have a look at the [API of SandboxContext.]
Class-level Permissions
Via class-level permissions you can control which classes can be accessed from within the sandbox. Note that these checks are only performed if the sandbox is run with the SandboxLoader class loader, which is, for example, the case if you construct the sandbox via the runSandboxed() commands.

You can either allow access to classes or deny class access. Whenever checks are performed it is first evaluated if a specific permission is specifically denied before it is checked whether any of the rules might grant access. Thus, it is possible to allow access to all classes in java.lang while, for example, denying access to java.lang.System. To configure class-level access use the following methods:

  • addClassPermission(AccessType, Mode, String)  The AccessType defines if this is added to the whitelist or blacklist. The mode defines if the class is matched in normal or prefix mode and the final parameter is the classname. In normal mode the string is either added to the whitelist/blacklist or if it ends with a "." it is added the the prefix list.
  • addClassPermission(ClassPermission)
    Allows to add classes to the whitelist which include stacktrace checks. We will shortly see how this works.
  • addJarToWhitelist(URL url)
    Takes a URL that points to a jar. The SandboxLoader will attempt to load classes from this jar and will allow access to any class from this jar.

To allow access to any class you can set to bypass class access checks by setting setBypassClassAccessChecks(). The default is to enable class level checking.

Complex Permissions including Stack Traces

Permission objects such as ClassPermission or SecurityPermission can be configured to consider the current stack trace. Thus, it is possible to define permissions such as: class foo may be accessed if the caller stack at position 4 is class bar. For this, the permission objects take a StackEntry object which is instantiated as

new StackEntry(int pos, String type, boolean prefix)

where pos is a position in the stack trace (set this to -1 to denote any position in the stack trace), type is the class and prefix denotes whether it is sufficient that the class in the stack trace is a prefix of the defined type or if they have to match exactly. If multiple StackEntries are passed to a permission object than the permission is granted if and only if all StackEntries validate.

Package-level permissions

Package-level restrictions are defined analogously to class-level restrictions. Note however, that we do not recommend to use package-level.

To allow access to any package you can set to bypass class access checks by setting setBypassPackageAccessChecks(). The default is to disable package level checking (we advocate the use of class level checks).

Filesystem permissions

Filesystem permissions are configured using the FilePermission interface. There are 4 default implementations: FileEqualsPermissions, FilePrefixPermission, FileRegexPermission and FileSuffixPermission which are given a name and test if a given file(name) is equal, has the same prefix or suffix or whether it matches the regular expression. Use the addFilePermission method of SandboxContext to add a permission to the whitelist (AccessType.PERMIT) or blacklist (AccessType.DENY).

If class access is restricted, the class loader should be given read access for objects on the classpath (otherwise it cannot load any class). For this you can use the convenience method addClasspath(). Further convenience methods are

  • addClasspath()
    allows read access to files on the classpath
  • addHome()_allows read access to files in java's home directory (java.home)_
  • addTempDir()
    allows read, write and delete access to the temp dir specified in the system property "java.io.tmpdir"
  • addWorkDir()_Allows read access in the user's home directory (user.dir)_

Security permissions

Via general security permissions you can blacklist or whitelist requests for java.security.Permissions. These are made by (trusted) code whenever critical resources are requested (for example, the method getClassLoader on a Class object will ask the security manager if the "java.lang.RuntimePermission" "createClassLoader" is granted. You can find an introduction here. To grant (or deny) permissions use the addSecurtiyPermission() method in SandboxContext.

To grant access for any permission (this includes FilePermissions) you can set setBypassPermissionAccessChecks(). The default is to enable permission checking. 

ClassLoader Guidance

As we explained above it is crucial that the untrusted code is loaded in a special class loader, this loader being the [SandboxLoader]. From here on, any classes are loaded by the SandboxLoader (with the exception of classes in java.). In some instances you might, however, need to load classes with your regular class loader. To tell the SandboxLoader not to load certain classes directly, but rather use its parent class loader use the methods

  • addClassForApplicationLoader(String clazz, Mode mode)
    Tells the SandboxLoader to load the class via the parent class loader. If clazz ends with "." or if mode is set to Mode.PREFIX the SandboxLoader loads all classes with the given prefix using its parent.
  • addJarForApplicationLoader(URL url)
    Tells the SandboxLoader to load all classes that are in the jar at location url with the parent class loader.

To override behavior defined with the above methods you can use the addClassForSandboxLoader(String, Mode) method.

Besides telling the SandboxLoader which class loader to use for loading classes you have the following options:

  • To remove finalize methods (see discussion below) set setRemoveFinalizers()
  • To tap into the class loading process set a [SandboxLoaderEnhancer] via setLoaderEnhancer(SandboxLoaderEnhancer loaderEnhancer)
  • You can specify to dynamically create sub-classloaders when loading specific packages. We'll discuss this in more detail when discussing the double loader pattern.

Sandbox Specific Options

When generating proper sandboxes (that is using a custom classloader) via the SandboxService's methods [runSandboxed] and [runInContext] you can specify the following options.

  • setRunInThread()
    Wraps the execution of the sandboxed code in an own thread. This thread is, furthermore, monitored and you can specify a maximum runtime and a maximum stack-depth using

  • setMaximumRunTime(long)

  • setMaximumStackDepth(int)

  • setRunRemote()
    Tells the SandboxService to execute the sandboxed code on a separate java virtual machine. See treatment of remote sandboxes below.

Debugging

To debug you can set the SandboxContext into debug mode using the setDebug() method. Java-sandbox uses the standard java.util.logging framework to output debug information.

Examples

Enabling the Sandbox Service

Before you can use any sandbox you need to enable the sandbox service. For this it is sufficient to simply load an instance of SandboxServiceImpl. Note that this service should be used as a singleton

SandboxService sandboxService = SandboxServiceImpl.getInstance();

This will install the security manager and from now on, you can use the sandbox services. If you are not planning to use remote agents (see documentation of remote sandboxes below) you should initialize the sandbox service using

SandboxServiceImpl.initLocalSandboxService();

Note that this is equivalent to

new SandboxServiceImpl(true, 
   new SandboxCleanupServiceImpl(), 
   null);

Invoking a Simple Sandbox without a custom ClassLoader

To invoke a simple sandbox without a custom class loader (note that in this case the sandbox will only track security permissions see above) it is sufficient to create a context and call the restrict method on the SandboxService object:

SandboxContext context = new SandboxContext();

String password = null;
try {
 password = sandboxService.restrict(context);

 /* untrusted code goes here */

} finally {
 sandboxService.releaseRestriction(password);
}

Invoking the Sandbox with a custom ClassLoader

To invoke a proper sandbox using the SandboxLoader as class loader we need to wrap the untrusted code into an object, such that it can be loaded by the SandboxLoader. For this the sandbox service class offers convenience methods that allow you to pass a SandboxedEnvironment object (which is essentially something like a Callable object) which is loaded into the SandboxClassloader and then executed.

SandboxedEnvironment<Object> c = new SandboxedEnvironment<Object>() {
    @Override
    public Object execute() throws Exception {
       /* run untrusted code */

       /* return some value */
       return null;
    }
};

SandboxContext context = new SandboxContext();
SandboxedCallResult<Object> result = sandboxService.runSandboxed(c.getClass(), context);

As you can see the runSandboxed command returns an object of type [SandboxedCallResult] which is a wrapper around the object returned by the environment. Note that the SandboxedEnvironment runs inside the SandboxLoader while the calling code usually is loaded in a different class loader. Thus, the result of the computation is necessarily also in the scope of the SandboxLoader and not of the outside application class loader. The SandboxCallResult object allows you to bridge between the two class loaders. That is, it provides the following methods:

  • get()
    returns the wrapped object in the scope of the classloader that has loaded the SandboxCallResult object. This is usually the classloader that called the runSandboxed method.
  • getRaw()
    returns the raw object in the scope of the SandboxLoader.

Let us note that the cloning of objects from one classloader to the other can fail for complicated object graphs. You should thus try to ensure that the return value is of a rather simple type.

Passing values to the SandboxedEnvironment

Often you might want to pass values into the SandboxedEnvironment. There are several ways to do this, which we will explain now. One option is that the code from within the SandboxedEnvironment accesses member variables from the outer class. This is the case in this example:

public class ExampleClass {

  public String myValue = "This is some value";

  public void run(){
    SandboxService sandboxService = SandboxServiceImpl.getInstance();

    SandboxedEnvironment<String> c = new SandboxedEnvironment<String>() {
        @Override
        public String execute() throws Exception {
           /* run untrusted code */
           System.out.println(myValue);

           /* return some value */
          return "This is a different value";
        }
    };

    /* configure context */
    SandboxContext context = new SandboxContext();
    context.addClassForApplicationLoader(getClass().getName());
    context.addClassPermission(AccessType.PERMIT, "java.lang.System");
    context.addClassPermission(AccessType.PERMIT, "java.io.PrintStream");

    /* run code in sandbox */
    SandboxedCallResult<String> result = sandboxService.runSandboxed(c.getClass(), context, this);

    /* output result */
    System.out.println(result.get());
  }

}

Let us go through the example step by step. We have a class ExampleClass which executes the sandboxed code within its run method. We call the classloader which loaded ExampleClass the application class loader. The example looks pretty similar to the previous example except that the SandboxedEnvironment object accesses the member variable myValue from the surrounding ExampleClass instance. To understand what happens, you need to understand how java internally compiles anonymous classes that access outside variables. For this java generates a constructor for the SandboxedEnvironment object which expects a single parameter of type ExampleClass. To ensure that this parameter is set, when the SandboxedEnvironment object is loaded and instantiated by the sandbox service we need to supply the sandbox service with the necessary object. This is done in the runSandboxed call. The last parameters (if specified) are passed to the constructor.

One problem, however, is that the SandboxedEnvironment attempts to load the class ExampleClass and as itself is loaded in the SandboxLoader it asks the SandboxLoader to load it. When we pass the outer instance to the constructor the outer instance was loaded using the application loader. As two classes are only equivalent if they are loaded by the very same classloader java would complain here, saying something like

argument type mismatch: Could not instantiate the Callable obect. Is there a classloader problem? The Callable's constructor expects post.Example$1(post.Example) but I got: [post.Example@3dee2310, ]

To avoid this, we need to make sure, that ExampleClass is always loaded by the application loader. For this, we configure our SandboxContext to load the ExampleClass using the application loader:

    context.addClassForApplicationLoader(getClass().getName());

As we access System.out.println from within the sandbox we also need to allow class access to java.lang.System as well as java.io.PrintStream.

Private Member Variables

Note that it is not possible to access non-public member variables via this method.

A Custom SandboxedEnvironment Class

A second, more flexible, and usually preferred approach is to use a custom class for the environment. That is, not an anonymous class, but a defined class with a defined constructor. Assume we have the following SandboxedEnvironment class:

public class MyEnvironment implements SandboxedEnvironment<String> {

    private final String myValue;

    public MyEnvironment(String myValue){
       this.myValue = myValue;
    }

    @Override
    public String execute() throws Exception {
       /* run untrusted code */
       System.out.println(myValue);

       /* return some value */
       return "This is a different value";
    } 
}

Then we invoke sandbox the sandbox using the following code:

SandboxService sandboxService = SandboxServiceImpl.initLocalSandboxService();

/* configure context */
SandboxContext context = new SandboxContext();
context.addClassPermission(AccessType.PERMIT, "java.lang.System");
context.addClassPermission(AccessType.PERMIT, "java.io.PrintStream");

/* run code in sandbox */
SandboxedCallResult<String> result = sandboxService.runSandboxed(MyEnvironment.class, context, "This is some value");

/* output result */
System.out.println(result.get());

Running the sandbox in its own Thread

To properly protect against malicious code we want to run the sandbox in an extra thread. This allows us to better monitor what the untrusted code is doing. To enable that a custom thread is generated in which the SandboxedEnvironment is executed simply set the runInThread() method on the SandboxContext object.

Assume we have a malicious environment such as the following:

public class InfiniteLoopEnvironment implements SandboxedEnvironment<Object> {

    @Override
    public Object execute() throws Exception {
       while(true){
           try{
              Thread.sleep(1000);
           } catch(InterruptedException e){}
       }
    }

}

Calling the execute method would block the calling thread indefinitely.  Using the option to run this in a threaded sandbox allows us to kill the thread if necessary. Consider the following calling code.

SandboxService sandboxService = SandboxServiceImpl.initLocalSandboxService();

/* configure context */
SandboxContext context = new SandboxContext();
context.setRunInThread(true);
context.setMaximumRunTime(2, TimeUnit.SECONDS, RuntimeMode.ABSOLUTE_TIME);

/* run code in sandbox */
SandboxedCallResult<String> result = sandboxService.runInContext(InfiniteLoopEnvironment.class, context);

We have configured the sandbox to run in a thread and that the maximum runtime should not exceed 2 seconds. The runInContext method works exactly as the runSandboxed method except that the securitymanager is not immediately activated (this could for example be part of the environments task). If we run the above code we get the following exception

Exception in thread "main" net.datenwerke.sandbox.exception.SandboxedTaskKilledException: killed task as maxmimum runtime was exceeded at net.datenwerke.sandbox.SandboxMonitorDaemon.testRuntime(SandboxMonitorDaemon.java:82)    at net.datenwerke.sandbox.SandboxMonitorDaemon.run(SandboxMonitorDaemon.java:57)
    at java.lang.Thread.run(Thread.java:722)

When reading this, you should ask the question: how does the java-sandbox library kill the wild running thread. For this we use the deprecated Thread.stop(). This method can potentially be problematic which is why it was deprecated in the first place. In a nutshell the problem is that when the thread is killed any locks held by the thread are immediately released. This in turn can lead to objects being corrupted (if for example the killed thread was not yet finished initializing an object that is shared between multiple threads). However, sandboxed code will in many cases not be allowed to access shared objects. In these instances it should be safe to stop threads the hard way. To provide more control about what is going on, the sandbox service analyzes the thread on stopping and informs you if the thread held any locks. If this is not the case we assume it safe to kill the thread.

Consider the following adaption of the above environment.

public class InfiniteLoopEnvironment implements SandboxedEnvironment<Object> {

    @Override
    public Object execute() throws Exception {
       synchronized (this) {
            while(true){
               try{
                    Thread.sleep(1000);
               } catch(InterruptedException e){}
            }
       }
    }

}

Now if run the following calling code:

SandboxService sandboxService = SandboxServiceImpl.initLocalSandboxService();

sandboxService.attachHandler(new BadThreadKillHandler() {
    @Override
    public void badThreadKilled(BadKillInfo killInfo) {
        System.out.println("bad thread was killed");
    }
});

/* configure context */
SandboxContext context = new SandboxContext();
context.setRunInThread(true);
context.setMaximumRunTime(2, TimeUnit.SECONDS, RuntimeMode.ABSOLUTE_TIME);

/* run code in sandbox */
SandboxedCallResult <string>result = sandboxService.runInContext(InfiniteLoopEnvironment.class, context);</string> 

we are informed about that a thread still holding locks was stopped and might take action.

A possible way to deal with this issue is to run sandboxed code in an extra process and not just in an extra thread.

Sandboxing and Remote Agents

By default the sandboxing service starts two extra java virtual machines. One which is used as a so called freelancer (we get to this) and the other to execute sandboxed code transparently. To run a SandboxEnvironment on a remote agent, simply configure the SandboxContext using the setRunRemote method. Consider the following environment:

public class RemoteEnvironment implements SandboxedEnvironment <string>{
    @Override
    public String execute() throws Exception {
        return ManagementFactory.getRuntimeMXBean().getName();
    }
}</string> 

That is, the environment simply outputs the process name. Let us first run this code in its own thread but on the same machine:

SandboxService sandboxService = SandboxServiceImpl.getInstance();

/* configure context */
SandboxContext context = new SandboxContext();
context.setRunInThread(true);

/* run code in sandbox */
SandboxedCallResult <string>result = sandboxService.runInContext(RemoteEnvironment.class, context);

System.out.println(ManagementFactory.getRuntimeMXBean().getName());
System.out.println(result.get());</string> 

On my machine the output would look like

Jun 12, 2013 7:33:21 PM net.datenwerke.sandbox.jvm.server.SandboxJvmServer <init>
INFO: started sandbox server: SandboxRemoteServerNr1
Jun 12, 2013 7:33:22 PM net.datenwerke.sandbox.jvm.server.SandboxJvmServer <init>
INFO: started sandbox server: SandboxRemoteServerNr2
30020@AMBPro.local
30020@AMBPro.local

The first two info messages tell me that two remote agents were initialized. But the process id of the calling code and that of the sandbox are the same. If we now add the following line to the context definition:

context.setRunRemote(true);

The output changes to

31134@AMBPro.local
31136@AMBPro.local

indicating that the sandbox was actually executed on a different process.

Parameter Passing to Remote Agents

The communication between the main java process and the remote agent is implemented using RMI.  Thus, we can easily pass parameters into our SandboxEnvironment as before, with the only exception that the parameters need to implement the Serializable interface.

Consider the following environment:

public class RemoteEnvironment implements SandboxedEnvironment<String> {

    private String value;

    public RemoteEnvironment(String value){
       this.value = value;
    }

    @Override
    public String execute() throws Exception {
        return "I have been given value: " + value;
    }

}

If we called this using the following setup

SandboxService sandboxService = SandboxServiceImpl.getInstance();

/* configure context */
SandboxContext context = new SandboxContext();
context.setRunInThread(true);
context.setRunRemote(true);

/* run code in sandbox */
SandboxedCallResult <string>result = sandboxService.runInContext(RemoteEnvironment.class, context, "Let's pass a value");
System.out.println(result.get());</string> 

we would get the following output

Jun 12, 2013 7:50:20 PM net.datenwerke.sandbox.jvm.server.SandboxJvmServer <init>
INFO: started sandbox server: SandboxRemoteServerNr1
Jun 12, 2013 7:50:21 PM net.datenwerke.sandbox.jvm.server.SandboxJvmServer <init>
INFO: started sandbox server: SandboxRemoteServerNr2
I have been given value: Let's pass a value

JvmFreelancers

When invoking many small remote sandboxes the performance bottleneck is the creation of the class loader on the remote agent. To cache this class loader you can use what we coined [JvmFreelancers]. Lets say we have a SandboxContext and want to reuse this object for many invocations. If we changed the above calling code to

SandboxService sandboxService = SandboxServiceImpl.getInstance();

/* configure context */
SandboxContext context = new SandboxContext();
context.setRunInThread(true);
context.setRunRemote(true);

/* run code in sandbox */
long l = System.currentTimeMillis();
for(int i = 0; i < 1000; i++)
    sandboxService.runInContext(RemoteEnvironment.class, context, "Let's pass a value");
System.out.println("Time: " + (System.currentTimeMillis() - l));

I'll get an output like "Time: 3218" on my machine. If we use a freelancer, this can be drastically optimized:

SandboxService sandboxService = SandboxServiceImpl.getInstance();

/* configure context */
SandboxContext context = new SandboxContext();
context.setRunInThread(true);
context.setRunRemote(true);
context.addClassForApplicationLoader("sun.reflect.");

long l = System.currentTimeMillis();
JvmFreelancer freelancer = sandboxService.acquireFreelancer();
freelancer.init(context);
try{
    for(int i = 0; i < 1000; i++){
 freelancer.runInContext(RemoteEnvironment.class, "pass over some value");
    }
} finally {
    sandboxService.releaseFreelancer(freelancer);
}
System.out.println("Time: " + (System.currentTimeMillis() - l));

Now I get a run-time of 1092ms. If the sandbox needs to load more classes the speedup increases even more.

Note two things about the above code. First, the freelancer should be released to the sandboxService, after it isn't needed anymore. Seconde, we added

context.addClassForApplicationLoader("sun.reflect.");

to tell the SandboxLoader to load any class within the sun.reflect. packages with the system class loader. I haven't really figured out when reflection makes use of classes in sun.reflect, but for some reason after the 15th iteration of the above loop it does and complains badly that it is not loaded from the system class loader.

Reflection and the Reflective Sandcastle

Reflection is the enemy of the sandbox. If a user were able to get his or her hands on, for example, the SandboxSecurityManager then it could easily escape the sandbox. Consider the following code snippet:

Object manager = System.getSecurityManager();

Class clazz = manager.getClass();
Field f = clazz.getDeclaredField("restrict");
f.setAccessible(true);

Object tl = f.get(manager);
Class tlClazz = tl.getClass();
Method setMethod = tlClazz.getMethod("set", Object.class);

setMethod.invoke(tl,new Object[]{ null });

System.out.println("I've broken out!\n");

If we run this code inside a sandbox the only two permissions that are checked are:

PermissionCheck: ("java.lang.RuntimePermission" "accessDeclaredMembers")
PermissionCheck: ("java.lang.reflect.ReflectPermission" "suppressAccessChecks") 

The problem is, that to not permit reflection is often not possible. Take groovy as example. Groovy handles most of its logic via reflection. So how can we protect against this attack? We could try to deny access to the System class. But that might also not work since some code might need it. We can also not deny access to the SandboxSecurityManager class, because it is not even loaded in this context.

The [reflective sandcastle] is a simple extension to the permission checks done by java when encountering reflection. It allows for permission checking at class level and thus, for example, to allow reflection in general, but not for classes SandboxLoader and SandboxSecurityManager. In fact, the SandboxContext is by default configured such that these two classes cannot be accessed via reflection (if the reflective sandcastle is enabled).

To enable the reflective sandcastle simply download the library and add it to the boot classpath:

-Xbootclasspath/p:/PATH-TO/reflective-sandcastle-java7-0.1.jar

You will find more information in the [documention of the reflective sandcastle].


Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.