为什么我的自定义SecurityManager在第16次使用Constructor.newInstance创建对象时会导致异常?

tem*_*def 10 java security reflection sandbox securitymanager

我目前正在开发一个小型Java应用程序,其中必须与不受信任的代码一起运行受信任的代码.为了实现这一点,我已经安装了一个自定义SecurityManager,它会SecurityException在检查权限时抛出.

作为可信代码和不可信代码之间的桥梁,我有一个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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该程序将安装SecurityManagercheckPermission总是抛出一个异常,无论是什么要求的权限.然后它位于一个循环中,用于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)
Run Code Online (Sandbox Code Playgroud)

根据Javadoc的RuntimePermission说法,createClassLoader许可是一个有风险的许可:

这是非常危险的许可.可以实例化自己的类加载器的恶意应用程序然后可以将自己的恶意类加载到系统中.这些新加载的类可以由类加载器放入任何保护域,从而自动授予类该域的权限.

我有两个问题:

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

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

  3. 我从根本上以错误的方式接近这个吗?

谢谢!

Mar*_*nik 12

在GrepCode上查看:

72  private static int     inflationThreshold = 15;

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

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

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

387  // Load class
388 vec.trim();
389 final byte[] bytes = vec.getData();
390 // Note: the class loader is the only thing that really matters
391 // here -- it's important to get the generated code into the
392 // same namespace as the target class. Since the generated code
393 // is privileged anyway, the protection domain probably doesn't
394 // matter.
395 return AccessController.doPrivileged(
396 new PrivilegedAction<MagicAccessorImpl>() {
397 public MagicAccessorImpl run() {
398 try {
399 return (MagicAccessorImpl)
400 ClassDefiner.defineClass
401 (generatedName,
402 bytes,
403 0,
404 bytes.length,
405 declaringClass.getClassLoader()).newInstance();
406 } catch (InstantiationException e) {
407 throw (InternalError)
408 new InternalError().initCause(e);
409 } catch (IllegalAccessException e) {
410 throw (InternalError)
411 new InternalError().initCause(e);
412 }
413 }
414 });

至于授予该权限,您仍然可以选择为您的代码仔细构建保护域,您可以向其授予权限,而不会将其授予外部代码.