如果您有应用程序服务器,并且想要在其中运行第三方插件,则可以使用限制性安全管理器来阻止它们执行System.exit()等操作,但这只是故事的一半。那些不受信任的插件仍然可以进入无限循环,或者在你有时间眨眼之前吃完所有的空闲堆。 Thread.stop()已被弃用,所以你不能只是杀死一个amok线程,并且由于堆是共享的,因此插件不仅会在它耗尽所有堆时获得OutOfMemoryError,但所有其他线程也会运行
是否有一些开源应用程序/ API /框架可以操作插件类的字节码,使线程可以运行和/或跟踪分配,以便线程可以被杀死,如果它分配太多?即使代码不容易“打包”为“单独使用”。您可以通过插入可以随意产生异常的代码来使线程可以运行,由另一个“管理器”线程触发,并确保插件不会捕获异常。你可以添加一些计数器来计算调用和循环次数以及分配数量,并让“manager”线程杀死一个打破配置限制的插件。
我认为所有这些事情都可以通过ASM完成,但我希望他们之前已经完成了。我可以让插件在他们自己的JVM中运行,但这将涉及数据的大量不断编组/解组,并且如果插件JVM死亡/崩溃,我仍然不知道哪个潜在的几十(100s?)的插件是问题,我不可能每个插件运行一个JVM。
我找到了一些相关的问题,但是没有一个问题解决了无限循环和吃堆的问题:
答案 0 :(得分:1)
我为System.exec找到了一个非常简单的解决方案(' rm -rf *')'问题:
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.
}
答案 1 :(得分:0)
我参与使用OSGi框架将插件支持添加到我现有的Web应用程序中。根据我在工作和阅读这个主题方面的经验,这就是我所理解的:
1)OSGi是JVM上最着名和最受支持的插件标准。这个规范有多种不同的实现,如Equinox(eclipse),Felix(Apache),动态模块(Spring)等。因此,很多大型开源基础工作背后都有。
2)规范中没有涉及资源约束的内容。事实上,他们已经积极避免谈论它。它不像他们不知道它,但他们的立场是,在JVM上,没有什么可以阻止人们做某种伤害。因此,JVM上的插件规范的黄金标准没有谈到这一点。
有一些信息(比如你发布的链接)关于如何实现其中一些约束,但是在阻止恶意插件做坏事方面你无能为力。
这意味着,没有全能的方式来阻止资源占用(CPU,内存,文件描述符,SQL连接等)。
堆和CPU很容易。怎么样只是做一个“System.exec('rm -rf')”?或者打开让我们说64000个套接字并且可能无法创建任何新的套接字。
尝试为JVM提供进程内沙箱以允许插件几乎不可能出现问题的方法很多。