【问题标题】:Why does my custom SecurityManager cause exceptions the 16th time I create an object with Constructor.newInstance?为什么我的自定义 SecurityManager 在第 16 次使用 Constructor.newInstance 创建对象时会导致异常?
【发布时间】:2013-12-29 22:27:45
【问题描述】:

我目前正在开发一个小型 Java 应用程序,在该应用程序中,受信任的代码必须与不受信任的代码一起运行。为此,我安装了一个自定义的SecurityManager,它会在任何时候检查权限时抛出SecurityExceptions。

作为可信和不可信代码之间的桥梁,我有一个线程使用Constructor.newInstance() 来实例化一个不可信类型的对象。在进行此调用时,安全管理器被配置为阻止所有内容。有趣的是,前 15 次我尝试使用 Constructor.newInstance() 创建对象,一切正常,但第 16 次我得到了 SecurityException

我已经设法把它归结为一个简单的测试程序:

import java.lang.reflect.*;
import java.security.*;

public class Main {
    /* Track how many instances have been created so that we can see when the exception
     * is thrown.
     */
    private static int numInstances = 0;
    public Main() {
        System.out.println("Number created: " + ++numInstances);
    }

    public static void main(String[] args) {
        /* Get the constructor for Main so that we can instantiate everything
         * later on.
         */
        Constructor<Main> ctor;
        try {
            ctor = Main.class.getConstructor();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return;
        }

        /* Install a super prohibitive security manager that disallows all operations. */
        System.setSecurityManager(new SecurityManager() {
            @Override
            public void checkPermission(Permission p) {
                /* Nothing is allowed - any permission check causes a     security
                 * exception.
                 */
                throw new SecurityException("Not permitted: " + p);
            }
        });

        /* Continuously create new Main objects. */
        try {
            while (true) {
                ctor.newInstance();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
    }
}

此程序安装一个SecurityManager,其checkPermission 总是抛出异常,无论请求什么权限。然后它位于一个循环中并使用ctor.newInstance() 实例化一个无害的Main 对象,该对象打印出到目前为止生成的实例数。这个程序在我的系统上的输出如下:

Number created: 1
Number created: 2
Number created: 3
Number created: 4
Number created: 5
Number created: 6
Number created: 7
Number created: 8
Number created: 9
Number created: 10
Number created: 11
Number created: 12
Number created: 13
Number created: 14
Number created: 15
java.lang.SecurityException: Not permitted: ("java.lang.RuntimePermission" "createClassLoader")
    at Main$1.checkPermission(Main.java:32)
    at java.lang.SecurityManager.checkCreateClassLoader(SecurityManager.java:611)
    at java.lang.ClassLoader.checkCreateClassLoader(ClassLoader.java:274)
    at java.lang.ClassLoader.<init>(ClassLoader.java:316)
    at sun.reflect.DelegatingClassLoader.<init>(ClassDefiner.java:72)
    at sun.reflect.ClassDefiner$1.run(ClassDefiner.java:60)
    at sun.reflect.ClassDefiner$1.run(ClassDefiner.java:58)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:57)
    at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399)
    at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:396)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:395)
    at sun.reflect.MethodAccessorGenerator.generateConstructor(MethodAccessorGenerator.java:94)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:48)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at Main.main(Main.java:39)

根据the Javadoc for RuntimePermission,授予createClassLoader 权限是有风险的:

这是一个极其危险的授予权限。可以实例化它们自己的类加载器的恶意应用程序然后可以将它们自己的流氓类加载到系统中。类加载器可以将这些新加载的类放入任何保护域中,从而自动授予类对该域的权限。

我有两个问题:

  1. 具体是什么导致了这个错误?为什么在第 16 次时,我收到了一个类加载器的请求?我怀疑这与 Java 试图通过生成字节码来直接实例化对象来优化反射有关,但我不确定。

  2. 如果不将createClassLoader 权限列入白名单,这很危险,有没有办法从受信任的代码中实例化不受信任的对象?

  3. 我从根本上是不是以错误的方式处理这个问题?

谢谢!

【问题讨论】:

  • 我试过你的代码,同样的行为。如果删除 throw 异常并添加打印语句,checkPermission 在创建第 16 个实例之前会被调用 3 次。

标签: java security reflection sandbox securitymanager


【解决方案1】:

查看this at GrepCode:

72 private static int 通货膨胀阈值 = 15;

15 是通胀阈值的默认值,在NativeConstructorAccessorImpl 中引入更积极的优化之前的反射调用计数:

47 if (++numInvocations > ReflectionFactory.inflationThreshold()) {
48 ConstructorAccessorImpl acc = (ConstructorAccessorImpl)
49 MethodAccessorGenerator().
50 generateConstructor(c.getDeclaringClass(),
51 c.getParameterTypes(),
@ 987654336@ c.getExceptionTypes(),
53 c.getModifiers());
54 parent.setDelegate(acc);

并且该特定代码会导致实例化一个新的类加载器,从而导致您在第 16 次迭代时出现异常。字节码生成发生在MethodAccessorGenerator 类中,这是最有趣的一点:

387 // 加载类
388 vec.trim();
389 final byte[] bytes = vec.getData();
390 // 注意:类加载器是唯一真正重要的东西
391 // 这里 - 将生成的代码放入
392 // 与目标类相同的命名空间。由于生成的代码
393 // 无论如何都具有特权,因此保护域可能不
394 // 重要。
395 return AccessController.@ 987654354@(
396 PrivilegedActionMagicAccessorImpl>() {
397 public MagicAccessorImplrun() {398 尝试 {
399 返回 (MagicAccessorImpl)
400 ClassDefiner.defineClass
401 (生成名称,
402 字节,
403 0,
404 字节.长度,
405 declaringClass.getClassLoader()).newInstance();
406 } catch (InstantiationException e) {
407 throw (InternalError)
408 InternalError().initCause(e);
409 } catch (IllegalAccessException e) {
410 throw (InternalError)
411 new @ 987654384@().initCause(e);
412 }
413 }
414 });

至于授予该权限,您仍然可以选择为您的代码仔细形成一个保护域,您授予该权限,而不是授予外国代码。

【讨论】:

  • 侦探 Marko Topolnik,案件!
  • 我无法抗拒的诱惑 :)
  • 这太棒了。如果这是一个愚蠢的问题,我仍在学习保护域并道歉,但是我将如何构建域以防止不受信任的代码使用类加载器,同时让我的受信任代码将其用于 newInstance 调用?或者将整个事情放到一个保护域中,其中类加载器权限被撤销导致内部实现不尝试膨胀?
  • 不幸的是,我花了这段时间思考它,我不知道如何在不传递给最终调用的构造函数的情况下授予 JDK 反射代码必要的权限。您也许可以完全禁用通货膨胀。
  • Java 库确实随机需要权限才能运行。自 1.2 (1998) 以来,通常不值得拥有“自定义”安全管理器。相反,检查会转发到java.security.AccessController。查看示例代码,您的 SecurityManager 可能无法正常工作,因为它似乎不受信任。任何通过它的检查都将失败,因为它没有特权。毫不奇怪,管理信任的代码需要被信任(至少达到信任级别)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多