深入理解Java虚拟机读书笔记之:第3章 安全(1)
程序员文章站
2022-06-24 23:50:43
...
为了解决由网络引起的安全问题,Java体系结构采用了一个扩展的内置安全模型,这个模型随着Java平台的主要版本而不断发展。
为什么需要安全性
Java的安全模型是其多个重要结构特点之一,它使Java成为适于网络环境的技术。因为网络提供了一条攻击连入的计算机的潜在途径 ,因此安全性是非常重要的。
Java安全模型侧重于保护终端用户免受从网络下载的、来自不可靠来源的、恶意程序(以及善意程序中的bug)的侵犯。为了达到这个目的,Java提供了一个用户可配置的“沙箱”,在沙箱中可以放置不可靠的Java程序。沙箱对不可靠程序的活动进行了限制,程序可以在沙箱的安全边界内做任何事,但是不能进行任何跨越这些边界的举动。
基本沙箱
沙箱安全模型使得工作变得容易,即使某个软件来自你不能完全信任的地方。沙箱模型使你可以接收来自任何来源的代码,而不是要求用户避免将来自不信任站点的代码下载到机器上。但是当来自不可靠来源的代码运行时,沙箱限制它进行可能破坏系统的任何动作。不必指出哪些代码可以信任,哪些代码不可以信任,也不必扫描查找病毒,沙箱本身限制了下载的任何病毒或其他恶意的、有漏洞的代码,使得它们不能对计算机进行破坏。
组成Java沙箱的基本组件如下:
1)类装载器结构
2)class文件检验器
3)内置于Java虚拟机(及语言)的安全特性
4)安全管理器及Java API
Java的沙箱安全模型,最重要的优点之一就是这些组件中的类装载器和安全管理器是可以由用户定制的。通过定制这些组件,可以为Java程序创建个性化的安全策略。
类装载器体系结构
在Java沙箱中,类装载器体系结构是第一道防线。毕竟,是由类装载器将代码——这个代码可能是恶意的或是有漏洞的——装入Java虚拟机中。
类装载器体系结构在三个方面对Java的沙箱起作用:
1)它防止恶意代码去干涉善意的代码
2)它守护了被信任的类库的边界
3)它将代码归入某类(称为保护域),该类确定了代码可以进行哪些操作
类装载器体系结构可以防止恶意的代码去干涉善意的代码,这是通过为由不同的类装载器装入的类提供不同的命名空间来实现的。
在Java虚拟机中,在同一命名空间内的类可以直接进行交互,而不同的命名空间中的类甚至不能察觉彼此的存在,除非显示的提供了允许它们进行交互的机制。
在版本1.2中,类装载器请求另一个类装载器来装载类型的过程被形式化,称为双亲委派模式。从版本1.2开始,除启动类装载器以外的每一个类装载器,都有一个“双亲”类装载器,在某个特定的类装载器试图以常用方式装载类型以前,它会先默认地将这个任务“委派 ”给它的双亲——请求它的双亲来装载这个类型。这个双亲再依次请求它自己的双亲来装载这个类型。这个委派的过程一直向上继续,直到达到启动类装载器,通常启动类装载器是委派链中的最后一个类装载器。如果一个类装载器的双亲类装载器有能力来装载这个类型,则这个类装载器返回这个类型。否则,这个类装载器试图自己来装载这个类型。
因为核心Java API的class文件是用于“启动”Java虚拟机的class文件,所以,启动类装载器的名字也则得。
在使用双亲-孩子委派链的方法中,启动类装载器会在最可信的类库——核心Java API——中首先检查每个被装载的类型,然后,才依次到标准扩展、类路径上的本地类文件中检查。
运行时包这个名词,是在Java虚拟机第2版规范中第一次出现的,它指由同一个类装载器装载的、属于同一个包的、多个类型的集合。在允许两个类型之间对包内可见的成员(声明为受保护的或包访问的成员)进行访问前,虚拟机不但要确定两个类型属于同一个包,还必须确认它们属于同一个运行时包——它们必须是由同一个类装载器装载的。
class文件检验器
和类装载器一起,class文件检验器保证装载的class文件内容有正确的内部结构,并且这些class文件相互间协调一致。如果class文件检验器在class文件中发现了问题,它将抛出异常。
因为一个class文件实质上是一个字节序列,所以虚拟机无法分辨特定的class文件是由正常的Java编译器产生的,还是由黑客特制的(黑客可能威胁虚拟机的完整性)。所以,所有的Java虚拟机的实现必须有一个class文件检验器,文件检验器可以调用class文件以确保这些定义的类型可以安全地使用。
class文件检验器实现的安全目标之一就是程序的健壮性。
class文件检验器要进行四趟独立的扫描来完成它的操作。
1)第一趟:class文件的结构检查
第一趟扫描是在类被装载时进行的,主要目的就是保证这个字节序列正确地定义了一个新类型,它必须遵从Java的class文件的固定格式,这样它才能被编译成在方法区中的(基于实现的)内部数据结构。
2)第二趟:类型数据的语义检查
在这趟扫描中,检验器查看每个组成部分,确认它们是否是其所属类型的实例,它们的结构是否正确。例如,方法描述符(它的返回类型,以及参数的类型和个数)在class文件中被存储为一个字符串,这个字符串必须符合特定的上下文无关文法。检验器对每个组成部分进行检查的目的之一是,为了确认每个方法描述符都是符合特定语法的、格式正确的字符串。
另外,class文件检验器检查这个类本身是否符合特定的条件,它们是由Java编程语言规定的。也就是说,class文件检验器在运行时检查了一些Java语言应该在编译时遵守的强制规则。(如,除Object类之外的所有类都必须有一个超类,final类没有被子类化,final方法没有被覆盖等)
3)第三趟:字节码验证
在这趟扫描中,Java虚拟机对字节流进行数据流分析,这些字节流代表的是类的方法。为了理解字节码检验器,必须对字节码和栈帧有一定的了解。
字节码流代表了Java的方法,它是由被称为操作码的单字节指令组成的序列,每一个操作码后都跟着一个或多个操作数。操作数用于在Java虚拟机执行操作码指令时提供所需的额外的数据。执行字节码时,依次执行每个操作码,这就在Java虚拟机内构成了执行的线程。每一个线程被授予自己的Java栈,这个栈是由不同的栈帧构成的。每一个方法调用将获得一个自己的栈帧——栈帧其实就是一个内存片断,其中存储着局部变量和计算的中间结果。在栈帧中,用于存储方法的中间结果的部分被称为该方法的操作数栈。
4)第四趟:符号引用的验证
在动态连接的过程中,如果包含在一个class文件中的符号引用被解析时,class文件检验器将进行第四趟检查。在这趟检查中,Java虚拟机将追踪那些引用——从被验证的class文件到被引用的class文件,以确保这个引用是正确的。因为第四趟扫描必须检查被检测的class文件以外的其他类,所以这次扫描可能需要装载新的类。大多数Java虚拟机的实现采用延迟装载类的策略,直到类真正地被程序使用时才装载。
class文件检验器的第四趟扫描仅仅是动态连接过程的一部分。
动态连接是一个将符号引用解析为直接引用的过程。
二进制兼容
正因为Java程序是动态连接的,所以class文件检验器在第四次扫描中,必须检查相互引用的类之间是否兼容。如果你修改了一个类,Java编译器常会重新编译这些类,从而在编译时检测是否有任何的不兼容性。
Java语言规范中列出了用户可以做的多种改动,这些改动称为二进制兼容性规则。
Java虚拟机中内置的安全特性
1)类型安全的引用转换
2)结构化的内存访问(无指针算法)
3)自动垃圾收集(不必显式地释放被分配的内存)
4)数组边界检查
5)空引用检查
安全管理器和Java API
Java安全模型的前三个组成部分——类装载器体系结构、class文件检验器以及Java中内置的安全特性——一起达到一个共同的目的:保持Java虚拟机的实例和它正在运行的应用程序的内部完整性,使得它们不被下载的恶意或有漏洞的代码侵犯。相反,这个安全模型的第四个组成部分是安全管理器,它主要用于保护虚拟机的外部资源不被虚拟机内运行的恶意或有漏洞的代码侵犯。这个安全管理器是一个单独的对象,在运行的Java虚拟机中,它在访问控制——对于外部资源的访问控制——中起中枢作用。
安全管理器定义了沙箱的外部边界。因为它是可定制的,所以它允许为程序建立自定义的安全策略。当Java API进行任何可能不安全的操作时,它都会向安全管理器请求许可,从而强制执行自定义的安全策略。要向安全管理器请求许可,Java API将调用安全管理器对象的“check”方法。
当Java应用程序启动时,它还没有安全管理器,但是,应用程序通过将一个指向java.lang.SecurityManager或是其子类的实例传给setSecurityManager(),以此来安装安全管理器,这个动作是可选的。
当一个Java API即将进行一个潜在不安全的动作时,它将遵循以下两个步骤。首先,Java API的代码检查有没有安装安全管理器,如果没有安装,则跳过第二步直接继续这个潜在不安全的动作。否则,在第二步中,它将调用安全管理器中的合适的“check”方法。
安全管理器负责两个方面的工作:说明一个安全策略以及执行这个安全策略。
(转载请注明来源:http://zhanjia.iteye.com/blog/1842144)