Sandbox vs. malicious code in a Java application

In a simulation server environment, where users are allowed to send their own code to run by the server, it would be clearly beneficial for any user code to run on the sandbox side, unlike applets in the browser, I would like to be able to use the JVM myself, instead to add another level of VM to isolate these components presented.

This limitation is apparently possible using the existing Java sandbox model, but is there a dynamic way to enable this only for parts of the application that the user runs?

+84
java plugins sandbox
Feb 02 '09 at 4:50
source share
7 answers
  • Run the untrusted code in your thread. This, for example, prevents problems with infinite loops, etc. And facilitates future steps. Ask the main thread to wait for the thread to finish, and if it takes too long, kill it with Thread.stop. Thread.stop is deprecated, but since untrusted code should not have access to any resources, it would be safe to kill it.

  • Install SecurityManager in this thread. Create a subclass of SecurityManager that overrides checkPermission (Permission perm) to simply throw a SecurityException for all but a few selected permissions. There is a list of the methods and permissions that they require here: Permissions in the Java TM 6 SDK.

  • Use a custom ClassLoader to load untrusted code. Your classloader will be called for all classes that use untrusted code, so you can do things like disable access to individual JDK classes. To do this, you need to have a white list of allowed JDK classes.

  • You might want to run untrusted code in a separate JVM. Although the previous steps will make the code safe, there is one annoying thing that isolated code can still do: allocate as much memory as possible, which will increase the visible footprint of the main application.

JSR 121: The Application Isolation API specification was developed to solve this problem, but unfortunately it has not yet been implemented.

This is a pretty detailed topic, and I mostly write it all from my head.

But, in any case, some imperfect code used for your own risk is probably a buggy (pseudo) code:

Classloader

class MyClassLoader extends ClassLoader { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (name is white-listed JDK class) return super.loadClass(name); return findClass(name); } @Override public Class findClass(String name) { byte[] b = loadClassData(name); return defineClass(name, b, 0, b.length); } private byte[] loadClassData(String name) { // load the untrusted class data here } } 

Securitymanager

 class MySecurityManager extends SecurityManager { private Object secret; public MySecurityManager(Object pass) { secret = pass; } private void disable(Object pass) { if (pass == secret) secret = null; } // ... override checkXXX method(s) here. // Always allow them to succeed when secret==null } 

Topic

 class MyIsolatedThread extends Thread { private Object pass = new Object(); private MyClassLoader loader = new MyClassLoader(); private MySecurityManager sm = new MySecurityManager(pass); public void run() { SecurityManager old = System.getSecurityManager(); System.setSecurityManager(sm); runUntrustedCode(); sm.disable(pass); System.setSecurityManager(old); } private void runUntrustedCode() { try { // run the custom class main method for example: loader.loadClass("customclassname") .getMethod("main", String[].class) .invoke(null, new Object[]{...}); } catch (Throwable t) {} } } 
+101
Feb 02 '09 at 6:49
source share

Obviously, such a scheme causes all kinds of security problems. Java has a strict security system, but this is not trivial. We should not lose sight of the possibility of screwing it on and providing an unprivileged user with access to the vital components of the system.

This warning aside, if you accept user input in the form of source code, the first thing you need to do is compile it into Java bytecode. AFIAK, this cannot be done initially, so you will need to make a javac system call and compile the source code into bytecode on disk. Here is a tutorial that can be used as a starting point for this. Edit : as I found out in the comments, you really can compile Java code from source using javax.tools.JavaCompiler

Once you use the JVM bytecode, you can load it into the JVM using the ClassLoader defineClass . To set the security context for this loaded class, you need to specify ProtectionDomain . The minimum constructor for ProtectionDomain requires both a CodeSource and a PermissionCollection . PermissionCollection is the primary use object for you here - you can use it to specify the exact permissions that the loaded class has. These permissions must ultimately be applied by the AccessController JVM.

There are many possible errors, and you should be extremely careful to fully understand everything before you implement anything.

+18
Feb 02 '09 at 6:24
source share

Java-Sandbox is a library for executing Java code with a limited set of permissions. It can be used to access only the set of classes and resources listed in the white list. It does not seem to be able to restrict access to individual methods. It uses a system with a custom class loader and to achieve this.

I have not used it, but it looks well thought out and well documented enough.

@waqas gave a very interesting answer explaining how this can be implemented independently. But it’s much safer to leave a critical and complex security code to experts.

Please note that the project has not been updated since 2013, and the creators describe it as "experimental." His homepage has disappeared, but the Source Forge record remains.

Sample code adapted from the project website:

 SandboxService sandboxService = SandboxServiceImpl.getInstance(); // Configure context SandboxContext context = new SandboxContext(); context.addClassForApplicationLoader(getClass().getName()); context.addClassPermission(AccessType.PERMIT, "java.lang.System"); // Whithout this line we get a SandboxException when touching System.out context.addClassPermission(AccessType.PERMIT, "java.io.PrintStream"); String someValue = "Input value"; class TestEnvironment implements SandboxedEnvironment<String> { @Override public String execute() throws Exception { // This is untrusted code System.out.println(someValue); return "Output value"; } }; // Run code in sandbox. Pass arguments to generated constructor in TestEnvironment. SandboxedCallResult<String> result = sandboxService.runSandboxed(TestEnvironment.class, context, this, someValue); System.out.println(result.get()); 
+10
Nov 17 '13 at 21:13
source share

Well, it’s very late to give any suggestions or solutions, but still I came across a similar problem, more research-oriented. I basically tried to provide and automatically evaluate programming assignments for a Java course on e-learning platforms.

  • One way could be, Create separate virtual machines (not JVMs), but virtual virtual machines with the lowest possible OS configuration for each student.
  • Install the JRE for Java or libraries according to your programming languages, depending on what you want students to compile and run on these machines.

I know this sounds rather complicated and a lot of tasks, but Oracle Virtual Box already provides a Java API for dynamically creating or cloning virtual machines. https://www.virtualbox.org/sdkref/index.html (Note that even VMware also provides an API for this)

And for the minimum size and configuration of the Linux distribution, you can refer to this here http://www.slitaz.org/en/ ,

So, now, if students get confused or try to do this, they may be with memory or a file system or a network, a socket, it can damage a maximum of one's own virtual machine.

Also inside this virtual machine you can provide additional security, such as Sandbox (security manager) for Java or the creation of user accounts in Linux and, thus, restrict access.

Hope this helps!

+4
Mar 12 '15 at 23:51
source share

There is a thread-safe solution to the problem:

https://svn.code.sf.net/p/loggifier/code/trunk/de.unkrig.commons.lang/src/de/unkrig/commons/lang/security/Sandbox.java

 package de.unkrig.commons.lang.security; import java.security.AccessControlContext; import java.security.Permission; import java.security.Permissions; import java.security.ProtectionDomain; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import de.unkrig.commons.nullanalysis.Nullable; /** * This class establishes a security manager that confines the permissions for code executed through specific classes, * which may be specified by class, class name and/or class loader. * <p> * To 'execute through a class' means that the execution stack includes the class. Eg, if a method of class {@code A} * invokes a method of class {@code B}, which then invokes a method of class {@code C}, and all three classes were * previously {@link #confine(Class, Permissions) confined}, then for all actions that are executed by class {@code C} * the <i>intersection</i> of the three {@link Permissions} apply. * <p> * Once the permissions for a class, class name or class loader are confined, they cannot be changed; this prevents any * attempts (eg of the confined class itself) to release the confinement. * <p> * Code example: * <pre> * Runnable unprivileged = new Runnable() { * public void run() { * System.getProperty("user.dir"); * } * }; * * // Run without confinement. * unprivileged.run(); // Works fine. * * // Set the most strict permissions. * Sandbox.confine(unprivileged.getClass(), new Permissions()); * unprivileged.run(); // Throws a SecurityException. * * // Attempt to change the permissions. * { * Permissions permissions = new Permissions(); * permissions.add(new AllPermission()); * Sandbox.confine(unprivileged.getClass(), permissions); // Throws a SecurityException. * } * unprivileged.run(); * </pre> */ public final class Sandbox { private Sandbox() {} private static final Map<Class<?>, AccessControlContext> CHECKED_CLASSES = Collections.synchronizedMap(new WeakHashMap<Class<?>, AccessControlContext>()); private static final Map<String, AccessControlContext> CHECKED_CLASS_NAMES = Collections.synchronizedMap(new HashMap<String, AccessControlContext>()); private static final Map<ClassLoader, AccessControlContext> CHECKED_CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap<ClassLoader, AccessControlContext>()); static { // Install our custom security manager. if (System.getSecurityManager() != null) { throw new ExceptionInInitializerError("There already a security manager set"); } System.setSecurityManager(new SecurityManager() { @Override public void checkPermission(@Nullable Permission perm) { assert perm != null; for (Class<?> clasS : this.getClassContext()) { // Check if an ACC was set for the class. { AccessControlContext acc = Sandbox.CHECKED_CLASSES.get(clasS); if (acc != null) acc.checkPermission(perm); } // Check if an ACC was set for the class name. { AccessControlContext acc = Sandbox.CHECKED_CLASS_NAMES.get(clasS.getName()); if (acc != null) acc.checkPermission(perm); } // Check if an ACC was set for the class loader. { AccessControlContext acc = Sandbox.CHECKED_CLASS_LOADERS.get(clasS.getClassLoader()); if (acc != null) acc.checkPermission(perm); } } } }); } // -------------------------- /** * All future actions that are executed through the given {@code clasS} will be checked against the given {@code * accessControlContext}. * * @throws SecurityException Permissions are already confined for the {@code clasS} */ public static void confine(Class<?> clasS, AccessControlContext accessControlContext) { if (Sandbox.CHECKED_CLASSES.containsKey(clasS)) { throw new SecurityException("Attempt to change the access control context for '" + clasS + "'"); } Sandbox.CHECKED_CLASSES.put(clasS, accessControlContext); } /** * All future actions that are executed through the given {@code clasS} will be checked against the given {@code * protectionDomain}. * * @throws SecurityException Permissions are already confined for the {@code clasS} */ public static void confine(Class<?> clasS, ProtectionDomain protectionDomain) { Sandbox.confine( clasS, new AccessControlContext(new ProtectionDomain[] { protectionDomain }) ); } /** * All future actions that are executed through the given {@code clasS} will be checked against the given {@code * permissions}. * * @throws SecurityException Permissions are already confined for the {@code clasS} */ public static void confine(Class<?> clasS, Permissions permissions) { Sandbox.confine(clasS, new ProtectionDomain(null, permissions)); } // Code for 'CHECKED_CLASS_NAMES' and 'CHECKED_CLASS_LOADERS' omitted here. } 

Comment!

Cu

Arno

+2
Jun 14 '14 at 23:07
source share

To solve the problem in the accepted answer, as a result of which the custom SecurityManager will be applied to all threads in the JVM, and not for each thread, you can create a custom SecurityManager that you can enable / disable for specific threads as follows:

 import java.security.Permission; public class SelectiveSecurityManager extends SecurityManager { private static final ToggleSecurityManagerPermission TOGGLE_PERMISSION = new ToggleSecurityManagerPermission(); ThreadLocal<Boolean> enabledFlag = null; public SelectiveSecurityManager(final boolean enabledByDefault) { enabledFlag = new ThreadLocal<Boolean>() { @Override protected Boolean initialValue() { return enabledByDefault; } @Override public void set(Boolean value) { SecurityManager securityManager = System.getSecurityManager(); if (securityManager != null) { securityManager.checkPermission(TOGGLE_PERMISSION); } super.set(value); } }; } @Override public void checkPermission(Permission permission) { if (shouldCheck(permission)) { super.checkPermission(permission); } } @Override public void checkPermission(Permission permission, Object context) { if (shouldCheck(permission)) { super.checkPermission(permission, context); } } private boolean shouldCheck(Permission permission) { return isEnabled() || permission instanceof ToggleSecurityManagerPermission; } public void enable() { enabledFlag.set(true); } public void disable() { enabledFlag.set(false); } public boolean isEnabled() { return enabledFlag.get(); } } 

ToggleSecurirtyManagerPermission is simply an implementation of java.security.Permission to ensure that only authorized code can enable / disable the security manager. It looks like this:

 import java.security.Permission; public class ToggleSecurityManagerPermission extends Permission { private static final long serialVersionUID = 4812713037565136922L; private static final String NAME = "ToggleSecurityManagerPermission"; public ToggleSecurityManagerPermission() { super(NAME); } @Override public boolean implies(Permission permission) { return this.equals(permission); } @Override public boolean equals(Object obj) { if (obj instanceof ToggleSecurityManagerPermission) { return true; } return false; } @Override public int hashCode() { return NAME.hashCode(); } @Override public String getActions() { return ""; } } 
+2
Aug 26 '14 at 2:32 on
source share

You will probably need to use SecurityManger and / or AccessController . For more information, see Java Security Architecture and other Sun safety documentation .

0
Feb 02 '09 at 5:44
source share



All Articles