8

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

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

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

4

3 回答 3

12

也许查看调用堆栈就足以满足您的目的?就像是:

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);
    }
}
于 2011-02-05T20:49:21.463 回答
3

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

此自定义权限起作用的一种可能方式是使其访问基于调用者的类加载器和正在修改访问的对象。使用它允许隔离代码(使用它自己的类加载器从单独的代码加载)调用setAccessible它自己的 jar 中的类,但不能调用标准 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 构建一个转换器,用于转换 JavaAccessibleObject以使用您的存根。

创建转换器的第一步是创建一个 Byte Buddy 类型池,其中包括引导类和包含存根的 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有一个完整、深入的沙盒运行时环境示例,它执行隔离的不受信任的代码和更具选择性的反射权限。我还有一篇博文,其中包含有关选择性 setAccessible 权限部分的更多详细信息。

于 2016-07-20T04:30:20.803 回答
0

FWI:由于 setAccessible 似乎只有一个有效的序列化用例,我想你可能经常直接直接否认它就可以侥幸逃脱。

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

于 2010-02-22T23:57:22.057 回答