首页 > 代码库 > 沙箱安全模型、类加载、class文件校验

沙箱安全模型、类加载、class文件校验

本文主要用于自己学习的记录,参考了网上的一些博客,不再一一列出,谢谢前辈们的无私奉献

相比较C++而言,Java具有良好的安全性,使用Java进行开发,可以更好地减少出错的几率,为了使Java程序更健壮,JAVA提供了以下基本组件,组成了安全水箱模型,来为用户提供更好的安全性,也减少开发人员犯错的机率:
1、类装载器结构
2、class文件检验器
3、内置Java虚拟机的安全特性
4、安全管理器及JAVA API
下图显示了各部分包含的内容:

上图中深色的框框内(包含框框),组成了JAVA在运行时的沙箱安全模型。需要说明的是,上图只是为了用于说明JAVA为了实现高安全性,提供了哪些东东,并不是说在JAVA的虚拟机中真的有这个一个范围的划分。
下面我把对各部的理解依次说一下,可能不对,也没怎么深入,慢慢学习中
1、安全管理器与JAVA API
先说API:JAVA提供了大量可用的API,并保证使用这些API是可信任的,没有恶意的。如果我们编写的JAVA程序都是使用JAVA API来完成的,那么JAVA 虚拟机就可以保存使用这些API的程序是安全的。
安全管理器:
在JAVA的安全机制中,类装载器、class文件校验器、内置JAVA虚拟机的安全特性都是用来保存虚拟机加载、运行的程序的完整性,使它们不受恶意或有漏洞的代码的侵犯;尽量保存在程序运行过程中不会出现内存益处、当出现异常或错误时尽量保证其他线程的正常运行(异常时可以抛出而不用终止当前线程,错误时可以中止当前线程,而不影响其他线程),而不影响其他功能的工作。
而安全管理器,它主要是保护虚拟机的外部资源不被虚拟机内运行的恶意或有漏洞的代码侵犯。它定义了沙箱的外部边界。
总的来说,安全管理器的思想是:为要访问的外部资源(如本地文件)提供一个访问控制的策略文件,当访问指定的文件时,强制执行自定义的安全策略进行安全检查。
2、JAVA类加载
在JAVA沙箱中,类装载体系是第一道防线。毕竟,是由类装载器将代码(class文件)-这个代码可能是恶意的或是有漏洞的--装入虚拟机中。
虚拟机再加载类时,才用了双亲委派机制。
在安装的Java虚拟机中,已经包含了三个类加载器,分别是:启动类加载器、扩展类加载器、系统类加载器。从前到后,前面一个依次是后一个的父加载器。从设计模式上说,是采用了代理模式,即子加载器代理了父加载器,默认的一个class文件从系统类加载器开始搜索,但最终加载该类的可能不是它(可能是扩展类加载器或启动类加载器),但对外看起来好像是系统类加载器加载了该类一样。
启动类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录(如安装的jdk下有:jdk1.5.0_04\jre\lib\ext)。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
当然用户也可以实现自己的类加载器,开发人员通过继承java.lang.ClassLoader类来实现自己的类加载器。开发人员编写的类加载器的父加载器就是系统类加载器,这样就组成了类加载器的树状结构:

类加载器的代理模式
类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA和 ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException
了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。

类加载器与 Web 容器

对于运行在 Java EE?容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全

3、class文件校验器
和类装载器一起,class文件校验器保证装载的class文件内容有正确的内部结构,并且这些class文件相互协调一致。如果class文件校验器在class文件中发现了问题,它将抛出异常。好的java编译器不应该产生畸形的class文件,但是java虚拟机并不知道某个特定的class文件是如何被创建的。因为一个class文件实质上是一个字节序列,所以虚拟机无法分辨特定的class文件是由正常的Java编译器产生的,还是由黑客特制的。所以所有的Java虚拟机的实现必须要有一个class文件检验器,文件检验器可以调用class文件以确保这些定义的类型可以安全的使用。
class文件校验器通过四趟独立的扫描来完成它的操作。
3.1 第一趟:class文件的结构检查
第一趟扫描中,对每一段将被当作类型导入的字节序列,class文件检验器都会确认它是否符合Java class文件的基本结构。在这次扫描中,检验器将进行许多检查,例如:每个class文件必须以四个同样的字节开始:魔数0xCAFEBABE。第一趟扫描的主要目的是保证这个字节序列正确地定义了一个类型,它必须遵从Java class文件的固定格式,这样它才能被编译成在方法区中的(基于实现)内部数据结构。第二、三、四趟扫描 不是在符合class文件格式的二进制数据上进行的,而是在方法区中进行的,由实现决定的数据结构上进行的。
3.2 第二趟:类型数据的主义检查
扫描发生在方法区中,主要对于,语义,词法和语法的分析,也就是检查这个类是否能够顺利的编译。在第二趟扫描中,class文件检查器不需要查看字节码,也不需要查看和装载任何其他类型。在这趟扫描中,检验器查看每个组成部分,确认它们是不是其所属类型的实例,它们结构是否正确。例如,方法描述符(它 的返回类型,以及参数的类型和个数)在class文件中被存储为一个字符串。检验器检查方法描述符是否符合特定语法,格式正确。
3.3 第三趟:字节码验证
字节码校验
 在这一趟的校验中涉及两个比较不好理解的概念,第一个是字节码流,第二个是栈帧.
 执行字节码时,一次执行操作码,java虚拟机内构成了执行线程,而每个线程会有自己的java栈,这个栈是由不同的帧栈构成的,每一个方法调用都将获得自己的一个栈帧。
 如果学过汇编的人理解这两个概念会容易一点
 字节码流=操作码+操作数,在这里可以看做汇编里的伪指令+操作数,因为这里的操作码实际上就是给jvm识别的“汇编伪指令”,而操作数的概念和汇编里的除了数据类型,并没有多大的差异
 重点来看一下栈帧,栈帧其实也很好理解,栈帧里有局部变量栈和操作数栈,这两块内存就是放数据的时机不同,操作数栈就是用来存放字节码指令执行的中间结果,结果或操作数,而局部变量区,就是用来存局部变量形参等,这个很好理解
 这个字节码的校验过程校验的就是字节码流的合法过程,也就是校验操作数+操作码的合法性。
 
而java的class文件编码我们之所以称之为字节码,是因为每调条操作指令都只占一个字节,除了两个例外情况,所有的操作码和他们的操作数按字节对齐,这使得字节流在传输的时候跟小,更有优势,这两个例外是这样一些操作码,在操作码和他们的操作数之间会天上一至三个字节,以便操作数都按字节对齐

3.4 第四趟:符号引用的验证
由于大部分jvm的实现都是延迟加载或者说动态链接的,延迟加载的意思就是,jvm装载某个类A时,如果A类里有引用其他的类B,虚拟机并不会把这个被引用B类也同时装载入内存,而是等到执行到的时候才去装载。
 而这个被引用的B类在引用它的类A中的表现形式主要被登记在了符号表中,而第四趟的这个过程就是当需要用到被引用类B的时候,将被引用类B在引用类A的符号引用名改为内存里的直接引用
 所以第四趟发生的时间是不可预料的,而且发生在方法区中。总个这个过程称之为动态连接
 可以简单的划分为两步
 1.查找被引用的类(有必要的话就加载它)
 2.将符号引用替换为直接引用,例如一个指向类、字段或方法的指针,下次再需要用到被引用类的时候直接运用直接引用,不需要再去装载。

4、内置Java虚拟机的安全特性
Java虚拟机装载了一个类,并且对它进行一到三趟的class文件检验,这些字节码就可以运行了。除了对符号引用的检验(第四趟扫描),java虚拟机在执行字节码时还要进行一些其他的安全机制操作。这些机制大多是JAVA语言提供的机制。但同样也是java虚拟机的特性。
类型安全的引用
无指针的结构化访问
自动垃圾收集
数组边界检查
空引用检查

可以说java就是通过以上的安全机制,实现了沙箱的安全要求。

 

沙箱安全模型、类加载、class文件校验