如何构建沙箱环境

时间:2016-10-11 09:32:28

标签: java securitymanager

Hi SecurityManager专家在那里; - )

我编写了一个小插件框架,可以使用单独的隔离类加载器加载插件。对于成功取消部署插件,确保应用程序不保留对插件类加载器加载的类的引用非常重要。

Java中有一个名为shutdown hook的功能,它允许客户端代码注册在请求JVM关闭时执行的线程。这意味着JVM可能会对插件类加载器加载的类进行引用。

我的第一次尝试是安装拒绝添加关闭挂钩的SecurityManager。这完全有效并且拒绝所有添加关闭钩子的尝试。有趣的是,我意识到,不是插件本身想要添加一个关闭钩子。该插件只是通过加载字体来触发AWT / Swing内部(精确:SunFontManager)。 {J}退出时,SunFontManager反过来添加了关闭钩子以执行其中一些内部处理。我不想否认该插件加载一个字体(因为它必须)并且我不想拒绝Java内部添加他们的东西。

对我而言,似乎我必须为代码授予更多权限(如果sun.*|java.*|javax.*包在堆栈trace..uuuhmm ...丑陋),而不是正常情况。但这打破了安全框架的惯例,该框架假定客户端代码不能拥有比调用它的代码更多的特权。

我如何区分它不是插件代码而是想要做某事的Java内部?或者还有其他方法可以确保不通过拒绝某些内容来破坏内部构件吗?

发生的事情的例子

为了使事情更清楚,这是我的应用程序中实际发生的事情。我安装了一个自定义的SecurityManager,它应该拒绝addShutdownHook直接由插件执行,但如果由Java内部类作为副作用执行,则不应拒绝这些调用。

Context:            Calls:
----------------------------------------------
AppClassloader      Application
                        |
PluginClassloader   PluginObject.init()
                        |
AppClassloader      Font.create()
                        |
                    AccessController.doPrivileged(...) {
                        Runtime.addShutdownHook(...)
                    }

使用System.setSecurityManager()安装SecurityManager并执行此操作:

@Override
public void checkPermission(java.security.Permission perm) {
  if (perm.getName().equals("shutdownHooks")) {
    if (threadContextClassLoaderIsPluginClassLoader()) {
      throw new SecurityException("Installing shutdown hooks is not allowed.");
    }
  }
}

虽然在addShutdownHook - {{}}内调用doPrivileged,但我的SecurityManager被调用。我错过了什么吗?我应该自己检查特权上下文吗?

1 个答案:

答案 0 :(得分:3)

我发现我完全误解了Java安全概念。我想为插件代码构建一个沙箱,以便它始终在受限制的环境中运行,我可以在其中控制它拥有的权限。应用程序应始终与授予完全访问权限一起运行。

以下是如何为单独的类加载器加载的类构建沙箱:

  1. 使用默认的SecurityManager 如果您发现自己实施自己的java.lang.SecurityManager以允许或拒绝基于权限的操作,那么您可能做错了!您只需要执行此java.lang.SecurityManager即可安装默认System.setSecurityManager(new SecurityManager());。现在,您可以使用Java默认值来获得有效的访问控制。
  2. 授予应用程序的所有权限加载插件的应用程序应获得完全访问权限。这是因为我们相信自己。我通过使用授予所有访问权限的策略文件启动我的应用程序来解决这个问题。我认为应用程序启动时需要策略文件,因为AppClassLoader需要为类创建正确的保护域。使用JavaVM参数java.security.policy=<URL-TO-POLICY>启动主应用程序,其中<URL-TO-POLICY指向以下策略文件:

    grant { permission java.security.AllPermission; };

  3. 自定义策略实施我们需要在应用程序启动后安装自定义策略。我们想要分离主应用程序(使用完全访问权限运行)和插件(我们想要限制的权限)的权限。插件应该在具有手动选择权限的沙箱中运行。为此,以下是自定义策略实现:

    class SandboxPolicy extends Policy {
    @Override
    
      public PermissionCollection getPermissions(ProtectionDomain domain) {
        // Decide if the plugin permissions are needed or full access can be granted using all permissions.
        if (isPlugin(domain)) {
          return pluginPermissions();
        } else {
          return applicationPermissions();
        }
      }
    
      private boolean isPlugin(ProtectionDomain domain) {
        // Identify the classloader of the protection domain
        // The PluginClassLoader is assumed to be the one that loaded
        // the plugin
        return domain.getClassLoader() instanceof PluginClassLoader;
      }
    
      private PermissionCollection pluginPermissions() {
        // Empty permissions = No permissions
        // This is not the point to add plugin permissions
        return new Permissions();
      }
    
      private PermissionCollection applicationPermissions() {
        // Grant full access to the application
        Permissions permissions = new Permissions();
        permissions.add(new AllPermission());
        return permissions;
      }
    }
    
  4. 每个类加载器的ProtectionDomains 类加载器负责为每个源创建ProtectionDomain,从中加载代码。保护域指定将授予代码的权限。我们必须修改类加载器添加到保护域的权限集。为此,请让PluginClassloader扩展java.security.SecureClassLoader。然后按以下方式覆盖方法java.security.SecureClassLoader.getPermissions(CodeSource)

    @Override
    protected PermissionCollection getPermissions(CodeSource codeSource)
    {
        PermissionCollection pc;
        // The SecureClassloader per default grants access to read resources from the source JAR.
        // This is useful. Call super to get those permissions:
        pc = super.getPermissions(codeSource);
        // At this point you can extend permissions.
        // For example grant read access to a file.
        pc.add(new FilePermission("path\\file", "read"));
        return (pc);
    }
    
  5. 注意:请参阅政策实施,并查看我的评论// This is not the point to add plugin permissionsAccessController要求该政策查看是否可以授予权限。如果在此处添加了权限,则可以从调用方获取的信息非常有限。我不建议在这里计算和添加任何权限。类加载器是您可以根据代码源添加权限的点。我建议在这里添加权限。它们在保护域中可见,您可以轻松地观察AccessController对这些保护域的作用。

    特权行为

    我的问题中有另一部分我想回答。

      

    有趣的是,我意识到,不是插件本身想要添加一个关闭钩子。该插件只需加载字体即可触发AWT / Swing内部(精确:SunFontManager)。

    有些动作将作为插件代码的副作用执行。类org.plugin.A可以触发Swing / AWT内部以添加关闭钩子。在这种情况下,我们不想否认这一点。 Java类在应用程序的范围内,应该获得完全访问权限。插件代码可能受到限制,直接添加关闭挂钩应该会失败SecurityException。这是java.security.AccessController.doPrivileged(PrivilegedAction<T>)的常见用例。特权操作可确保AccessController仅考虑最后一个堆栈帧的保护域。 AWT / Swing在特权操作中添加了其关闭钩子,因此允许这样做,因为类的保护域在具有完全访问权限的情况下执行此操作。请参阅java.security.AccessController的任何文档以了解有关特权操作的更多信息。