有没有办法让java中的SecurityManager有选择地授予ReflectPermission(“suppressAccessChecks”)?

时间:2010-02-22 23:25:04

标签: java reflection securitymanager

有没有办法让Java中的SecurityManager有选择地授予ReflectPermission(“suppressAccessChecks”),具体取决于调用setAccessible()的详细信息?我认为没有办法做到这一点。

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

如果不可能,除了选择性授予ReflectPermission(“suppressAccessChecks”)之外,是否还有其他任何建议?在所有情况下,如果SecurityManager.checkMemberAccess()有足够的限制,可能会安全吗?

3 个答案:

答案 0 :(得分:10)

也许看一下调用堆栈就足够了吗?类似的东西:

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);
    }
}

答案 1 :(得分:3)

使用像Byte Buddy这样的库进行字节编码可以实现这一点。您可以创建自定义权限,而不是使用标准ReflectPermission("suppressAccessChecks")权限,并使用自定义方法替换AccessibleObject.setAccessible方法,这些方法会使用字节转换检查您的自定义权限。

此自定义权限工作的一种可能方式是使其基于访问者的类加载器和正在修改访问的对象进行访问。使用它允许隔离代码(从与它自己的类加载器分开加载的代码)在其自己的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以使用你的存根。

创建转换器的第一步是创建一个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中时将起作用。必须在运行时加载jar而不是将它们作为标准依赖项或捆绑库复杂化,但这似乎是在使用SecurityManager时隔离不受信任的代码的必要条件。

我的Github repo sandbox-runtime有一个完整的,深入的沙盒运行时环境示例,它执行隔离的不受信任的代码和更具选择性的反射权限。我还有一篇博文,其中详细介绍了selective setAccessible permissions件。

答案 2 :(得分:0)

FWI:由于setAccessible似乎只有一个有效的用例与序列化,我认为你可能经常逃脱完全否定它。

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