Sandboxing Java Code
In a previous post, we looked at securing mobile Java code. One of the options for doing so is to run the code in a cage or sandbox.
This post explores how to set up such a sandbox for Java applications.
Security Manager
The security facility in Java that supports sandboxing is the java.lang.SecurityManager
.
By default, Java runs without a SecurityManager
, so you should add code to your application to enable one:
System.setSecurityManager(new SecurityManager());
You can use the standard SecurityManager
, or a descendant.
The SecurityManager
has a bunch of checkXXX()
methods that all forward to checkPermission(permission, context)
. This method calls upon the AccessController
to do the actual work (see below).
[The checkXXX()
methods are a relic from Java 1.1.]
If a requested access is allowed, checkPermission()
returns quietly. If denied, a java.lang.SecurityException
is thrown.
Code that implements the sandbox should call a checkXXX
method before performing a sensitive operation:
SecurityManager securityManager = System.getSecurityManager(); if (securityManager != null) { Permission permission = ...; securityManager.checkPermission(permission); }
The JRE contains code just like that in many places.
Permissions
A permission represents access to a system resource.
In order for such access to be allowed, the corresponding permission must be explicitly granted (see below) to the code attempting the access.
Permissions derive from java.security.Permission. They have a name and an optional list of actions (in the form of comma separated string values).
Java ships with a bunch of predefined permissions, like FilePermission. You can also add your own permissions.
The following is a permission to read
the file /home/remon/thesis.pdf
:
Permission readPermission = new java.io.FilePermission( '/home/remon/thesis.pdf', 'read');
You can grant a piece of code permissions to do anything and everything by granting it AllPermission
. This has the same effect as running it without SecurityManager
.
Policies
Permissions are granted using policies. A Policy is responsible for determining whether code has permission to perform a security-sensitive operation.
The AccessController
consults the Policy
to see whether a Permission
is granted.
There can only be one Policy
object in use at any given time. Application code can subclass Policy
to provide a custom implementation.
The default implementation of Policy
uses configuration files to load grants. There is a single system-wide policy file, and a single (optional) user policy file.
You can create additional policy configuration files using the PolicyTool
program. Each configuration file must be encoded in UTF-8.
By default, code is granted no permissions at all. Every grant
statement adds some permissions. Permissions that are granted cannot be revoked.
The following policy fragment grants code that originates from the /home/remon/code/
directory read
permission to the file /home/remon/thesis.pdf
:
grant codeBase 'file:/home/remon/code/-' { permission java.io.FilePermission '/home/remon/thesis.pdf', 'read'; };
Note that the part following codeBase
is a URL, so you should always use forward slashes, even on a Windows system.
A codeBase
with a trailing /
matches all class files (not JAR files) in the specified directory. A codeBase
with a trailing /*
matches all files (both class and JAR files) contained in that directory. A codeBase
with a trailing /-
matches all files (both class and JAR files) in the directory and recursively all files in subdirectories contained in that directory.
For paths in file permissions on Windows systems, you need to use double backslashes (\\
), since the \
is an escape character:
grant codeBase 'file:/C:/Users/remon/code/-' { permission java.io.FilePermission 'C:\\Users\\remon\\thesis.pdf', 'read'; };
For more flexibility, you can write grants with variable parts. We already saw the codeBase
wildcards. You can also substitute system properties:
grant codeBase 'file:/${user.home}/code/-' { permission java.io.FilePermission '${user.home}${/}thesis.pdf', 'read'; };
Note that
${/}
is replaced with the path separator for your system. There is no need to use that in
codeBase
, since that’s a URL.
Signed Code
Of course, we should make sure that the code we use is signed, so that we know that it actually came from who we think it came from.
We can test for signatures in our policies using the signedBy
clause:
keystore 'my.keystore'; grant signedBy 'signer.alias', codeBase ... { ... };
This policy fragment uses the keystore with alias my.keystore
to look up the public key certificate with alias signer.alias
.
It then verifies that the executing code was signed by the private key corresponding to the public key in the found certificate.
There can be only one keystore
entry.
The combination of codeBase
and signedBy
clauses specifies a ProtectionDomain
. All classes in the same ProtectionDomain
have the same permissions.
Privileged Code
Whenever a resource access is attempted, all code on the stack must have permission for that resource access, unless some code on the stack has been marked as privileged.
Marking code as privileged enables a piece of trusted code to temporarily enable access to more resources than are available directly to the code that called it. In other words, the security system will treat all callers as if they originated from the ProtectionDomain
of the class that issues the privileged call, but only for the duration of the privileged call.
You make code privileged by running it inside an AccessController.doPrivileged()
call:
AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // ...privileged code goes here... return null; } });
Assembling the Sandbox
Now we have all the pieces we need to assemble our sandbox:
- Install a
SecurityManager
- Sign the application jars
- Grant all code signed by us
AllPermission
- Add permission checks in places that mobile code may call
- Run the code after the permission checks in a
doPrivileged()
block
I’ve created a simple example on GitHub.
Reference: Sandboxing Java Code from our JCG partner Remon Sinnema at the Secure Software Development blog.
Hi,
thanks for the detailed and very helpful article. can you please share with me the simple example. above Github link to simple example isn’t working. thanks.
https://github.com/RemonSinnema/blog/tree/master/sandbox