UnsafeRust安全化方法

调研结果

主要精读论文如下:

论文 解决问题
Keeping Safe Rust Safe with Galeed ACSAC ’21 利用硬件机制在调用非安全语言FFI时保护安全rust内存
Securing UnSafe Rust Programs with Xrust ICSE 2020 使用类似Sanitizer的方式防止unsafe的rust代码破坏安全rust的内存
Sandcrust: Automatic Sandboxing of Unsafe Components in Rust 基于Unix系统下在调用非安全语言FFI是

通用的思路是隔离unsafe代码能够访问的内存,其中Sandcrust利用Unix下沙盒进程隔离,XRust利用守护页面和指针检查实现,Gallead使用intelMPK和自动生成的外部语言调用RustAPI实现。

Sandcrust: Automatic Sandboxing of Unsafe Components in Rust

背景

rust的FFI的使用破坏了内存安全体系,这是因为FFI必须使用unsafe才能允许的原始指针解引用和不安全函数嗲用,在外部语言运行的过程中rust编译器无法保护可能存在的内存安全问题。

一种常见的FFI调用做法是将FFI调用包装在safe的rust函数中,这样对于主程序而言看起来减少了unsafe操作,但是实际上包装FFI的safe function仍然需要负责相关的数据传递,且可能的对于内存的破坏仍然存在。

主要内容

该文章认为在rust中使用FFI导致不安全因素的主要原因是它们共用了相同的地址空间。为了解决这个问题,该文章提出了一个基于Rust宏的自动化的创建沙箱进程的方法,利用操作系统提供的进程虚拟地址空间能力和Linux的Seccomp BPF对rust主程序和依赖的外部语言编写的库程序进行分离,以实现安全的目标。对于虚拟监视器项目组,该论文可能不太具有参考价值,因为虚拟机监视器如rustShyper整体运行在最底层,不存在一个已有的“操作系统”来管理进程地址空间。

实现方法

研究者设计了一个Rust宏操作应用于FFI的包装函数,该宏对开发者透明,注入调用过程。仅在第一次调用FFI时,宏用fork系统调用创建一个子进程,子进程进入由宏生成的服务循环并等待其余RPC以执行操作。

该论文使用两个Unix管道来发送和接受RPC请求,并提出共享内存方法并没有明显性能优势。对于每个外部函数调用,主进程用管道发送所有参数,子进程接收参数后调用FFI,并将返回时用管道发送回父进程。

总结

该论文提出了基于Unix系统的沙箱结构,只需要向宏中传入用Rust编写的FFI包装函数,即可无感实现地址空间的隔离。但是,该研究提出的方法不适用于类似虚拟机监视器的裸机程序。

Securing UnSafe Rust Programs with XRust

背景

在极少数的unsafe Rust中的非法访问可以破坏所有safe Rust代码的安全性,研究旨在确保safe Rust的数据完整性。

主要内容

该论文首先将Unsafe的Rust进行分类,分为:

  1. 原始指针访问内存,用于base64、缓冲区、vec等

  2. 未检查的类型转换

  3. 手动调整内部状态,如原始指针初始化Vec后手动调整内部状态来设置向量大小

通过分析,作者认为Rust中大多数对象本身是安全的,只需要分区存放不安全的数据即可。同时,作者分析了控制流劫持,认为由于Rust将vtable函数表与数据分别存放,故不安全的堆内存区域中不会包括函数表,进而可以用论文的方式避免控制流劫持攻击。

实现方法

论文中保护安全堆内存的方式是通过数据流分析找到不安全对象,为其分配不安全堆内存空间,在unsafe访问时插入运行时检查或者插入保护页面阻止跨区域访问。

对于一个具体的示例如下:

encoded_size有整数溢出漏洞,会计算出一个小于所需空间的size,进而会写入到buf分配的空间外的内存。该研究会先通过从safe到unsafe的数据流分析发现buf是不安全对象,为其在不安全堆空间分配空间,最后通过在访问时插入运行时检查避免该漏洞。

实现上分为三部分,扩展的堆内存分配api、多区域堆内存分配器、两种运行时保护手段。

堆内存分配api修改:

  • 对于alloc方式分配内存,在Alloc trait中添加unsafe分配入口点

  • 对于box表达式分配内存,添加了一个新的运算符unsafe_box

多区域堆内存分配器扩展:

  • 扩展了ptmalloc2,ptmalloc2是一个多线程内存分配器。为不安全和安全区域分别提供分配区,且不会重用。通过位图记录堆段类型来加速跨区域错误判断(默认情况下每1MB为1段仅需1bit)

运行时保护手段:

  • 插入检查:基于SVF(上下文敏感的指针分析)框架确定所有被不安全代码访问的对象并插入检查。特别地,一旦被标记为不安全,即使在安全区域中的访问也不再安全,需要插入检查。
  • 保护页面:通过在每个堆段上下各插入一个保护页面,当访问到保护页面时会被拒绝。
    • 更高效但不是完全的,因为可以跳过保护也直接访问其它区域。对此,文章提到可以结合Intel MPK / ARM memory domains等基于硬件的保护技术来进行优化。

Keeping Safe Rust Safe with Galeed

背景

目前存在大量从传统非安全语言到Rust的移植开发,如火狐浏览器,这使得代码中存在大量跨语言交互。该研究主要处理不安全语言编写的外部代码对于Rust堆内存的非法访问。

主要内容

首先,该研究将内存访问方向分为四种,来源是Rust代码和C++代码(不安全语言代码),目标内存分为Rust分配的安全空间和不安全代码分配的空间。需要关注的是不安全语言代码对于安全内存空间的访问。

对于这种需要关注的访问,研究者进一步划分为意外访问和有意访问。意外访问指的是由于bug等原因导致的访问,研究者通过堆内存隔离机制对这种访问进行阻止。有意访问指的是外部函数需要访问的Rust地址空间,当发生指针传递时可能发生这种访问,研究者通过对每个Rust结构体属性自动化地创建getter和setter来将访问转移到Rust中来,接受Rust语言的保护。

总的来说,保证了只有Rust可以访问Rust内存,不安全语言在任何情况下对Rust堆内存的访问都会被阻止。

实现方法

对于意外访问的隔离,研究者使用了IntelMPK技术。MPK是英特尔提供的基于非特权指令的线程内存隔离。

其将内存分为16组,页表中的位表示页面所属的组。每个core有一个PKRU寄存器可以用来控制core对每组页面的访问权限共16组1位二进制

二位 权限
00 Read and Write
10 Read only
x1 Deny

通过WRPKRU和RDPKRU来写入/读取PKRU,这两条指令不是特权指令,故与其他平台不同。

PKRU由于可以被恶意代码改写,所以不能完全保护内存,如遇任意指令执行漏洞可以通过改写PKRU来完成攻击。但是对于不能执行任意指令的漏斗如Unsafe Rust对Safe Rust的堆内存破坏,可以通过MPK来阻止,只需要在进入unSafeRust前设置PKRU阻止UnsafeRust代码访问即可。

对于有意访问的保护,可以说时在上述保护之外开了个类似后门的东西,利用rust属性宏自动生成对Rust结构体属性的访问方法。并由FFI调用。为了能够实现对开发者尽可能透明,研究者设计了一个LLVM的PASS用于将对于Rust结构体的访问转化为对相应访问方法的调用,即“伪指针”。

开源情况

开源了论文中提到的LLVMpass和属性宏,但是其又引用了自己多年前研究中开源的libmpk库,所有环境均是老Rust版本且修改了Rust的堆内存分配api以调用libmpk分配页面,对于当前版本配置环境复杂且涉及的libmpk开源不是针对裸机应用设计。

从原理来说其实不需要直接引入其libmpk,对于如虚拟机监视器的项目来说如果有MPK之类的硬件保护措施或许可以直接使用以进行保护。

总结

相对而言这篇论文中介绍的方法是最有可行性的方法。其提出了基于MPK的保护方法,在有高性能的同时满足了外部函数调用安全Rust内存的要求,其自动化生成代码的方式也有可以借鉴学习的地方。其硬件保护内存的相关措施和思想或许可以在hypervisor中有所应用。

相关技术调研

代码安全性相关:ASan(AddressSanitizer)方法

如之前在组会上所讲的内容,sanitizer方法效果类似在前述Xrust论文提到过的“保护页面”+”指针检查“方法。

该方法替换了所有malloc方法从而在申请的内存前后申请额外的“redZone”雷区内存,所有对内存的访问都会经过一次检测,识别出是否访问了雷区内存,从而识别堆栈内存越界。这种检测通过影子内存实现,影子内存和主内存的范围隔离,8字节的主内存对应1字节的影子内存,从而用bit记录内存的“雷区状态”,从而分别访问的内存地址属于正常内存还是雷区内存,进而阻止非法访问。

从性能的角度上来讲,该方法替换了所有的内存分配和内存访问并进行检测,需要频繁进行访问和大量额外的操作,对于内存访问需要至少一次额外的内存访问,这是非常消耗性能的。

从功能的角度上来讲,该方法无法完全隔离内存越界访问。以rust调用的FFI为例,只要存在任意内存访问漏洞,针对隔离方法特别设计的攻击或者大量的扫描可以找到非雷区内存位置并进行越权访问,即使这块内存属于Rust且没有传入FFI。即该方法理论上不能解决unsafe代码中可能的漏洞导致的安全性问题。

对比类似的XRust研究,ASan无法区别内存是否是安全内存空间,而XRust可以区别内存的安全性以保护安全内存。

ARM相关调研

主要针对ARMV7版本进行了初步学习。

寄存器概况

16个32位通用寄存器(R0-R15),4GB虚拟地址空间。

按照约定,其中0-12是通用目的寄存器,R13是SP,R14是LR(Link register),R15是Counter。

另外有CPSR寄存器和SPSR(Saved Program Status Register)两个寄存器。

Banking技术

根据当前所处的模式,寄存器的值可能处于不同物理位置。也就是说,对于某些模式,不会共用User模式下的寄存器。以下描述中“共用”表示与User模式是否共用,不共用则表示该模式下该寄存器有独属的值。

举例来讲,FIQ下访问SP寄存器,访问到的不是User模式下的值,而是FIQ自己的值。对应地,User下访问SP寄存器访问到的是User模式下的值。相当于北航OS课设中切换上下文的硬件实现。

  • R0-R7、R15在所有模式下都共用。
  • SYS和User共用所有通用寄存器。
  • R13(SP),R14(LR)除Sys外不共用。

R13总是代表OS的栈指针。

R14表示返回地址(RA)。进入异常状态时,R14保存了发生异常时的R15。

R15类似北航CO课设的NPC,ARM模式下比当前指令提前8字节,Thumb模式下提前4字节。

PSR(Program Status Register)

CPSR存储当前状态,APSR时用户模式下的CPSR视图。

CPSR存储:

  1. APSR标记
  2. 当前处理器模式
  3. 屏蔽中断标记
  4. 当前处理器模式(ARM/Thumb/…)
  5. 大小端
  6. IT块执行的标记位

进入异常时,CPSR自动拷贝到对应异常模式的SPSR中。

APSR只能访问NZCVQ和GE比特。通常这些比特被分支语句用来保存结果。

CPSRbit视图:

  • 31-27:
    • N:ALU结果取反
    • Z:ALU零结果
    • C:ALU操作进位
    • V:ALU操作溢出
    • Q:累积状态
  • 26-25:
    • IT:空
  • 24:
    • J:核心是否处于Jazelle状态
  • 23-20:保留
  • 19-16:
    • GE:被SIMD指令使用
  • 15-10:
    • IT[7:2]:Thumb2指令集中If-Then条件处理使用
  • 9-5:
    • E:控制load-store字节序
    • A:屏蔽异步中断
    • I:屏蔽IRQ
    • F:屏蔽FIQ中断
    • T:表示核心是否处于Thumb状态
  • 4-0:
    • M:表示处理器模式

用户模式下不能操作M位和AIF位,特权下可以操作M以进入对应状态,通常状态转换随异常事件自动完成。

CP15协处理器

协处理器中有c0-c15十六个主寄存器,用来控制核心的特性。

运行模式

不考虑扩展,有六个特权模式和一个用户模式

  • User 非特权,正常运行
  • FIQ中断
  • IRQ中断
  • Supervisor
  • Abort 内存访问异常
  • Undef 未定义指令异常
  • System 操作系统级别,共享用户寄存器

安全扩展

TrustZone 安全扩展在原有的特权级别之外定义了安全世界和非安全世界,增加了监视模式(Monitor mode),MON中的软件控制两个世界的切换,非安全世界无法访问安全世界中申请的内存。

虚拟化扩展

虚拟机监视器模式(Hyp)在ARMv7中添加,独立于现有的六个特权模式之外。

如果虚拟化拓展被实现,非安全状态时特权级别与之前不同,分为PL0,PL1,PL2三个级别。

  • PL0表示应用程序级别,在User mode中执行。
  • PL1表示所有User mode、Hyp mode之外的的模式中运行,操作系统通常在此处运行。
  • PL2表示Hyp mode,虚拟机监视器运行在此处。虚拟机监视器可以切换运行在PL1的Guest系统,并可以实现多系统共用处理器。

特权级别表示了当前状态下访问资源的能力。

安全扩展和虚拟化扩展共存

Hyp模式运行在非安全模式下、MON运行在安全模式下,仅有Hyp运行在PL2、UserMode运行在PL0,其余所有模式均运行于PL1(无论是否在安全模式下)

识别当前模式

当前模式和执行状态保存在Current Program Status Register(CPSR)中。

总结

通过本学期的学习和调研,我的一些可能比较片面的理解如下:

根据学长的历次组会的报告和交流来看,性能对于虚拟机监视器而言比较重要, 尽力减少unsafe并做好防御性设计是一种较为合理的解决方案。Device Tree的解析库的调用可以视情况使用硬件提供的内存隔离机制。其他的非硬件安全手段相比而言开销很大。

对于可能的隔离机制,Intel方面可以使用已经提供的MPK,ARM方面可以预备使用Armv8.5-A及Armv9-A将引入的[Memory Tagging Extension (MTE)](https://learn.arm.com/learning-paths/smartphones-and-mobile/mte/mte/#:~:text=The Arm Memory Tagging Extension (MTE) is a,safety issues are the primary source of vulnerabilities.)安全性扩展,其提供了类似Intel的内存隔离能力。

Licensed under CC BY-NC-SA 4.0
京ICP备2021032224号-1
Built with Hugo
主题 StackJimmy 设计
vi ./themes/hugo-theme-learn/layouts/partials/footer.html