我目前正致力于开发一个小型Java应用程序,其中必须与不受信任的代码一起运行受信任的代码。为此,我安装了一个自定义SecurityManager
,只要检查权限就会抛出SecurityException
。
作为可信代码和不可信代码之间的桥梁,我有一个使用Constructor.newInstance()
实例化不受信任类型的对象的线程。在进行此调用时,安全管理器配置为阻止所有内容。有趣的是,我尝试使用Constructor.newInstance()
创建对象的前15次,一切正常,但第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)
对象,该对象打印出到目前为止生成的实例数。在我的系统上,该程序的输出如下:
createClassLoader
根据the Javadoc for RuntimePermission
,createClassLoader
权限是一个有风险的许可:
这是一项非常危险的许可。可以实例化自己的类加载器的恶意应用程序然后可以将自己的恶意类加载到系统中。这些新加载的类可以由类加载器放入任何保护域,从而自动授予类该域的权限。
我有两个问题:
具体导致此错误的原因是什么?为什么在第16次,我得到一个类加载器的请求?我怀疑这与Java试图通过生成字节码来直接实例化对象来优化反射有关,但我不确定。
没有将{{1}}权限列入白名单,这是危险的,有没有办法从可信代码中实例化不受信任的对象?
我是否从根本上以错误的方式接近这个?
谢谢!
答案 0 :(得分:12)
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 });
至于授予该权限,您仍然可以选择为您的代码仔细构建保护域,您可以向其授予权限,而不会将其授予外部代码。