【问题标题】:Looking for a safe Sandbox API for JVM plugins [closed]为 JVM 插件寻找安全的沙盒 API [关闭]
【发布时间】:2012-03-14 21:14:17
【问题描述】:

如果您有一个应用服务器,并且想要在其中运行第三方插件,您可以使用限制性安全管理器来阻止它们执行 System.exit() 之类的操作,但这只是故事的一半。那些不受信任的插件仍然会陷入无限循环,或者在你有时间眨眼之前吃掉所有的空闲堆。 Thread.stop() 已被弃用,因此您不能只杀死一个 amok 线程,并且由于堆是共享的,因此插件在用完所有堆时不仅会收到 OutOfMemoryError,而且所有其他正在运行的线程也会.

是否有一些开源应用程序/API/框架可以操纵插件类的字节码以使线程可终止和/或跟踪分配,以便在分配过多时可以终止线程?即使代码不容易“打包”以“单独使用”。您可以通过插入可以随意产生异常的代码来使线程可终止,由另一个“管理器”线程触发,并确保插件不会捕获异常。您可以添加某种计数器来计算调用次数和循环次数以及分配量,并让“管理器”线程杀死一个打破配置限制的插件。

我认为所有这些事情都可以使用 ASM 完成,但我希望它们以前已经完成。我可以让插件在他们自己的 JVM 中运行,但这将涉及大量不断的数据编组/解组,并且万一插件 JVM 死机/崩溃,我仍然不知道潜在的几十个(100 个?)插件是问题所在,我不可能每个插件运行一个 JVM。

我发现了一些相关的问题,但没有一个解决无限循环和吃堆的问题:

【问题讨论】:

    标签: java plugins


    【解决方案1】:

    我为'System.exec('rm -rf *')'问题找到了一个非常简单的解决方案:

    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. 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.
    
    }
    

    【讨论】:

      【解决方案2】:

      我参与了使用 OSGi 框架将插件支持添加到我现有的 Web 应用程序中。根据我对这个主题的工作和阅读经验,这是我所理解的:

      1) OSGi 是 JVM 上最著名和高度支持的插件标准。该规范有多种不同的实现方式,例如 Equinox (eclipse)、Felix (Apache)、Dynamic Modules (Spring) 等。因此,这背后有很多大型开源基金会的工作。

      2) 规范中没有任何关于资源约束的内容。事实上,他们一直在积极避免谈论它。不是他们不知道,而是他们的立场是,在 JVM 上,你无法阻止人们做出某种伤害。所以,JVM插件规范的黄金标准并没有谈到这个。

      有一些关于如何实施这些约束的信息(如您发布的链接),但在防止恶意插件做坏事方面您无能为力。

      这意味着,没有万能的方法来阻止资源占用(CPU、内存、文件描述符、SQL 连接等)。

      堆和 CPU 是最简单的。只做一个“System.exec('rm -rf')”怎么样?或者打开让我们说 64000 个套接字,并可能停止创建任何新的套接字。

      出错的方式有很多,因此几乎不可能为 JVM 提供一个进程内沙箱以允许插件。

      【讨论】:

      • 首先,可以通过使用适当配置的 ClassLoader 和 SecurityManager 来防止 System.exec() 和 IO 之类的事情。插件根本无法使用它无法加载的类,无论它多么努力。看一下:它抛出了一个 SecurityException!防止对 JVM 外部资源的访问很容易,因为安全性已经集成在 JVM 中。
      • 其次,我不同意你的观点,如果你不能解决所有的问题,你根本不应该尝试。小偷可以使用电锯将我的后门切开这一事实并不是让它不上锁的理由。防病毒和反垃圾邮件程序也是如此。 OSGi 是一个极简标准;它只是给第三方工具留下了没有明确解决方案的问题;这并不意味着你不应该考虑它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多