11.3 安全的编程和类加载
Java 程序能从多种源动态加载 Java 类,包括不信任的源,例如能通过不安全的网络访问的网站。动态创建和使用这种动态代码源是 Java 的一大优势和特性。不过,为了让这种机制能正常运作,Java 着重强调了一种安全架构,让不信任的代码能安全运行,而不用担心会损害宿主系统。
Java 的类加载子系统实现了很多安全功能。类加载架构的核心安全机制是,只允许使用一种方式把可执行的代码传入进程——类。
由此我们看到了希望,因为创建新类只有一种方式,即使用 Classloader 类提供的功能,从字节流中加载类。所以,我们可以限制需要保护的攻击面,全力保障类加载过程的安全性。
JVM 的一个特性对此特别有帮助:JVM 是栈机器。因此,所有操作都在栈中执行,而不在寄存器中执行。栈的状态在方法内的任何地方都能推知,这一点可以保证字节码不会破坏安全模型。
JVM 实现的一些安全检查措施如下所示:
类的所有字节码都有有效的参数;
调用所有方法时,传入的参数数量都正确,而且静态类型也正确;
字节码决不能试图上溢或下溢 JVM 栈;
局部变量在初始化之前不能使用;
只能把类型合适的值赋值给变量;
必须考虑字段、方法和类的访问控制修饰符;
没有危险的校正(例如,试图把
int类型的值校正为指针);所有分支指令都指向同一个方法中的有效位置。
其中最重要的是对内存和指针的检查。在汇编语言和 C/C++ 中,整数和指针可以相互转换,所以整数能用作内存地址。使用汇编语言可以编写如下代码:
mov eax, [STAT] ; 从STAT地址中移动4个字节到eax中
Java 安全架构的最底层涉及 Java 虚拟机和它执行的字节码的设计方式。JVM 不允许使用任何方式直接访问底层系统中的单个内存地址,因此 Java 代码无法干扰本地硬件和操作系统。这些特意为 JVM 制定的限制在 Java 语言中也有体现,即 Java 不支持指针或指针运算。
Java 语言和 JVM 都不允许把整数校正成对象引用,反之亦然。而且,无论如何都不能获取对象在内存中的地址。没有这种功能,恶意代码就没了立足之地。
第 2 章说过,Java 的值有两种类型——基本类型和对象引用。只有这两种值能赋值给变量。注意,“对象的内容”不能赋值给变量。Java 没有 C 语言中的结构体(struct),而且传递的始终是值。对引用类型来说,传递的是引用副本——这也是值。
引用在 JVM 中使用指针表示,不过,指针不直接通过字节码处理。事实上,字节码没有用于访问特定位置内存的操作码。
我们所能做的只是访问字段和方法,字节码不能调用任意位置的内存。这意味着,JVM 始终知道代码和数据之间的区别。因此,这样能避免一整类栈溢出和其他攻击。
