JVM的安全检查机制

前阵子写了个组件然后发现类no signer certificates,那么我就看看咋加一个吧,加之前说下JVM机制

JVM在执行字节码的时候需要经过下边的步骤:
由类加载器(Class Loader)负责把类文件加载到Java虚拟机中(.class),在这个过程需要校验该类文件是否符合类文件规范 。字节码校验器(Bytecode Verifier)检查该类文件的代码中是否存在着某些非法操作 ,如果字节码校验器校验通过,就由Java解释器负责把该类文件解释成机器码进行执行 。
  JVM在上边操作过程使用了“沙箱”模型,即把Java程序的代码和数据都限制起来放在一定的内存空间执行,不允许程序访问该内存空间以外的内存。这种访问过程不仅仅是本地的,也可以是远程的,最明显的体验是使用RMI的时候。
  [b]一:“双亲委派类加载模型”:[/b]
  双亲委派方式,指的是优先从顶层启动类加载器,自定向下的方式加载类模型,这种方式在“沙箱”安全模型里面做了第一道安全保障;而且这样的方式使得底层的类加载器加载的类和顶层的类加载器的类不能相互调用,哪怕两种类加载器加载的是同一个包里面的类,只要加载的时候不属于同一个类加载器,就是相互隔绝的,这样的操作称为JVM的“安全隔离”。
  [b]二:字节码校验:[/b]
  字节码校验过程需要经过四个步骤:
检查class文件的内部结构是否正确,主要是检查该文件是否以某个内存地址打头一般为0xCAFEBABE,但是可能32bit和64bit存在一定的差异,通过这样的检查可以使得被损坏的class文件或者伪装的class文件不能够被加载,可以标记为不安全的。第一趟扫描的主要目的是保证这个字节序列正确的定义了一个类型,它必须遵从java class文件的固定格式,这样它才能被编译成在方法区中的(基于实现的)内部数据结构。
检查是否符合JVM语言特性里面的编译规则,因为Java里面所有的Class都是从Object类继承过来的,一旦发现这种不安全的类存在,就直接抛异常。这次检查,class文件检验器不需要查看字节码,也不需要查看和装载任何其他类型。在这趟扫描中,检验器查看每个组成部分,确认它们是否是其所属类型的实例,它们结构是否正确。比如,方法描述符(它的返回类型,以及参数的类型和个数)在class文件中被存储为一个字符串,这个字符串必须符合特定的上下文无关文法。另外,还会检查这个类本身是否符合特定的条件,它们是由java编程语言规定的。比如,除Object外,所有类都必须要有一个超类,final的类不能被子类化,final方法也没有被覆盖,检查常量池中的条目是合法的,而且常量池的所有索引必须指向正确类型的常量池条目。
检查字节码是否导致JVM崩溃掉,这里存在一个JVM中断的问题,编程过程无法捕捉Error,同样的没有办法判断程序是否因为执行的class字节码中断或者崩溃。字节码流代表了java的方法,它是由被称为操作码的单字节指令组成的序列,每一个操作码后都跟着一个或多个操作数。执行字节码时,依次执行操作码,这就在java虚拟机内构成了执行的线程,每一个线程被授予自己的java栈,这个栈是由不同的栈帧构成的,每一个方法调用将获得一个自己的栈帧——栈帧其实就是一个内存片段,其中存储着局部变量和计算的中间结果,用于存储中间结果的部分被称为操作数栈。字节码检验器要进行大量的检查,以确保采用任何路径在字节码流中都得到一个确定的操作码,确保操作数栈总是包含正确的数值以及正确的类型。它必须保证局部变量在赋予合适的值以前不能被访问,而且类的字段中必须总是被赋予正确类型的值,类的方法被调用时总是传递正确数值和类型的参数。字节码检验器还必须保证每个操作码都是合法的,即都有合法的操作数,以及对每一个操作码,合适类型的数值位于局部变量中或是在操作数栈中。这些仅仅是字节码检验器所做的大量检验工作中的一小部分,在整个检验过程通过后,它就能保证这个字节码流可以被java虚拟机安全的执行。
检查符号引用验证,一个类文件,它会包含它引用的其他类的全名和描述符,并跟他们建立符号引用(一种虚拟的,非物理连接的方式)。当程序第一次执行到需要符号引用的位置时,JVM会检查这个符号链接的正确性,然后建立真正的物理引用(直接引用)。java虚拟机将追踪那些引用——从被验证的class文件到被引用的class文件,以确保这个引用是正确的。这次扫描可能要装载新的类。考虑到虚拟机实现上的差别,第四趟扫描可能紧随第三趟扫描发生,也有可能在第三趟扫描之后很久,当字节码被执行时才执行。动态连接是一个将符号引用解析为直接引用的过程。当java虚拟机执行字节码时,如果它遇到一个操作码,这个操作码第一次使用一个指向另一个类的符号引用,那么虚拟机就必须解析这个符号引用。在解析时,虚拟机执行两个基本任务:
——查找被引用的类(如果必要的话就装载它)
——将符号引用替换为直接引用,例如指向一个类、字段或方法的指针或偏移量
虚拟机必须记住这个直接引用,这样当它以后再次遇到同样的引用时,就可以直接使用,而不需要重新解析该符号引用了。
  [b]三:内置于JVM的安全特性:[/b]
  在执行期,除了符号引用验证,JVM还会对一些内建的安全特性进行检查
类型安全的引用转化
结构化的内存访问(非指针算法)
GC
数组边界检查
空引用检查(NullPointer)
  强制内存的结构话访问,避免了恶意用户在了解内存分布的情况下通过指针对JVM的内部结构进行破外。当然要了解JVM的内存分布也不是易事。JVM对运行时数据空间的分配是一个黑盒过程,完全由JVM自己决定如何分配,在class中没有任何相关的信息。
  字节码检查的局限就是对于那些不经过字节码检查的方法(如本地方法:native method)无法验证其安全性,所以这里采用的是对动态链接库的访问控制,对于那些足有足够可信度的代码才被允许访问本地方法。具体的实现是,由安全管理器来决定代码是否有调用动态链接库的权限,因为调用本地方法必须调用动态链接库。这样一来,不可信的代码将无法通过调用本地方法来绕过字节码检查。
  [b]四:安全管理器SecurityManager:[/b]  这一步是编程中最接近程序的一个环节,SecurityManager存在于JavaAPI里面,是可以通过编程来完成安全策略管理的,默认情况下,Java应用是不设置SecurityManager实例的,但是这个实例却需要我们在启动的时候通过System.setSecurityManager来进行设置。一般情况下,调用了SecurityManager.checkPermission(Permission perm)来完成的,外部程序可以创建一个权限实例Permission来进行Check操作。主要应用为:
默认安全管理器:java.lang.SecurityManager
代码签名和认证
策略:java.security.Policy
权限:java.security.Permission
策略文件
保护域:CodeSource,PersimissionCollection,ProtectionDomain
访问控制器:java.security.AccessController
  五:Java签名/证书机制:
  Java签名机制使得使用者可以安全调用外部提供的jar文件,签名必须是基于jar包的,也就是如果要使用安全模型必须要将提供的class文件打包成jar,然后使用JDK工具(jarsigner)对jar进行签名。新安全平台中对足够信任度的代码放宽了限制,要获得充分信任须通过代码签名来实现,若我们对某一签名团体足够信任,比如SUN,那么具有该团体签名的代码将被给予充分信任。一个基本的思路大致为,代码发布者创建私钥/公钥对,然后利用私钥对发布代码进行签名,代码使用方在获得代码发布者提供的公钥后对代码进行验证,确认代码确为该提供者提供以及在发布后未经非法修改。这其中存在一些潜在的危险,既是公钥是否是该代码发布者提供的,恶意用户可能替换掉合法的公钥,这会导致用户将给与恶意用户发布的代码以充分信任,目前的常见做法是通过一些权威的证书机构来发布证书而不是公钥,代码发布者被证书发布机构认证合格后,可以将自己的公钥交付证书发布机构,证书发布机构再通过私钥加密该公钥,从而生成证书序列。这样替换公钥的可能性就变得微乎其微。不过世上无绝对安全之事,通过证书机构发布的证书也不是绝对安全的途径。
  证书是在签名基础上,对签名值,再进一步做一次加密。而这次加密使用的私钥和公钥都是证书机构提供的。这种方式,是为了防止,有些恶意用户,在公钥发到你手上前,就对其做了手脚,然后再发一个动过手脚的jar给你,用动过手脚的公钥解动过手脚的jar包,是可以解开的。而使用证书后,它会对已经加密的签名值,再做一层加密,这样,到你手里,你只需要通过证书机构的公钥进行解密,然后再用jar包发布者的公钥解密就行了。 


 
### JVM 类加载机制的工作原理 JVM 的类加载机制是一个复杂的流程,它负责将 Java 编译后的 `.class` 文件加载到内存中并将其转化为可由 JVM 使用的 `java.lang.Class` 实例。整个过程分为多个阶段,每个阶段都有其特定的功能和作用。 #### 一、类加载器的作用与分类 类加载器是 JVM 中用于加载类文件的核心组件之一。它的主要职责是通过类的全限定名找到对应的二进制数据,并将其加载到内存中[^1]。根据功能的不同,类加载器可以划分为以下几类: - **启动类加载器 (Bootstrap ClassLoader)** 启动类加载器是由 C/C++ 实现的原生代码构成,位于 JVM 内部。它主要用于加载 Java 核心类库,例如 `rt.jar` 和其他基础资源包。由于它是 JVM 自身的一部分,因此无法直接通过 Java 程序对其进行操作[^3]。 - **扩展类加载器 (Extension ClassLoader)** 扩展类加载器继承自 `java.lang.ClassLoader`,通常会加载存放在 `$JAVA_HOME/lib/ext` 或者由系统属性 `java.ext.dirs` 指定目录中的 JAR 包[^1]。 - **应用程序类加载器 (Application ClassLoader)** 应用程序类加载器也被称为系统类加载器,负责加载用户定义的应用程序类路径(即 `-classpath` 或环境变量 `CLASSPATH`)上的类文件[^1]。 #### 二、双亲委派机制 为了保证类的安全性和一致性,在类加载过程中采用了 **双亲委派机制**。该机制规定当某个类加载器收到类加载请求时,不会立即尝试自己去完成加载工作,而是先把这个请求委托给父级类加载器处理;只有当父级类加载器无法满足需求时才会自行查找并加载所需类。 这种设计的好处在于避免重复加载相同名称但不同版本的类,从而防止潜在冲突问题的发生。 #### 三、类加载的具体流程 类加载的整体流程主要包括以下几个重要阶段: ##### 1. 加载阶段 在此阶段,JVM 将 .class 文件的内容读取至内存之中形成相应的 java.lang.Class 对象表示形式。具体来说就是从磁盘或其他存储介质获取目标类型的二进制字节流,并将其放置于方法区(Method Area)内创建一个代表此类型的对象实例[^2]。 ```python def load_class(class_name): try: with open(f"{class_name}.class", 'rb') as file: byte_stream = file.read() # Convert the binary stream into a class object within Method Area. return create_class_object(byte_stream) except FileNotFoundError: raise Exception("Class not found.") ``` ##### 2. 验证阶段 验证是为了确保所加载的类符合当前虚拟机的要求而不危害安全或造成错误行为。这一步骤涉及多种检查项目,比如元信息格式检验、语义分析以及字节码验证等[^2]。 ##### 3. 准备阶段 准备阶段为静态字段分配内存空间并设置初始值(通常是零值),但是此时并不会执行任何初始化逻辑代码[^2]。 ##### 4. 解析阶段 解析是指将常量池内的符号引用替换为直接引用的过程。包括但不限于类及接口、字段、方法等方面的解析动作[^2]。 ##### 5. 初始化阶段 最后进入初始化阶段,这是真正开始执行用户的业务逻辑的地方。在这个环节里会对所有的 static 变量赋予指定初值并且调用 static 方法块[^2]。 --- ### 总结 综上所述,JVM 的类加载机制不仅包含了多层次的类加载器体系结构还融入了高效的双亲委派模式以保障系统的稳定可靠运行。同时完整的五步加载流程——加载 -> 验证 -> 准备 -> 解析 -> 初始化,则进一步细化和完善了整个机制的设计思路[^1].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值