Finding a Safe Sandbox API for JVM Plugins

When you have an application server and you want to run third-party plugins, you can use the restrictive security manager so that they don’t do things like System.exit (), but that’s only half the story. These unreliable plugins can still end up in an endless loop or eat the whole free pile before you have time to blink. Thread.stop () is deprecated, so you cannot just kill the amok thread, and since the heap is shared, not only will the plugin receive an OutOfMemoryError when it uses the whole heap, but all other threads will execute as well.

Is there some kind of Open / application / framework / Open Framework application that can manipulate the bytecode of the plugin classes to make the threads killer and / or track the distribution so that the thread can be killed if it allocates too much? Even if the code is not easily "packaged" for "use separately." You can make Thread killable by inserting code that can throw an Exception of its own, caused by another thread of the "manager" and make sure that the Exception is not caught by the plugin. And you can add some counters that count the number of calls and loops and the allocation volume, and the "manager" thread kills the plugin, which violates the set limits.

I think that all these things could be done with ASM, but I hope that they were done earlier. I could allow the plugins to work in their own JVM, but this would be due to massive constant data marshaling / unmarching, and in the case when the JVM plugin dies / crashes, I still don't know which of the potential values ​​(100s?) Were problems with plugins, and I can not start one JVM for each plugin.

I found some related questions, but no one solves the problem with endless loops and heap eating:

+4
source share
2 answers

I found a very simple solution to the 'System.exec (' rm -rf * ') 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. } 
+1
source

I participated in adding plugin support to my existing web application using the OSGi platform. Based on my experience and reading this topic, I realized:

1) OSGi are the most famous and highly supported Plugin standards on the JVM. There are several different implementations of this specification, such as Equinox (eclipse), Felix (Apache), Dynamic Modules (Spring), etc. So this is what most of the open source work is behind.

2) There is nothing in the specification that speaks of resource limitations. In fact, they actively avoided talking about it. It’s not like they don’t know about it, but their position in the JVM, you can’t do anything to prevent people from doing any harm. So, the gold standard of the plugin specification on the JVM does not say that.

There are bits and pieces of information (for example, links that you posted) on how to implement some of these restrictions, but you cannot do anything in terms of preventing malicious plugins from doing bad things.

This means that there are not all ways to stop resources from hanging (CPU, memory, file descriptors, SQL connections, etc.).

The heap and processor are simple. How about just "System.exec (" rm -rf ")? Or open allows you to speak 64,000 sockets and potentially stop being able to create any new sockets.

There are so many ways that can go wrong that trying to create an in-process sandbox for plugins JVM is almost impossible.

0
source

Source: https://habr.com/ru/post/1398223/


All Articles