8.1.20示例:类型安全性与装载约束
Posted mongotea
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了8.1.20示例:类型安全性与装载约束相关的知识,希望对你有一定的参考价值。
8.1.20示例:类型安全性与装载约束
在Java虚拟机的早期实现中,搞乱Java的类型系统是有可能的。一个Java程序可能欺骗Java 虚拟机,用一种类型的对象冒充另一种类型的对象。这种能力让破坏者非常高兴,因为他们可 以欺骗受信任的类非法访问非公开的数据,或者通过把类替换成新的版本改变方法的行为。比 如,如果一个破坏者编写了一个类,并且成功地愚弄Java虚拟机,让后者认为这个类是 SecurityManager,这样破坏者就可能突破整个沙箱。本节给出的示例用来帮助读者理解委派 类装载器可能带来的类型安全性问题,并说明Java虚拟机规范第2版中提出的装载约束是如何解 决这个问题的。
类型安全性问题的出现是因为在一个java虚拟机中的多个命名空间可能共享类型。如果某个 类装载器委派另外-个类装器,而后者定义了这个类型,这两个类装载器都会被标记为这个类型的初始类装载器。被委派的类装载器装载的这个类型,在所有被标记为该类型的初始类装 载器的命名空间中共享。
在编译时,类型被它的全限定名所惟一确定。比如说,只有一个名为Spoofed的类能够在编译时存在。然而在运行时,仅仅一个全限定名已经不足以惟一地确定被java虚拟机装载的类型了。 因为一个Java程序可能拥有多个类装载器,每个类装载器独有自己的命名空间,具有相同全限定 名的多个类型可能被装载进同一个Java虚拟机。因此,要惟一地确定一个类型,java虚拟机需要 知道类型的全限定名以及这个类型的定义类装载器。
正是因为Java虚拟机早期对编译时一个全限定名可以惟一地确认一个类型的信任,才带来了这 种类装载器结构上可能存在的安全性问题。可以在同一个Java虚拟机里面装载两个类型,它们的名 字都叫做Spoofed,每一个Spoofed类是用一个不同的类装载器定义的。但是耍一些小手腕,就可以 愚弄一个早期的java虚拟机实现,让它认为其中一个Spoofed的实例是另一个Spoofed的实例。
为了解决这个问题,Java虚拟机规范第2版中引入了装载约束的概念。从根本上来说,装载 约束可以让Java虚拟机加强类型安全性,它不仅仅基于全限定名,也基于定义类装载器——而并 非强迫更多的类装载。当虚拟机在常量池解析时发现潜在的类型混淆时,它会在一个内部约束 列表上加上一个约束。以后所有的解析必须满足这个新约束,如同必须满足这个列表上其他的 所有约束一样。
类型混淆问题及其装载约束解决方案的例子如下所示,考虑下面的greeter实现,这是由一个 恶意破坏者编写的:
Java编译器把源代码中的Delegated.getSpoofed ()表达式转换成一个invokestatic字节码指 令(它给出一个指向常量池中CONSTANT_Methodref_info入口的索引)。要执行这个指令,虚拟 机必须解析这个常量池入口。作为解析指向getSpoofed ()符号引用的第一步,虚拟机需要解析 一个CONSTANT_Class_info引用,它的索引在刚才的CONSTANT_Methodref_info人口的 class_index域中给出。这个CONSTANT_Class_info人口是一个指向类Delegated的符号引用。
要解析从Cracker到Delegated的符号引用,虚拟机请求Cracker的定义类装载器装载Delegated。 虚拟机又一次调用了GrecterClassLoader的loadClass ()方法,这一次传递了Delegated这个名字。 然而,因为这一次请求的名宇不是"Spoofed",loadClass ()方法继续向下执行并把这个装载请 求委派给了系统类装载器。因为Delegated.class在linking/ex8目录下,系统类装载器能够装载这个类。系统类装载器被标记为Delegated的定义类装载器,而系统类装载器和GreeterCiassLoader 二者都被标记为初始类装载器。
一旦Delegated被装载了,虚拟机完成了对CONSTANT_Methodref_info的解析,并调用 getSpoofed ()方法。Delegated的getSpoofed ()方法如下:
这段Java源程序看上去是无害的。getSpoofed ()方法只不过是创建了另-个Spoofed对象的实例并且返回指向它的引用。然而在Java虚拟机内部,却对Java的保证安全类型提出了严峻考验。
当Java编译器在类Delegated中遇到new Spoofed ()表达式的吋候,它产生了一个new宇节 码,给出了CONSTANT_Class_info人口的索引(这个入口是一个指向Spoofed的符号引用)。这
和虚拟机在类Cracker中遇到new Spoofed ()表达式时发生的事情一样。当Java虚拟机执行这句 new指令的时候,如同刚才在Cracker的greet ()方法中执行new指令一样,它从解析指向 Spoofed的符号引用开始。虚拟机要求Delegated的定义装载器来装载Spoofed,这个装载器就是 系统类装载器。
虽然这个过程和虚拟机前面解析Cracker的指向Spoofed的符号引用是完全一样的,但是虚拟 机用来装载请求的类装载器却不一样。因为Cracker的定义类装载器是GreeterClassLoader,所以 虚拟机要求GreeterClassLoader来装载Spoofed。但是因为Delegated的定义类装载器是系统类装载 器,所以现在虚拟机要求系统类装载器来装载Spoofed。
因为受信任的Spoofed版本位于随书光盘的linkiiig/ex8目录下,系统类装载器可以读取 Spoofed.class的字节并且传递给defineClass ()。下一步会发生什么就取决于程序是否运行在遵 守装载约束的Java虚拟机中(装载约束在Java虚拟机规范第2版中规定)。
假设程序运行在老版本的Java虚拟机实现中,它没有使用装载约束。在这种情形下, defineClass ()可以用从linking/ex2/Spoofed.class读人的字节来定义类型。虚拟机创建了这个受 信任的Spoofed类型。很快,Delegated的getSpoofed ()方法返回了一个对受信任的Spoofed对象的引用给它的调用者(即Cracker的greet ()方法)。Cracker把这个引用保存在局部变量spoofed 中,进一步打印出调用Spoofed的giveMeFive ()方法的返回值。
在Cracker.java被编译的时候,Java编译器把第二个giveMeFive ()调用转换为另一个 invokevirtual指令,这个指令引用了常量池中的CONSTANT_Methodref_info入口 :指向Spoofed 中giveMeFive ()方法的符号引用。然而当虚拟机解析这个符号引用的时候,它发现这个引用 已经被解析过了。第二次giveMeFive ()调用指定的CONSTANT_Methodref_info人口和第一次 调用指定的是同一个,都解折为恶意的giveMeFive ()实现。虚拟机在受信任的Spoofed对象上 调用了恶意的Spoofed的方法,程序打印出:
secret val=42
虽然这种混淆类型进行攻击的可能性在很多1.2版本之前的java虚拟机中都存在,但是实际上一般不会发生,因为它需要类装载器的配合。在这个例子中,破坏者在GreeterQassLoader的 loadClass ()方法中加人了一条if语句來对Spoofed特别处理。但是如果破坏者试图通过一个不受信任的applet来进行这种类型混淆攻击,他或者她就会遇到麻烦。不受信任的applet不能够创 建类装载器。因此,在把applet装载进浏览器的应用程序中,假设它的类装载器的设计人员做了正确的处理,破坏者就没有办法利用这个Java类型安全性的弱点。
在进行检査装载约束的虚拟机实现中,这已经是Java虚拟机规范的一部分了,类型混淆根本 不可能发生。所有的虚拟机现在都必须保留一张关于已经装载的类型约束的内部列表。比如, 当这样的一个虚拟机解析Cracker的常量池中的CONSTANT_Methodref_info人口的时候,如果这 个人口是一个指向类Delegated的getSpoofed ()方法的符号引用,虚拟机记下了一个装载约束。 因为Delegated和Cracker相比是被另外一个类装载器装载的,而Delegated的getSpoofed ()方法 返回了一个指向Spoofed的引用,虚拟机记下了这样一个约束:
系统类装栽器(Delegated的定义类装栽器)被标记为Spoofed类型的初始类装栽器, GreeterClassLoader( Cracker的定义类装栽器)也被标记为Spoofed类型的初始类装栽器,
这两个类型必须是同一个类型。
这个约束以后就被用到了,当虚拟机解析Delegated的CONSTANT_Class_info入口,后者指 向一个类Spoofed的符号引用时,这一次,虚拟机发现约束被违反了。这个即将要被系统类装载 器装载的名为Spoofed的类型并不是那个以前被(GreeterClassLoader装载的同名类型。因此,Java 虚拟机抛出了一个LinkageError异常:
Exception in thread "main" java.lang.LinkageError:Class Spoofed violates loader
constraints
Java对于类型安全性的保证是其安全模型的基础。类型安全性意味着程序只允许按照类所设 计的那样处理在栈中的对象实例占据的内存。同样,类型安全性意味着程序只允许按照类所设 计的那样处理在方法区中类的静态变量所占据的内存。如果虚拟机可能混淆类型,如同这个例 子所演示的,恶意的代码就可能看到或者修改非公开的变量。除此之外,如果恶意的代码可能 使用某个类型版本中返回一个int变量的方法,然后在另一个版本的这个类型中解释这个int返回 值是一个数组,恶意的代码就可以把一个int转换成一个数组引用。通过这种伪造的指针,恶意 的代码可以制造一场浩劫。因此,给Java的类型安全性提供保障是很重要的。装载约束就可以保 证,就算在存在多个命名空间的情况下,java的类型安全性在运行时也要坚持。
以上是关于8.1.20示例:类型安全性与装载约束的主要内容,如果未能解决你的问题,请参考以下文章