我目前正在开发一个小型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;
}
}
}
这个程序安装了一个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)
根据RuntimePermission
的Javadoc,授予createClassLoader
权限是一项风险较高的操作:
授予此权限十分危险。恶意应用程序可以实例化其自己的类加载器并将其自己的恶意类加载到系统中。这些新加载的类可以由类加载器放置在任何保护域中,从而自动授予这些类该域的权限。
我有两个问题:
具体是什么原因导致了这个错误?为什么第16次循环时需要一个类加载器?我猜测这可能与Java尝试通过生成字节码来直接实例化对象以优化反射有关,但我不确定。
在不白名单列出
createClassLoader
特权(这是危险的)的情况下,是否有办法从受信任的代码中实例化不受信任的对象?我是不是从根本上就走错了吗?
谢谢!