Intel MPX指令研究及其应用*
2021-11-23彭茜珍
彭茜珍,胡 莉
(湖北科技学院 学报编辑部,湖北 咸宁 437100)
在现代程序设计中,经常会涉及大量数据的处理,通用的方式就是在内存中开辟固定大小的存储区域作为缓冲区,并将待处理数据放入其中,以便程序对其进行各种处理,由于编程中难免出现错误,当代码向缓冲区内写入的数据量超过其大小时,就会发生溢出现象,攻击者可以利用这个漏洞,通过用精心设计的一段入侵代码覆盖缓冲区之外的内存区域,这些入侵代码就可能被CPU执行,从而危及系统的安全。本文提出了一种基于Intel MPX指令集监测技术可以预防此类危害系统的事情发生。
一、引言
缓冲区溢出是软件代码中常见的漏洞[1,2],对系统的安全带来隐患。Intel内存保护扩展(Intel MPX)可用于消除此类缺陷,它于2013年首次发布,并于2015年底作为Skylake微体系结构的一部分推出的一项新功能。使用Intel MPX 并与修改后的编译器相结合,可以检查内存引用,提高软件的稳健性,还可以使编译时正常运用这些引用在运行时由于缓冲区上溢或下溢而不被恶意攻击。Intel MPX还提供了与旧式的软件组件相兼容的机制。
Intel MPX 被设计成允许系统(即逻辑处理器和操作系统软件)运行支持Intel MPX 的软件以及传统软件(为没有Intel MPX 的处理器编写)。在执行包含Intel MPX 未知代码(传统代码)和Intel MPX 启用代码混合的软件时,传统代码不会受到Intel MPX 影响,它类似于在指令流中嵌入 NOP操作,不会发生任何功能变化或性能下降。
开启了Intel MPX 的指令代码会从针对缓冲区溢出等漏洞的内存保护中受益。因此,软件开发者想采用这项技术的内在动力很高。同时,由于Intel MPX 保护带来的安全优势可以根据开发者的业务优先级来实施。开发者可以选择在某些模块中采用Intel MPX以快速实现Intel MPX 的部分优势,并且分阶段在其他模块中引入Intel MPX(例如,传统调用的接口处)。Intel MPX 的这种自适应特性旨在让软件开发者控制他们的规划和模块化。它还可使开发者首先保护更高优先级或更容易受到攻击的软件,并在软件工程的某个阶段(例如,测试)中使用Intel MPX 功能,而不是在另一个阶段(例如,发布)中由业务现实决定采用Intel MPX 功能。
像任何指令集扩展一样,应用程序开发者不但使用Intel MPX检测缓冲区溢出,还使处理器可以使用Intel MPX 做缓冲区溢出检测之外的事情。
二、Intel MPX指令编程环境
为了支持检测缓冲区溢出缺陷的硬件特性,Intel MPX引入了新的边界寄存器,以及在这些寄存器上运行的新指令集扩展。此外,还定义了一组新的“边界表(bound table)”,使其可以存储超出边界寄存器范围的边界。这些硬件特征的具体内容如下[3]:
1.边界寄存器
共4个,它们是BND0、BND1、BND2和BND3,128位长,保存指针变量的下限和上限(每个界限64位宽),上限在架构上是以1的补码形式表示。下限 = 0,上限 = 0(全 1 的 1 的补码)将允许访问整个地址空间。当下限和上限均为 0(覆盖整个地址空间)时,该边界被视为INIT。它们用于支持Intel MPX指令。
2.配置寄存器和状态寄存器
共3个,两个配置寄存器和一个状态寄存器,两个配置寄存器是为用户模式(CPL = 3)和管理模式(CPL < 3)定义的。配置寄存器用于设置边界目录的基地址(线性地址)和开启Intel MPX功能。状态寄存器用于报错,并提供错误代码。
3.越界异常
当程序运行过程中出现指针引用不正确时,CPU触发越界异常#BR,借助于此异常可以及时处理缓冲区溢出等漏洞,保护内存不受侵犯。
4.新指令
共7条,它们是:
1)BNDCL bnd, r/m,如果r/m中的地址低于bnd.LB中的下限,则生成#BR异常。
2)BNDCU bnd, r/m,如果r/m32中的地址高于bnd.UB中的上限(以1的补码形式表示bnb.UB),则产生#BR异常。
3)BNDCN bnd, r/m,如果r/m中的地址高于bnd.UB中的上限(bnb.UB不是1的补码形式),则产生#BR异常。
4)BNDMOV b, b/m,从内存或边界寄存器复制/加载LB和UB界限。
5)BNDMOV b/m, b,把在边界寄存器中LB和UB界限存储到内存或其他寄存器中。
6)BNDLDX b, mib,利用sib-addressing表达式mib,使用地址转换加载边界。
7)BNDSTX mib, b,利用sib-addressing表达式mib,使用地址转换存储边界。
利用这些硬件设施,Intel MPX特别适用于其与旧代码的向后兼容性和互操作性。 一方面,MPX检测代码可以在传统硬件上运行,因为MPX指令在旧架构上被解释为NOP,这简化了二进制文件的分发,可以将同一个支持MPX的程序/库分发给所有客户端。另一方面,MPX全面支持与未修改的旧代码进行互操作:(1)BNDPRESERVE配置位允许传递由旧代码创建的没有边界信息的指针,(2)当旧代码更改内存中的指针时,这个指针的bndldx注意到了变化,并为它分配了总是为真(INIT)的界限。在这两种情况下,在旧代码中创建/更改的指针被认为是“无边界的”:这允许互操作性。
三、Intel MPX关键指令原理的研究
Intel MPX关键指令是BNDLDX和BNDSTX[4]。在应用程序可以包含数百个指针,并且四个BND寄存器不足以存储此指针数量的边界。这时,借助于边界目录及其条目,该条目指向的边界表用于存储其边界和其他指针值。边界表是一个边界表条目数组,它是64位长的4元组,存储指针值,缓冲区的下限,缓冲区的上限和保留字段,如图1所示。
图1 MPX边界目录和边界表条目
当使用bndstx和bndldx时,边界存储在使用两级地址转换方案计算的存储器位置,以便存储/加载指针边界,地址转换方案如图2所示。
图2 两阶段地址转换(64位模式)
在第一阶段,必须加载相应的BD条目。为此,CPU:(1)从指针地址的位20-47中提取BD条目的偏移量并将其移3位(因为所有BD条目都是23位长),(2)加载BD的基址,它来自BNDCFGx(特别是用户空间中的BNDCFGU和内核模式中的BNDCFGS)寄存器,以及(3)对基数和偏移量求和并从结果地址加载BD条目。
在第二阶段,CPU:(4)从指针地址的第3-19位提取BT条目的偏移量并将其移5位(因为所有BT条目都是25位长),(5)移位这个加载的条目——对应于BT的基址除以3以便去除包含在前3位中的元数据,(6)对基址和偏移量求和,(7)最终从结果地址加载BT条目。 注意,BT条目具有附加的“指针”字段 —— 如果实际指针值和该字段中的值不匹配,则MPX将边界标记为始终为真(INIT)。这是与传统代码进行互操作所必需的,并且仅在某些传统代码修改指针时才会发生。
四、Intel MPX指令应用
Intel MPX指令基于处理器硬件设施,可以实现快速检测边界越界并引发#BR异常,借助于异常处理,避免系统受到危及。特别是可以透明地为传统版C/C++程序添加边界检查。下面给出Intel MPX指令应用举例及指导,请探讨下列C语言程序段:
struct object
{
char buffer[100];
int len;
}
1: object * a[20] // 指向object的指针数组
2: sum = 0
3: for (i=0; i 4: ai = a + i // 在a上做指针算术运算 5: objectptr = load ai // a[i]是指向object的指针 6: lenptr = objptr + 100 // 指向object.len的指针 7: len = load lenptr 8: sum += len } // 所有object的总长度 这段程序分配一个指针数组a[20],其中每个指针指向一些object类型的缓冲区对象(第1行)。接着,它遍历数组的前N个元素以计算对象长度值的总和(第3-8行)。在C程序中,这个循环可以这样写: for (i=0; i sum += a[i]->len; } 注意如何访问数组元素a[i]请看第4行的指针ai,以及如何访问其子字段请看第6行的lenptr。 当应用Intel MPX保护后,这段程序将转换为以下代码: 1: object * a[20] 2: a_b = bndmk a, a+159 // 生成边界[a, a+159] 3: sum = 0 4: for (i=0; i 5: ai = a + i 6: bndcl a_b, ai // 监测a[i]的下限 7: bndcu a_b, ai+7 // 监测a[i]的上限 8: objectptr = load ai 9: objectptr_b = bndldx ai // a[i]作为指针的边界 10: lenptr = objptr + 100 11: bndcl objectptr_b, lenptr // 监测object.len的下限 12: bndcu objectptr_b, lenptr+3 // 监测object.len的上下限 13: len = load lenptr 14: sum += len } 首先,在第2行创建数组a[20]的边界(数组包含20个指针,每个指针宽8个字节,因此上限界限为159)。然后在循环中,在第8行的数组元素访问之前,插入两个MPX边界检查以检测a[i]是否溢出(第6-7行)。请注意,由于受保护的加载从内存中读取一个8字节的指针,因此检查ai + 7与上限(第7行)非常重要。 既然指向对象的指针是在objectptr中加载的,程序想要加载object.len子字段。按照设计,MPX必须通过检查objectptr指针的边界来保护第二个加载。在MPX中,存储在存储器中的每个指针都有其相关的边界,也存储在由bndstx和bndldx MPX指令访问的特殊存储区中(前述)。因此,当从存储器地址ai检索objectptr指针时,使用来自相同地址的bndldx检索其对应的边界(第9行)。最后,在第11-12行加载长度值之前,插入两个边界检查。 由于这些Intel MPX指令可以引发#BR异常。因此,在操作系统级别,增加了一个新的#BR处理程序,它具有两个主要功能:(1)按需分配存储区域;(2)每当检测到边界违规时向主程序发送越界异常#BR。 一方面,借助于编译器、运行时库和操作系统的支持,Intel MPX 通过检查指针引用为软件带来了更高的安全性,杜绝缓冲区溢出在运行时被恶意利用的缺陷。另一方面,Intel MPX 的突出特点是其与传统代码的向后兼容性和互操作性。这就为软件开发者带来了一项新的设计技术。五、结语