语境

我正在编写一个Java系统,其中的代码在非常严格的沙箱中执行。一个查询(由一个或多个类组成)仅应允许在执行期间访问一个文件夹(以及文件夹中的子文件夹和文件)。

我通过使用SecurityManager和每个查询执行一个新的ClassLoader来强制执行沙箱操作。当使用ClassLoaderdefineClass中定义类时,我传递了一个ProtectionDomain,其中包含应授予的文件读取权限。

由于并非调用堆栈上的所有objets都具有所需的特权,因此查询中的读取操作在AccessController.doPrivileged(...) -block中运行。

问题

  • 当我直接从AccessController.checkPermission(...)块内调用doPrivileged(...)时,它会默默返回
  • 当我调用System.getSecurityManager().checkPermission(...)时,它将请求转发到AccessController,然后AccessController引发异常。
  • 通过ProtectionDomain调用AccessController时,SecurityManager似乎丢失了吗?
  • 受限制的文件操作(例如创建java.io.FileReader),直接调用SecurityManager而不是AccessController通过AccessController调用时,如何获得SecurityManager以尊重调用ProtectionDomain -block的类的doRestricted(...)
  • 可能是SecurityManager本身没有所需的权限吗?从而,通过夹在特权代码之间的调用堆栈中,AccessController会生成无特权联合?

  • 下面是一个示例部分:
    AccessController.doPrivileged(new PrivilegedAction<QueryResult>() {
      public QueryResult run() {
        String location = folderName + "/hello";
        FilePermission p = new FilePermission(location, "read");
        try {
          AccessController.checkPermission(p); // Doesn't raise an exception
          System.out.println("AccessController says OK");
          System.getSecurityManager().checkPermission(p);  // Raises AccessControlException
          System.out.println("SecurityManager says OK");
        } catch (AccessControlException e) {
          System.out.println("### Not allowed to read");
        }
        return null;
      }
    });
    

    程序生成的输出,包括堆栈跟踪的部分内容(用PATH代替使用的长路径名):
    AccessController says OK
    Asked for permission: ("java.io.FilePermission" "PATH/hello" "read")
    java.security.AccessControlException: access denied ("java.io.FilePermission" "PATH/hello" "read")
      at java.security.AccessControlContext.checkPermission(AccessControlContext.java:366)
      at java.security.AccessController.checkPermission(AccessController.java:560)
      at com.aircloak.cloak.security.CloakSecurityManager.checkPermission(CloakSecurityManager.java:40)
      at com.dummycorp.queries.ValidQuery$1.run(ValidQuery.java:23)
      at com.dummycorp.queries.ValidQuery$1.run(ValidQuery.java:1)
      at java.security.AccessController.doPrivileged(Native Method)
      at com.dummycorp.queries.ValidQuery.run(ValidQuery.java:16)
      at com.aircloak.cloak.security.CloakSecurityManagerTest$1.run(CloakSecurityManagerTest.java:75)
      at java.lang.Thread.run(Thread.java:722)
    
    CloakAccessController.checkPermission(...)实现可能也很有趣。看起来像这样:
    public void checkPermission(Permission perm) {
      if (Thread.currentThread().getId() == this.masterThread) {
        return;
      } else {
        System.out.println("Asked for permission: "+perm.toString());
      }
      AccessController.checkPermission(perm);
    }
    

    它所做的主要是绕过创建它的线程的安全限制。

    我的java.policy文件的内容是标准MacOSX系统的内容。我相信它不包含任何非标准的更改。

    最佳答案

    我在回答自己的问题时感到有点尴尬,但我想出了正确的解决方案,并认为在此处添加它只是正确的选择,因此,将来有记录在案的情况下,以防万一有人迷惑于此问题。

    TL; DR :

    我的自定义SecurityManager没有正确的权限。由于它位于调用doPrivileged(...) -block的类和AccessController之间的调用堆栈中,所以特权的交集根本不是特权。

    长版

    Java安全模型的工作方式如下。当AccessController验证是否允许某个类调用方法时,它将查看从调用堆栈顶部到底部的权限。如果调用堆栈中的每个条目都具有许可权,则允许该操作。

    这是一个任意示例,其中一切正常:

    +-----------+-------------------+-----------------------+
    | Callstack | Class permissions | Permissions in effect |
    +-----------+-------------------+-----------------------+
    | Some      | {Read,Write}      | {Read}                |
    | Other     | {Read}            | {Read}                |
    +-----------+-------------------+-----------------------+
    

    现在,就我的问题而言,调用堆栈中的较低层根本没有任何权限。
    因此,我们最终得到了这样的图片,其中顶部的query实际上没有任何权限。
    +-----------+-------------------+-----------------------+
    | Callstack | Class permissions | Permissions in effect |
    +-----------+-------------------+-----------------------+
    | Query     | {Read}            | {}                    |
    | Other     | {}                | {}                    |
    +-----------+-------------------+-----------------------+
    

    通过使用doPrivileged(...) -block可以解决此问题。这允许通过调用堆栈进行权限搜索在调用特权操作的条目处结束:
    +-----------+-------------------+-----------------------+
    | Callstack | Class permissions | Permissions in effect |
    +-----------+-------------------+-----------------------+
    | Query     | {Read}            | {Read}                |
    | Other     | {}                | {}                    |
    +-----------+-------------------+-----------------------+
    

    这就是为什么当我从查询中调用AccessController.checkPermission(...)时一切正常的原因。毕竟它确实具有正确的权限。 (Un)幸运的是,(为了向后兼容)Java API始终调用SecurityManager。在我的情况下,SecurityManager完全没有特权。因为它实际上是在进行特权调用的查询和AccessController之间的调用堆栈上,所以最终得到的权限是无:
    +-----------------+-------------------+-----------------------+
    |    Callstack    | Class permissions | Permissions in effect |
    +-----------------+-------------------+-----------------------+
    | SecurityManager | {}                | {}                    |
    | Query           | {Read}            | {Read}                |
    | Other           | {}                | {}                    |
    +-----------------+-------------------+-----------------------+
    

    解决方案

    解决方案是给SecurityManager基本权限集。结果,授予查询的权限确实是所需的权限:
    +-----------------+---------------------+-----------------------+
    |    Callstack    |  Class permissions  | Permissions in effect |
    +-----------------+---------------------+-----------------------+
    | SecurityManager | {Read,Write,Delete} | {Read}                |
    | Query           | {Read}              | {Read}                |
    | Other           | {}                  | {}                    |
    +-----------------+---------------------+-----------------------+
    

    !那真是满嘴!希望这对外面的人有用:)

    09-03 21:39