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

发布于 2024-08-22 17:43:08 字数 385 浏览 6 评论 0原文

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

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

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

Is there any way for a SecurityManager in Java to selectively grant ReflectPermission("suppressAccessChecks") depending on the details of what setAccessible() is being called on? I don't see any way for this to be done.

For some sandboxed code, it would be very useful (such as for running various dynamic JVM languages) to allow the setAccessible() reflection API to be called, but only when setAccessible() is called on a method/field of a class that originates in the sandboxed code.

Does anyone have any alternative suggestions other than selective granting of ReflectPermission("suppressAccessChecks") if this isn't possible? Perhaps it would be safe to grant in all cases if SecurityManager.checkMemberAccess() is sufficiently restrictive?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

不再见 2024-08-29 17:43:08

也许查看调用堆栈就足以满足您的目的?像这样的东西:

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

Maybe looking at the call stack would be enough for your purposes? Something like:

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);
    }
}
拥有 2024-08-29 17:43:08

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

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

这样的权限可能看起来像:

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 存储库 sandbox-runtime 有一个完整、深入的沙盒运行时环境示例,其中包含执行隔离的不可信代码和更具选择性的反射权限。我还有一篇博客文章,其中详细介绍了 选择性设置可访问权限件。

This is possible using byte code weaving with a library like Byte Buddy. Instead of using the standard ReflectPermission("suppressAccessChecks") permission, you can create a custom permission and replace the AccessibleObject.setAccessible methods with custom methods that check your custom permission using Byte Buddy transformations.

One possible way for this custom permission to work is for it to base access on the classloader of the caller and the object that access is being modified on. Using this allows isolated code (code loaded from a separate with it's own class loader) to call setAccessible on classes in its own jar, but not Standard Java classes or your own application classes.

Such a permission might look like:

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

This is how I chose to implement this permission, but it could instead be a package or class whitelist or blacklist.

Now with this permission you can create a stub class that will replace the AccessibleObject.setAcessible method to instead use this permission.

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

With these two classes in place you can now use Byte Buddy to build a transformer for transforming the Java AccessibleObject to use your stub.

The first step to create the transformer is to create a Byte Buddy type pool that includes the bootstrap classes and a jar file containing your stubs.

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

Next use reflections to get a reference to the AccessObject.setAccessible0 method. This is a private method that actually modifies the accessibility if the call to setAccessible passes permission checks.

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

With these two pieces the transformer can be built.

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

The final step is to then install the Byte Buddy Java agent and perform the transformation. The jar containing the stubs must also be appended to the bootstrap class path. This is necessary because the AccessibleObject class will be loaded by the bootstrap loader, thus any stubs must be loaded there as well.

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

This will work when using a SecurityManager and isolating both the stubs classes and the code that you are applying the selective permissions to in separate jars that are loaded at runtime. Having to load the jars at runtime rather than having them as standard dependencies or bundled libs complicates things a bit, but this seems to be a requirement for isolating untrusted code when using the SecurityManager.

My Github repo sandbox-runtime has a full, in-depth, example of a sandboxed runtime environment with execution of isolated untrusted code and more selective reflection permissions. I also have a blog post with more detail on just the selective setAccessible permissions pieces.

洋洋洒洒 2024-08-29 17:43:08

FWI:由于 setAccessible 似乎只有一个有效的序列化用例,我认为您可能经常会直接否认它。

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

FWI: Since setAccessible seems only to have a valid use-case with serialization, I would think you might often get away with simply denying it outright.

That said, I am interested in how one does this sort of thing in general because I too have to write a security manager to block dynamically loaded code from doing things that our application container code needs to be able to do.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文