34

问题摘要:如何修改下面的代码,以便不受信任的、动态加载的代码在安全沙箱中运行,而应用程序的其余部分保持不受限制?为什么 URLClassLoader 不像它所说的那样处理它?

编辑:更新以回应 Ani B.

编辑 2:添加了更新的 PluginSecurityManager。

我的应用程序有一个插件机制,第三方可以提供一个包含实现特定接口的类的 JAR。使用 URLClassLoader,我可以加载该类并实例化它,没问题。因为代码可能不受信任,所以我需要防止它行为不端。例如,我在一个单独的线程中运行插件代码,以便在它进入无限循环或时间过长时将其终止。但是试图为他们设置一个安全沙箱,这样他们就不能做诸如建立网络连接或访问硬盘上的文件之类的事情,这让我非常生气。我的努力总是导致要么对插件没有影响(它与应用程序具有相同的权限),要么还限制了应用程序。我希望主应用程序代码能够做任何它想做的事情,

关于该主题的文档和在线资源是复杂的、令人困惑的和矛盾的。我已经在不同的地方(例如这个问题)读到我需要提供一个自定义的 SecurityManager,但是当我尝试它时我遇到了问题,因为 JVM 延迟加载 JAR 中的类。所以我可以很好地实例化它,但是如果我在加载的对象上调用从同一个 JAR 实例化另一个类的方法,它就会爆炸,因为它被拒绝从 JAR 读取的权利。

从理论上讲,我可以在我的 SecurityManager 中检查 FilePermission 以查看它是否试图从自己的 JAR 中加载。这很好,但是URLClassLoader 文档说:“默认情况下,加载的类仅被授予访问 URLClassLoader 创建时指定的 URL 的权限。” 那么为什么我什至需要一个自定义的 SecurityManager 呢?URLClassLoader 不应该只处理这个吗?为什么不呢?

这是一个重现问题的简化示例:

主应用程序(受信任)

插件测试.java

package test.app;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;

import test.api.Plugin;

public class PluginTest {
    public static void pluginTest(String pathToJar) {
        try {
            File file = new File(pathToJar);
            URL url = file.toURI().toURL();
            URLClassLoader cl = new URLClassLoader(new java.net.URL[] { url });
            Class<?> clazz = cl.loadClass("test.plugin.MyPlugin");
            final Plugin plugin = (Plugin) clazz.newInstance();
            PluginThread thread = new PluginThread(new Runnable() {
                @Override
                public void run() {
                    plugin.go();
                }
            });
            thread.start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

插件.java

package test.api;

public interface Plugin {
    public void go();
}

PluginSecurityManager.java

package test.app;

public class PluginSecurityManager extends SecurityManager {
    private boolean _sandboxed;

    @Override
    public void checkPermission(Permission perm) {
        check(perm);
    } 

    @Override
    public void checkPermission(Permission perm, Object context) {
        check(perm);
    }

    private void check(Permission perm) {
        if (!_sandboxed) {
            return;
        }

        // I *could* check FilePermission here, but why doesn't
        // URLClassLoader handle it like it says it does?

        throw new SecurityException("Permission denied");
    }

    void enableSandbox() {
    _sandboxed = true;
    }

    void disableSandbox() {
        _sandboxed = false;
    }
}

插件线程.java

package test.app;

class PluginThread extends Thread {
    PluginThread(Runnable target) {
        super(target);
    }

    @Override
    public void run() {
        SecurityManager old = System.getSecurityManager();
        PluginSecurityManager psm = new PluginSecurityManager();
        System.setSecurityManager(psm);
        psm.enableSandbox();
        super.run();
        psm.disableSandbox();
        System.setSecurityManager(old);
    }
}

插件 JAR(不受信任)

我的插件.java

package test.plugin;

public MyPlugin implements Plugin {
    @Override
    public void go() {
        new AnotherClassInTheSamePlugin(); // ClassNotFoundException with a SecurityManager
        doSomethingDangerous(); // permitted without a SecurityManager
    }

    private void doSomethingDangerous() {
        // use your imagination
    }
}

更新: 我对其进行了更改,以便在插件代码即将运行之前通知 PluginSecurityManager 以便它知道它正在使用哪个类源。然后它将只允许对该类源路径下的文件进行文件访问。这也有一个很好的优势,我可以在我的应用程序开始时设置一次安全管理器,并在我输入和离开插件代码时更新它。

这几乎解决了这个问题,但没有回答我的另一个问题:为什么 URLClassLoader 不像它所说的那样为我处理这个问题?我将把这个问题留待一段时间,看看是否有人对这个问题有答案。如果是这样,该人将获得接受的答案。否则,我会将它授予 Ani B.,前提是 URLClassLoader 文档存在缺陷,并且他关于制作自定义 SecurityManager 的建议是正确的。

PluginThread 必须在 PluginSecurityManager 上设置 classSource 属性,这是类文件的路径。PluginSecurityManager 现在看起来像这样:

package test.app;

public class PluginSecurityManager extends SecurityManager {
    private String _classSource;

    @Override
    public void checkPermission(Permission perm) {
        check(perm);
    } 

    @Override
    public void checkPermission(Permission perm, Object context) {
        check(perm);
    }

    private void check(Permission perm) {
        if (_classSource == null) {
            // Not running plugin code
            return;
        }

        if (perm instanceof FilePermission) {
            // Is the request inside the class source?
            String path = perm.getName();
            boolean inClassSource = path.startsWith(_classSource);

            // Is the request for read-only access?
            boolean readOnly = "read".equals(perm.getActions());

            if (inClassSource && readOnly) {
                return;
            }
        }

        throw new SecurityException("Permission denied: " + perm);
    }

    void setClassSource(String classSource) {
    _classSource = classSource;
    }
}
4

3 回答 3

8

从文档:
The AccessControlContext of the thread that created the instance of URLClassLoader will be used when subsequently loading classes and resources.

The classes that are loaded are by default granted permission only to access the URLs specified when the URLClassLoader was created.

URLClassLoader 完全按照它所说的那样做,AccessControlContext 是您需要查看的内容。基本上,在 AccessControlContext 中引用的线程无权执行您认为的操作。

于 2010-10-19T17:46:28.810 回答
7

我在应用程序中运行一些 Groovy 脚本时使用以下方法。我显然想阻止脚本(有意或无意地)运行 System.exit

我以通常的方式安装 java SecurityManager:

-Djava.security.manager -Djava.security.policy=<policy file>

<policy file>我给我的应用程序所有权限(我完全信任我的应用程序),即:

grant {
    permission java.security.AllPermission;
};

我限制了运行 Groovy 脚本的部分的功能:

list = AccessController.doPrivileged(new PrivilegedExceptionAction<List<Stuff>> () {
    public List<Stuff> run() throws Exception {
        return groovyToExecute.someFunction();
    }
}, allowedPermissionsAcc);

allowedPermissionsAcc不会改变,因此我在静态块中创建它们

private static final AccessControlContext allowedPermissionsAcc; 
static {    // initialization of the allowed permissions
    PermissionCollection allowedPermissions = new Permissions();
    allowedPermissions.add(new RuntimePermission("accessDeclaredMembers"));
    // ... <many more permissions here> ...

    allowedPermissionsAcc = new AccessControlContext(new ProtectionDomain[] {
        new ProtectionDomain(null, allowedPermissions)});
}

现在棘手的部分是找到正确的权限。

如果您想允许访问某些库,您很快就会意识到编写它们时并没有考虑到安全管理器,也没有很好地处理安全管理器,并且找出它们需要哪些权限可能非常棘手。如果您想通过 Maven Surefire 插件运行 UnitTests,或者在 Linux/Windows 等不同平台上运行,您将遇到其他问题,因为行为可能会有所不同:-(。但这些问题是另一个话题

于 2011-08-24T07:58:28.883 回答
5

实施 aSecurityManager可能是最好的方法。您将不得不覆盖checkPermission. 该方法将查看Permission传递给它的对象,并确定某个操作是否危险。这样,您可以允许某些权限并禁止其他权限。

你能描述一下SecurityManager你使用的习惯吗?

于 2010-10-18T01:48:19.777 回答