instantreality 1.0

Breaking out of the Java Sandbox

Keywords:
Script, Java, Security, AccessControlException
Author(s): Patrick Dähne
Date: 2009-01-08

Summary: Implementing Script nodes Java gives you the full power of the standard Java library. You can read and write files, communicate with other software components over the network, and much more. Unfortunately, for security reasons, all Java code is executed in a sandbox, i.e. it is not allowed to do any potentially hazardous operation. This tutorial explains how to break out of this sandbox.

Problem Statement

We'll start with a simple example. We want to write a VRML scene that displays the name of the user. Our scene simply consists of a Shape with a Text node and the Script node that loads our Java code ("Sandbox.class"). The Script node has a SFNode field "text" that holds a reference to the Text node. In classic VRML encoding, our scene looks like this:

Code: Test scene (VRML encoding)

#VRML V2.0 utf8

Shape
{
  geometry DEF text Text
  {
    string [ "Username:" "???" ]
    fontStyle FontStyle
    {
      justify [ "MIDDLE" "MIDDLE"]
    } # FontStyle
  } # Text
} # Shape

DEF script Script
{
  field SFNode text USE text
  url "Sandbox.class"
} # Script

In XML encoding, the same scene looks like this:

Code: Test scene (XML encoding)

<?xml version="1.0" encoding="UTF-8"?>
<X3D profile='Full' version='3.0'>
  <Scene>

    <Shape>
      <Text DEF='text' string='"Username:" "???"'>
        <FontStyle justify='"MIDDLE" "MIDDLE"'/>
      </Text>
    </Shape>

    <Script DEF='script' url='"Sandbox.class"'>
      <field accessType='initializeOnly' name='text' type='SFNode'>
        <Text USE='text'/>
      </field>
    </Script>

  </Scene>
</X3D>

The default user name in our VRML scene is "???". In our Java code, we'll replace that default name when the scene starts running with the actual name of the user. So we implement a class called "Sandbox" that inherits from "vrml.node.Script" (as required for Java in Script nodes) and overwrite the "initialize()" method. In this method, we get the Text node from the "text" field of the Script node, get its "string" field, and replace the second entry of that MFString field with the user name we get from the Java system property "user.name":

Code: Java implementation of the Script node

import vrml.*;
import vrml.field.*;
import vrml.node.*;

public class Sandbox extends vrml.node.Script
{
  // This method gets called when loading the scene
  public void initialize()
  {
    // Get the Text node from "text" field of the Script node
    SFNode textField = (SFNode)getField("text");
    Node textNode = (Node)textField.getValue();

    // Get the "string" exposed field of the Text node
    MFString stringField = (MFString)textNode.getExposedField("string");

    // Get the name of the user. This is a privileged method call that
    // will throw an AccessControlException when we do not give this
    // script the right to read that property.
    String username = System.getProperty("user.name");

    // Write the user name into the "string" field of the Text node
    stringField.set1Value(1, username);
  }
}

Ok, after compiling our Java code, we start our test scene and get the following result:

Image: Failed scene

To our surprise, we still see the default name "???" instead of the actual user name. So what went wrong?

Whenever something goes wrong, you should have a look into the console. You'll get it when you click onto the "Console" item in the "Window" menu. When we do that after loading our test scene, we'll see that we've got an AccessControlException:

Code: Error message in the console

Exception in thread "main" java.security.AccessControlException: access denied (java.util.PropertyPermission user.name read)
	at java.security.AccessControlContext.checkPermission(AccessControlContext.java:269)
	at java.security.AccessController.checkPermission(AccessController.java:401)
	at java.lang.SecurityManager.checkPermission(SecurityManager.java:524)
	at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1276)
	at java.lang.System.getProperty(System.java:573)
	at Sandbox.initialize(Sandbox.java:20)

This exception tells us that our Java code in line 20 of "Sandbox.java" failed when reading the property "user.name" because access to this property is denied.

The reason for this behaviour is that your code for security reasons is running inside a restriced Java sandbox, just like a Java applet in a web browser. Anything that is considered potentially harmful is forbidden inside that sandbox. Harmful operations for example are reading files, opening sockets, and in our case getting the name of the user. So whenever your code throws an AccessControlException, you know that you tried to do something forbidden.

So the situation is as follows: With Java, we can implement much more powerful Script nodes than with JavaScript. But due to the restrictions, we cannot actually do much useful. We are trapped inside a sandbox with no chance to interact with the environment. Obviously that's quite unimpressive. But fortunately the inventors of Java knew about that and implemented a way for the user to grant additional rights to code running in a Java sandbox. In the next section, you'll learn how to do that.

The Policy Tool

Whenever you want do perform a privileged operation in a Java sandbox, you have to give your code the right to do that. To grant additional rights to Java classes, you have to create a new or edit an existing file called ".java.policy". This file is located in your home directory on Unix systems (Linux and Mac OS X), in the directory "C:\Documents and Settings\<username>\" on an english Windows 2000/XP and in "C:\Users\<username>\" on Vista, where you replace "<username>" by your actual username/account. Keep in mind that on Unix, files whose names start with a dot (as in the case of ".java.policy") are hidden and not visible in file explorers.

".java.policy" is a (UTF8-encoded) text file, so you can edit it by using a text editor. But I strongly recommend to use a small software called "policytool" that is part of the Java Runtime Environment (JRE). On Unix systems, you'll usually find it under "/usr/bin/policytool". Simply open a terminal ("/Applications/Utilities/Terminal" on Mac OS X) and enter "policytool" on the command line, it should be in the search path. On an english Windows, you'll find it under "C:\Program Files\Java\jre*\bin\policytool" when you installed the JRE into the default location (where "*" stands for the version number). When you're successfull, you'll get the main policytool window. When ".java.policy" already exists, policytool will automatically load that file, otherwise you'll get an empty window like this:

Image: The main policytool window

After starting the policy tool and loading an existing policy file if it exists, you first have to specify the URL of your Java class. Click on the button "Add Policy Entry". You'll get a dialog window that looks like this:

Image: The Policy Entry dialog

Enter the URL into the text field "CodeBase". The exact meaning of the URL depends on the characters at the end. An URL with a trailing "/" matches all class files (not JAR files) in the specified directory. An URL with a trailing "/*" matches all files (both class and JAR files) contained in that directory. An URL with a trailing "/-" matches all files (both class and JAR files) in the directory and recursively all files in subdirectories contained in that directory. So in my case, the example scene for the tutorial (more precisely, the compiled Java byte code "Sandbox.class") is located in "C:\Users\pdaehne", so I have to enter the URL "file:/C:/Users/pdaehne/". Note that you have to specify an URL (starting with "file:"), not only the path. On Windows, make sure to use slashes ("/") instead of backslashes ("\"). When you're using file URLs frequently, you'll notice that the file URL is actually syntactically wrong (file URLs should start with "file:///"), but it has to be this way. Note also that you can leave the code base field empty - an empty URL means "grant the rights to all Java classes", which is quite dangerous.

After specifying the code base, you have to specify which rights you grant to the Java classes specified by the code base. Click on the button "Add Permission". You'll get yet another dialog window where you can click onto three drop-down-menus to specify the permission you grant to the classes. All these permissions are quite confusing, and explaining all settings is out of scope of this tutorial - have a look into the Java documentation. For now, we'll simply use the special permission "AllPermission" to give the code all permissions, i.e. to completely disable the sandbox for our example code. Usually, you should not do that, because it's quite dangerous - you should only grant the rights absolutely necessary. So the resulting settings should look like this:

Image: Granting all permissions

When you're finished, click onto "OK" to close the Permissions dialog. You'll get back to the Policy Entry dialog which should look like this:

Image: The Policy Entry dialog with all settings

Leave the Policy Entry dialog by clicking onto the "Done" button. You'll get back to the main window. Now save the settings. When you already had a ".java.policy" file, you can simply click onto the "Save" menu item in the "File" menu. Otherwise, you have to click onto the "Save As" menu item in the "File" menu and specify the correct location and file name. After saving, your main window should look like this:

Image: The main window after saving our settings

Now we're ready to load our test scene. And - voilà! It should print the user name.

Image: Working scene

Ok, now you know how to grant rights to code on your local hard disk. But what about VRML worlds downloaded from the Internet? Again, open the poliytool.

Let's say you can download our example scene from "http://www.instantreality.org/tutorial/breaking-out-of-the-java-sandbox/Sandbox.wrl". In the main window, click on "Add Policy Entry" and enter the URL "http://www.instantreality.org/tutorial/breaking-out-of-the-java-sandbox/" into the "CodeBase" field. Then, click on "Add Permission".

This time, we are a little bit concerned if the code downloaded from the Internet is really trustworthy, so we won't give it all permissions, just the permission to read the property "user.name". So in the first drop-down menu, we select "PropertyPermission" which is the permission needed for reading or writing properties. Then we have to enter the name of the property we want to access ("user.name") into the second edit field. And finally, we have to select "read" from the third drop-down menu, because we want to read the property. After doing that, the dialog looks like this:

Image: Granting the permission to read the property "user.name"

Now we close the Permissions dialog by clicking onto "OK". The Policy Entry dialog looks like this:

Image: The Policy Entry dialog for the HTTP code base

Leave the Policy Entry dialog by clicking onto "Done", and save the settings in the main window. The main window should look like this now:

Image: The main window with the HTTP code base

When you now try to open the scene from the Internet, it should again display the correct user name.

That already concludes this tutorial. Keep in mind:

  • By default, your code has the right to read all files that are located in the same directory as the class file that contains your code.
  • You do not only have to grant rights to your own code, but also to code that gets called by your code. This especially means that you explicitly have to grant rights to JARs used by your code. The standard Java packages (“java.*”) contained in the Java runtime by default have all permissions. Non-standard packages like “javax.*” do not have any permissions. Instead of granting permissions to other code called by your code, you can also use so-called “Privileged Blocks”. Have a look into the Java documentation to see how to use privileged blocks.

As mentioned before, all of this is not Instant Player-specific, instead it is a standard approach to grant permissions to Java code running in a sandbox, and there is a lot of information available in the Java documentation about this topic.

Information about the policy tool is available under the following URL:

http://java.sun.com/javase/6/docs/technotes/guides/security/PolicyGuide.html

Information about permissions is available under the following URL:

http://java.sun.com/javase/6/docs/technotes/guides/security/permissions.html

Information about privileged blocks is available under the following URL:

http://java.sun.com/javase/6/docs/technotes/guides/security/doprivileged.html

Unfortunately, the location of these documents changes frequently.

Example Files

The test scene is available in classic VRML encoding ("Sandbox.wrl") as well as XML encoding ("Sandbox.x3d"). You'll find the Java source code in "Sandbox.java" and the compiled Java byte code in "Sandbox.class".

Files: