Java 为JVM插件寻找安全的沙盒API
当您有一个应用程序服务器,并且希望在其中运行第三方插件时,您可以使用限制性的安全管理器来阻止它们执行类似System.exit()的操作,但这只是问题的一半。那些不受信任的插件仍然可以进入无限循环,或者在你有时间眨眼之前吃掉所有的空闲堆。Thread.stop()已被弃用,因此您不能只杀死一个amok线程,而且由于堆是共享的,插件不仅会在耗尽所有堆时出现OutOfMemoryError,而且所有其他正在运行的线程也会出现OutOfMemoryError 是否有一些开源应用程序/API/框架可以操纵插件类的字节码,使线程可终止和/或跟踪分配,以便在分配太多时终止线程?即使代码不容易“打包”以便“单独使用”。您可以通过插入可以随意产生异常(由另一个“管理器”线程触发)的代码使线程可终止,并确保插件不会捕获异常。您还可以添加一些计数器来计算调用和循环的数量以及分配的数量,并让“管理器”线程杀死一个突破配置限制的插件 我认为所有这些事情都可以用ASM来完成,但我希望它们以前都做过。我可以让插件在它们自己的JVM中运行,但这将涉及大量的数据不断封送/解封,如果插件JVM死亡/崩溃,我仍然不知道潜在的几十个(100个?)插件中的哪一个是问题所在,我不可能每个插件运行一个JVM 我发现了一些相关的问题,但没有一个可以解决无限循环和占用堆的问题:Java 为JVM插件寻找安全的沙盒API,java,plugins,Java,Plugins,当您有一个应用程序服务器,并且希望在其中运行第三方插件时,您可以使用限制性的安全管理器来阻止它们执行类似System.exit()的操作,但这只是问题的一半。那些不受信任的插件仍然可以进入无限循环,或者在你有时间眨眼之前吃掉所有的空闲堆。Thread.stop()已被弃用,因此您不能只杀死一个amok线程,而且由于堆是共享的,插件不仅会在耗尽所有堆时出现OutOfMemoryError,而且所有其他正在运行的线程也会出现OutOfMemoryError 是否有一些开源应用程序/API/框架可以操
出现问题的方式太多了,试图为JVM设计一个进程中的沙箱来允许插件几乎是不可能的。我找到了一个非常简单的解决“System.exec('rm-rf*)”问题的方法:
package de.unkrig.commons.lang.security;
导入java.security.AccessControlContext;
导入java.security.Permission;
导入java.security.Permissions;
导入java.security.ProtectionDomain;
导入java.util.Collections;
导入java.util.HashMap;
导入java.util.Map;
导入java.util.WeakHashMap;
导入de.unkrig.commons.nullanalysis.Nullable;
/**
*此类建立一个安全管理器,该管理器限制通过特定类执行的代码的权限,
*可以由类、类名和/或类装入器指定。
*
*“通过类执行”意味着执行堆栈包含该类。例如,如果类{@code a}的方法
*调用类{@code B}的方法,然后调用类{@code C}的方法,这三个类都是
*以前{@link#conmited(Class,Permissions)conmited},然后对于类{@code C}执行的所有操作
*三个{@link Permissions}的交集适用。
*
*类、类名或类装入器的权限一旦被限制,就不能更改;这防止了任何
*试图(例如,封闭类本身)释放封闭。
*
*代码示例:
*
*Runnable unprivileged=new Runnable(){
*公开募捐{
*System.getProperty(“user.dir”);
* }
* };
*
*//不受限制地跑。
*非特权。运行();//很好。
*
*//设置最严格的权限。
*限制(unprivileged.getClass(),new Permissions());
*非特权。运行();//抛出SecurityException。
*
*//尝试更改权限。
* {
*权限=新权限();
*permissions.add(newallpermission());
*Sandbox.confine(unprivileged.getClass(),permissions);//引发SecurityException。
* }
*unprivileged.run();
*
*/
公开决赛
类沙箱{
私有沙盒(){}
私有静态最终映射首先,类似System.exec()的东西使用适当配置的类加载器和SecurityManager可以防止IO。插件根本无法使用它无法加载的类,无论它多么努力。看看它:它抛出了一个SecurityException!阻止访问JVM之外的资源是很容易的,因为安全性已经集成到JVM中了。其次,我不同意你的观点,如果你不能解决所有的问题,你就根本不应该尝试。事实上,小偷可以用电锯把我的后门打开,这并不是让它不锁的理由。
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. E.g., 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 (e.g. 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's 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.
}