【问题标题】:Is there a way for a SecurityManager in java to selectively grant ReflectPermission("suppressAccessChecks")?java中的SecurityManager有没有办法选择性地授予ReflectPermission(“suppressAccessChecks”)?
【发布时间】:2011-01-19 21:07:24
【问题描述】:

Java 中的 SecurityManager 有什么方法可以根据调用的 setAccessible() 的详细信息选择性地授予 ReflectPermission("suppressAccessChecks") ?我看不出有什么办法可以做到这一点。

对于某些沙盒代码,允许调用 setAccessible() 反射 API 会非常有用(例如运行各种动态 JVM 语言),但仅在调用 setAccessible() 时在源自沙盒代码的类的方法/字段上。

如果这是不可能的,除了选择性授予 ReflectPermission("suppressAccessChecks") 之外,是否还有其他建议?如果 SecurityManager.checkMemberAccess() 具有足够的限制性,也许在所有情况下都可以安全地授予?

【问题讨论】:

    标签: java reflection securitymanager


    【解决方案1】:

    这可以通过像Byte Buddy 这样的库使用字节码编织来实现。您可以创建自定义权限并将AccessibleObject.setAccessible 方法替换为使用字节好友转换检查您的自定义权限的自定义方法,而不是使用标准的ReflectPermission("suppressAccessChecks") 权限。

    此自定义权限起作用的一种可能方式是使其访问基于调用者的类加载器和正在修改访问的对象。使用它允许隔离代码(使用它自己的类加载器从单独加载的代码)在其自己的 jar 中的类上调用 setAccessible,但不能在标准 Java 类或您自己的应用程序类上调用。

    这样的权限可能如下所示:

    public class UserSetAccessiblePermission extends Permission {
      private final ClassLoader loader;
    
      public UserSetAccessiblePermission(ClassLoader loader) {
        super("userSetAccessible");
        this.loader = loader;
      }  
    
      @Override
      public boolean implies(Permission permission) {
        if (!(permission instanceof UserSetAccessiblePermission)) {
          return false;
        }
        UserSetAccessiblePermission that = (UserSetAccessiblePermission) permission;
        return that.loader == this.loader;
      }
    
      // equals and hashCode omitted  
    
      @Override
      public String getActions() {
        return "";
      }
    }
    

    这是我选择实现此权限的方式,但它可以是包或类白名单或黑名单。

    现在有了这个权限,你可以创建一个存根类来代替AccessibleObject.setAcessible 方法来使用这个权限。

    public class AccessibleObjectStub {
      private final static Permission STANDARD_ACCESS_PERMISSION =
          new ReflectPermission("suppressAccessChecks");
    
      public static void setAccessible(@This AccessibleObject ao, boolean flag)
          throws SecurityException {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
          Permission permission = STANDARD_ACCESS_PERMISSION;
          if (isFromUserLoader(ao)) {
            try {
              permission = getUserAccessPermission(ao);
            } catch (Exception e) {
              // Ignore. Use standard permission.
            }
          }
    
          sm.checkPermission(permission);
        }
      }
    
      private static Permission getUserAccessPermission(AccessibleObject ao)
          throws IllegalAccessException, InvocationTargetException, InstantiationException,
          NoSuchMethodException, ClassNotFoundException {
        ClassLoader aoClassLoader = getAccessibleObjectLoader(ao);
        return new UserSetAccessiblePermission(aoClassLoader);
      }
    
      private static ClassLoader getAccessibleObjectLoader(AccessibleObject ao) {
        return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
          @Override
          public ClassLoader run() {
            if (ao instanceof Executable) {
              return ((Executable) ao).getDeclaringClass().getClassLoader();
            } else if (ao instanceof Field) {
              return ((Field) ao).getDeclaringClass().getClassLoader();
            }
            throw new IllegalStateException("Unknown AccessibleObject type: " + ao.getClass());
          }
        });
      }
    
      private static boolean isFromUserLoader(AccessibleObject ao) {
        ClassLoader loader = getAccessibleObjectLoader(ao);
    
        if (loader == null) {
          return false;
        }
    
        // Check that the class loader instance is of a custom type
        return UserClassLoaders.isUserClassLoader(loader);
      }
    }
    

    有了这两个类,您现在可以使用 Byte Buddy 构建一个转换器,将 Java AccessibleObject 转换为使用您的存根。

    创建转换器的第一步是创建一个字节好友类型池,其中包括引导类和一个包含存根的 jar 文件。

    final TypePool bootstrapTypePool = TypePool.Default.of(
    new ClassFileLocator.Compound(
        new ClassFileLocator.ForJarFile(jarFile),
        ClassFileLocator.ForClassLoader.of(null)));
    

    接下来使用反射来获得对AccessObject.setAccessible0 方法的引用。这是一个私有方法,如果对 setAccessible 的调用通过权限检查,它实际上会修改可访问性。

    Method setAccessible0Method;
    try {
      String setAccessible0MethodName = "setAccessible0";
      Class[] paramTypes = new Class[2];
      paramTypes[0] = AccessibleObject.class;
      paramTypes[1] = boolean.class;
      setAccessible0Method = AccessibleObject.class
          .getDeclaredMethod(setAccessible0MethodName, paramTypes);
    } catch (NoSuchMethodException e) {
      throw new RuntimeException(e);
    }
    

    有了这两块,就可以搭建变压器了。

    AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
      @Override
      public DynamicType.Builder<?> transform(
          DynamicType.Builder<?> builder,
          TypeDescription typeDescription, ClassLoader classLoader) {
        return builder.method(
            ElementMatchers.named("setAccessible")
                .and(ElementMatchers.takesArguments(boolean.class)))
            .intercept(MethodDelegation.to(
                bootstrapTypePool.describe(
                    "com.leacox.sandbox.security.stub.java.lang.reflect.AccessibleObjectStub")
                    .resolve())
                .andThen(MethodCall.invoke(setAccessible0Method).withThis().withAllArguments()));
      }
    }
    

    最后一步是安装 Byte Buddy Java 代理并执行转换。包含存根的 jar 也必须附加到引导类路径。这是必要的,因为AccessibleObject 类将由引导加载程序加载,因此任何存根也必须在那里加载。

    Instrumentation instrumentation = ByteBuddyAgent.install();
    // Append the jar containing the stub replacement to the bootstrap classpath
    instrumentation.appendToBootstrapClassLoaderSearch(jarFile);
    
    AgentBuilder agentBuilder = new AgentBuilder.Default()
           .disableClassFormatChanges()
           .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
           .ignore(none()); // disable default ignores so we can transform Java classes
           .type(ElementMatchers.named("java.lang.reflect.AccessibleObject"))
           .transform(transformer)
           .installOnByteBuddyAgent();
    

    当使用 SecurityManager 并隔离存根类和您在运行时加载的单独 jar 中应用选择性权限的代码时,这将起作用。必须在运行时加载 jars 而不是将它们作为标准依赖项或捆绑的库使事情变得有点复杂,但这似乎是在使用 SecurityManager 时隔离​​不受信任的代码的要求。

    我的 Github 存储库 sandbox-runtime 有一个完整、深入的沙盒运行时环境示例,它执行隔离的不受信任的代码和更具选择性的反射权限。我还有一篇博文,其中包含更多关于 selective setAccessible permissions 作品的详细信息。

    【讨论】:

      【解决方案2】:

      也许查看调用堆栈就足以满足您的目的了?比如:

      import java.lang.reflect.ReflectPermission;
      import java.security.Permission;
      
      public class Test {
          private static int foo;
      
          public static void main(String[] args) throws Exception {
              System.setSecurityManager(new SecurityManager() {
                  @Override
                  public void checkPermission(Permission perm) {
                      if (perm instanceof ReflectPermission && "suppressAccessChecks".equals(perm.getName())) {
                          for (StackTraceElement elem : Thread.currentThread().getStackTrace()) {
                              if ("Test".equals(elem.getClassName()) && "badSetAccessible".equals(elem.getMethodName())) {
                                  throw new SecurityException();
                              }
                          }
                      }
                  }
              });
      
              goodSetAccessible(); // works
              badSetAccessible(); // throws SecurityException
          }
      
          private static void goodSetAccessible() throws Exception {
              Test.class.getDeclaredField("foo").setAccessible(true);
          }
      
          private static void badSetAccessible() throws Exception {
              Test.class.getDeclaredField("foo").setAccessible(true);
          }
      }
      

      【讨论】:

        【解决方案3】:

        FWI:由于 setAccessible 似乎只有一个有效的序列化用例,所以我认为您可能经常会直接直接拒绝它而侥幸逃脱。

        也就是说,我对一般如何做这类事情很感兴趣,因为我也必须编写一个安全管理器来阻止动态加载的代码执行我们的应用程序容器代码需要执行的操作。

        【讨论】:

        • 不幸的是,不幸的是,一些动态 JVM 语言是相当 setAccessible-happy 的,即使是他们不需要调用它的公共方法也会调用它。此外,还有一些用例,例如您提到的序列化,或者依赖注入框架的某些操作模式,它们宁愿不要不必要地阻塞。
        • 嗯。不知道其他用例 - 我一直认为 setAccessible 是 Sun 用 Ja​​va 制造的最大的安全问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-18
        • 2023-03-03
        • 2020-07-31
        • 1970-01-01
        相关资源
        最近更新 更多