RISC-V"安全"那些事儿(一)

背景

在数字化浪潮汹涌澎湃的当下,计算机已深度融入人们生活的各个方面,成为社会运转不可或缺的 “中枢神经”。

从清晨唤醒我们的智能设备,到工作中处理的海量数据,再到夜晚休闲时畅享的娱乐内容,计算机的身影无处不在, 它串联起生活的方方面面,已然成为信息交互、数据存储与处理的核心载体。

然而,计算机技术迅猛发展的同时,安全问题也如影随形。

网络空间并非一片净土,恶意软件如同隐匿在黑暗中的幽灵,随时可能入侵计算机,窃取保密信息;黑客们则像狡黠的盗贼,利用系统漏洞,肆意破坏、非法获取数据。这些安全威胁,严重扰乱了我们的数字生活秩序。

“The more connected we get, the more vulnerable we become.”(我们的联系越紧密,就越容易受到攻击)。

软件攻击

如今,许多底层软件(如操作系统)都是用 C 或 C++ 等非内存安全的编程语言编写的,这使得这类软件容易受到软件的攻击,攻击者往往会利用这些漏洞来达到非法的目的,诸如系统提权、隐私窃取等。先简单介绍一下软件常用的几种攻击手段:

  • Buffer Overflow
  • ret2libc/ret2text
  • Return Oriented Programming / Jump Oriented Programming

Buffer Overflow 攻击

对于底层操作系统而言, buffer overflow和memory corruption是漏洞的主要来源,因为操作系统主要是通过C/C++来实现的, 而C/C++并非内存安全的编程语言。


图片来自:https://www.cvedetails.com/vulnerabilities-by-types.php

Buffer Overflow(缓冲区溢出)是一种常见且危害严重的计算机安全漏洞的利用方式。在程序运行过程中,当向缓冲区写入的数据量超过其预先分配的容量时,就会发生缓冲区溢出。

当函数返回地址被覆盖时,攻击者可以将其修改为恶意代码(shellcode)的地址或者程序中其他关键函数的地址, 这使得攻击者能够以与被攻击程序相同的权限执行恶意操作,可能导致系统敏感信息泄露、系统被完全控制等严重后果。

为了防范 Buffer Overflow 攻击,现代操作系统和编译器采用了多种技术。如引入了栈金丝雀(Stack Canary)机制,在函数返回地址和局部变量之间放置一个特定值(canary 值),在函数返回前验证该值是否被修改,若被修改则说明可能发生了缓冲区溢出攻击,程序会立即终止运行,从而防止攻击者进一步篡改返回地址。

同时,非可执行(NX)内存技术,也称为 W⊕X(Write XOR eXecute)或数据执行预防(DEP),为每个内存页面分配一个 NX 位,标记其为可读可执行或不可执行但可写,防止攻击者在数据区域(如栈)上执行恶意代码,有效抵御了传统基于栈的代码注入攻击。

ret2libc/ret2text 攻击

在存在缓冲区溢出漏洞的情况下,当 Data Execution Prevention (DEP) 等机制禁止在数据区(如栈)执行代码时,ret2libc 攻击应运而生。

由于攻击者无法直接执行放置在栈上的 shellcode,于是转向利用程序中已有的库函数来达到恶意目的。

ret2text 攻击则是在程序未开启 Position-independent Code (PIE) 时,利用程序自身代码段中固定的地址来执行恶意操作。

在未开启 PIE 的情况下,程序自身某些关键代码段的地址在每次运行时是固定的,攻击者通过溢出漏洞覆盖函数返回地址,使其跳转到程序自身代码段中可利用的指令序列处。

这些攻击技术的出现促使安全研究人员不断研发新的防御策略,如强化内存保护机制、增强程序的地址随机化(ASLR)程度等,以应对不断变化的安全威胁,保障计算机系统的安全稳定运行。

然而,攻击者也在不断发展新的攻击手段来绕过这些防御机制,如代码重用攻击(CRAs)中的 Return Oriented Programming(ROP)等技术,使得计算机安全防御面临持续的挑战。

ROP/JOP 攻击

ROP(Return-Oriented Programming,返回导向编程)和 JOP(Jump-Oriented Programming,跳转导向编程)是两种先进且具有强大危害性的代码重用攻击技术。

ROP 攻击基于程序中已有的代码片段(gadgets)来构建攻击链。这些 gadgets 通常是位于程序的.text 段等可执行区域内,以 ret 指令结尾的短指令序列。在存在缓冲区溢出等漏洞的情况下,攻击者通过精心构造输入数据,将一系列 gadget 地址和对应的参数填充到栈上。


Example of ROP attack

ROP 攻击的优势在于能够绕过诸如 DEP(Data Execution Prevention)等内存保护机制,因为它利用的是程序自身合法的代码片段,而不是注入新的可执行代码。JOP 攻击与 ROP 类似,但它的 gadgets 是以间接分支(如 jmp 或 call 等)指令结尾,而不是 ret 指令。

JOP 攻击构建在一个 dispatch table(调度表)之上,该表存储着各种 gadgets 的地址和相关数据。攻击者通过控制程序的执行流程,使 CPU 寄存器(通常是一个用作虚拟程序计数器的寄存器)指向 dispatch table 中的不同 gadgets 地址,从而实现连续的 gadget 调用。

ROP 和 JOP 攻击的出现,促使安全研究人员不断探索新的防御技术,如更严格的控制流完整性检查、硬件辅助的安全机制等,以应对这些复杂的攻击手段,保护计算机系统的安全。

RISC-V Control Flow Integrity (CFI)

基于代码重用攻击(CRAs)不需要在受攻击的程序中引入新代码, CRAs基于通过覆盖函数指针和返回地址来转移应用程序的控制流。RISC-V 提供了影子栈(Shadow Stack, Zicfiss)和Landing Pads(Zicfilp) ISA 扩展(统称CFI扩展),通过该扩展来保护程序的控制流完整性。

具体来说,为了保护后向边缘(backward-edges),RISC-V定义一个影子堆栈来存储每个特权级别的返回地址, 主要用来防护ROP攻击;为了保护前向边缘(forward-edges), RISC-V设计了一种基于标签的指令着陆方法(Landing Pads),主要用来防御JOP攻击。

影子栈Zicfiss 扩展引入了影子栈来强制执行后向边缘控制流的完整性。影子堆栈是第二个堆栈,用于将返回地址的影子副本存储在链接寄存器(RA)中。

当启用 Zicfiss 后,每个需要返回地址完整性的函数(通常是非叶子函数)在进入函数(序言)时将链接寄存器值存储到常规堆栈,并将链接寄存器值的影子副本存储到影子堆栈。当此类函数返回(结尾)时,该函数从普通堆栈加载RA,从影子堆栈加载RA的影子副本,然后比较常规堆栈中的RA值和影子堆栈中的RA值。两个值不匹配表示return address被破坏,并触发软件异常。

Ladding pads旨在为使用间接调用和跳转执行的控制执行提供完整性(前向边缘保护)。当 Zicfilp 处于活动状态时,hart 会跟踪预期Ladding pads 的状态(ELP),该状态由 indirect_call 或 indirect_jump 更新,要求是否在分支目标处存在ladding pads指令。如果目标处的指令不是ladding pad指令,则会引发软件异常。

function_entry:

    lpad # loading pad check

    addi sp,sp,-8 # push link register x1

    sd x1,(sp) # on regular stack

    sspush x1 # push link register x1 on shadow stack

        :

    ld x1,(sp) # pop link register x1 from regular stack

    addi sp,sp,8

    sspopchk x1 # fault if x1 not equal to shadow return address

    ret

RISC-V CFI (Shadow Stack and Ladding Pads)Example

Trusted Execution Environment (TEE)

对于各类软件攻击,硬件以及操作系统提供了多种缓解和防御手段, 但是由于软件问题导致的软件漏洞数量每年仍然呈上升趋势。

图片来自:https://www.cvedetails.com/

尽管软硬件安全工程师付出很多的努力去修复漏洞,但是绝大多数底层基础软件都是用内存不安全的语言(如 C 和 C++)编写的,这很容易引入内存漏洞,从而使软件遭受代码重用攻击(CRAs),像缓冲区溢出、Return-Oriented Programming(ROP)和 Jump-Oriented Programming(JOP)等攻击手段频繁出现。

传统的安全机制在应对这些复杂攻击时往往存在局限性,难以全面保障系统的安全,这也导致计算机操作系统每年呈现的缺陷和漏洞每年都在呈上升趋势。

新的攻击手段层出不穷,黑客们不断寻找着系统的薄弱环节,试图突破安全防线,获取非法利益或达成恶意目的。这就如同一场永不停歇的攻防博弈,正应了那句 “道高一尺,魔高一丈”。

与其像一个补锅匠一样拆东墙补西墙,有没有一种更加彻底的方法来杜绝绝大部分的软件攻击呢?

比如,把应用程序放到一个与主系统隔离的环境里,主系统的软件(包括特权软件)、DMA外设都无法获取该环境里软件的执行状态、数据,从而就算再厉害的黑客也无法通过普通的软件手段来获取内部信息。

这是一个非常朴实的想法,而且早就有人这么做了,这便是TEE(Trusted Execution Enviromnent)技术。


The approaches of IoT TEE

Intel 的SGX

(Software Guard Extenstion)

Intel Software Guard Extensions(Intel SGX)是英特尔推出的一项基于硬件的可信执行环境(TEE)技术,旨在为应用程序提供更高级别的安全性,尤其是保护应用程序代码和数据的机密性与完整性。

这个隔离的区域, Intel 称之为Enclave, 它是应用程序地址空间内一个受保护的区域,由 CPU 提供硬件层面的安全隔离。Encalve中的代码和数据在内存中加密存储,只有Enclave内的代码才能够访问,即使操作系统内核、虚拟机监视器或其他特权软件也无法窥探或篡改Enclave的内容。


Intel SGX Enclave示意图

TEE(Tusted Execution Environment)可信执行环境能够为敏感数据和关键应用程序提供一个隔离且受保护的运行空间。它确保在这个环境中执行的代码和处理的数据不会被外部恶意软件或未经授权的实体所干扰或窃取。在物联网领域,众多设备收集和传输大量敏感数据,如医疗设备中的患者健康数据、工业控制系统中的关键生产数据等,TEE 可以为这些设备提供安全的执行环境,确保数据在设备端的处理过程中的安全性,防止数据泄露和恶意控制;在云计算环境中,不同用户的应用程序和数据在共享的硬件资源上运行,TEE 能够隔离用户的工作负载,保证每个用户的隐私和数据安全,防止云服务提供商或其他租户的非法访问。

Trusted Execution Environment: What It is, and What It is Not [2] 这篇论文对 TEE做了详细定义:可信执行环境 (TEE) 是在分离内核上运行的防篡改处理环境。它保证执行代码的真实性、运行时状态(例如 CPU 寄存器、内存和敏感 I/O)的完整性以及存储在持久内存中的代码、数据和运行时状态的机密性。此外,它还应能够提供远程认证,以证明其对第三方的可信度。TEE 的内容不是静态的,它可以安全地更新。TEE 技术主要用于抵御软件攻击以及部分非侵入式物理攻击。


An Overview of TEE Building Blocks

TEE主要包含以下几个方面:

安全启动

在启动时,TEE 安全启动从信任根开始,信任根是预先内置在硬件中的高度可信组件,如可信平台模块(TPM)中的密钥或固化在芯片中的根证书。启动过程是一个链式验证过程,从引导加载程序开始,每加载一个软件组件,都会依据前一个已验证的组件来验证其完整性和真实性。例如,引导加载程序会验证内核的数字签名,确保内核未被篡改,内核加载后又会验证系统服务和驱动程序等的合法性。这种链式验证确保了整个启动链条的可信性,只有经过授权和未被篡改的软件才能在 TEE 中加载和执行。

  • 在启动阶段阻止恶意软件的加载和执行,避免系统在启动过程中就被恶意软件入侵,从而保护系统的安全性和稳定性。
  • 保障数据安全:确保 TEE 在启动时加载的是可信的数据处理和存储模块,防止敏感数据在系统启动阶段就面临被窃取或篡改的风险。
  • 支持远程证明:安全启动过程中对软件组件的验证信息可用于远程证明,向远程服务器或其他设备证明本地 TEE 的可信状态,使得远程实体可以放心与本地设备进行交互。

安全启动需要依赖硬件信任根(HW RoT), 主要包含ROM、eFuse以及HW Crypto Engine。ROM用来运行启动代码,因为ROM不可修改,所以ROM的功能是固化的;eFuse主要用来存储密钥(HUK等)以及软硬件相关的配置,安全eFuse需要做到敏感数据特别是密钥信息不允许被导出;密码学引擎(Crypto Engine)需要提供密钥管理、密钥存储、密钥派生等功能,需要确保密钥信息不能被CPU所获取。基于HW RoT的密码学使用方法大致如下图所示:


The Usage of HW RoT during Secure Boot

安全调度

安全调度确保TEE与系统其余部分之间的“平衡”和“高效”的协同运作。实际上,它需要确保在TEE中运行的任务不会影响主操作系统的正常响应。如OP-TEE提出了一种安全调度策略,它把OP-TEE的线程设计为主操作系统线程的“延伸”,OP-TEE不再需要提供调度器而完全依赖主操作系统的调度,它可以减少对主操作系统调度的影响。

环境间通信

环境间通信定义了允许TEE 与系统其余部分通信的接口。通信接口应满足三个关键属性:可靠性(内存/时间隔离)、最小开销(不必要的数据复制和上下文切换)和通信结构的保护。较为常见的通信接口模型如:GlobalPlatform TEE Client API。

安全存储

安全存储是指保证存储数据的机密性、完整性和新鲜度(即防止重放攻击)的存储,并且只有授权实体才能访问数据 。实现安全存储的一种常见方法是加密(sealed)存储。加密存储基于三个组件:

  • 仅可由 TEE 访问的完整性保护密钥;
  • 加密机制,例如经过身份验证的加密算法;
  • 数据回滚保护机制,例如重放保护内存块 (RPMB)。

可信 I/O

可信I/O确保TEE 与外围设备(例如键盘或指纹)之间通信的真实性和机密性,因此,输入和输出数据不会被恶意应用程序嗅探或篡改。更准确地说,可信 I/O 需要防范以下四类攻击:屏幕捕获攻击、键盘记录攻击、覆盖攻击和网络钓鱼攻击。Trust UI的可信路径可在 TEE 内实现更广泛的功能,它允许用户直接与 TEE 内运行的应用程序交互。

前面介绍了TEE的通用模型,接下来我们来简单了解一下Arm基于TrustZone实现的TEE架构。为什么要先了解Arm TrustZone的实现原理,是因为RISC-V在嵌入式上的安全架构与Arm有很多相似之处,TrustZone作为嵌入式安全的先行者,已经有比较好的群众基础,通过了解TrustZone能让用户更好地理解RISC-V的安全机制。

TrustZone-based TEE

Arm TrustZone 技术可以看作是一种特殊的虚拟化,具有对内存、I/O 和中断虚拟化的硬件支持 。这种虚拟化使 Arm 内核能够提供两个虚拟内核 (VCPU) 的抽象:安全 VCPU 和非安全 VCPU。

Secure monitor被视为一个最小的虚拟机管理程序,其主要作用是控制两个虚拟内核之间的上下文管理和调度。我们可以从操作系统管理应用程序(进程)的视角来看TrustZone的轻量级虚拟化。从操作系统视角,每个应用程序都完全独占他的用户空间,不同的应用程序之间相互独立,即使在单个物理核的情况下,应用程序在操作系统的管理下能够“并发”运行(单核的情况下,并不是真正意义上的同时运行)。

TrustZone 的物理世界虚拟化的本质也是一样,我们把"虚拟世界“看成应用程序的用户空间,secure monitor看成是操作系统内核, 虚拟世界(安全世界和非安全世界)在secure monitor的管理下”并行“运行。在单核情况下,这并不是真正意义上的并发运行,而是一种对CPU物理资源的分时复用。

当然,因为是一个物理核,也就是硬件资源是多个vcpu(其实就2个,安全和非全vcpu)共享的,那么在世界切换的时候,除了做一些额外的上下文切换的工作以外,比如cpu上下文(通用寄存器、系统寄存器等)切换,还需要做一些清理的工作,主要是清理cache和TLB,这会带来一定的性能损耗,所以TrustZone在cachline和tlb的entry上额外增加了NS bit(这个下面会详细介绍)来标注cacheline和tlb的安全属性,不同属性之前的cacheline和tlb entry不会相互打架,相当于内部做了一层隔离。

于是, trustzone enxtension的软件整体架构如下:


Armv8 Software Architecture with TrustZone Extension

虽然1个物理核被虚拟出了2个vcpu(可以理解为相比2个物理核,TrustZone虚拟化技术节省了物理核资源), 但是不同vcpu访问资源是如何做到对资源访问(比如ddr)的保护的?

首先,TrustZone 技术在AMBA总线上扩展了 NS(Non-Secure,非安全)位,它作为一个标识位,用于在总线事务中标明该事务是属于安全域还是非安全域,这也是TrustZone技术的核心思想(将资源划分成安全世界和非安全世界)。

在整个系统中,NS bit 就是一个 “安全标签”,当总线上的事务携带 NS bit 信息时,系统的各个组件(如总线仲裁器、内存filter、从设备等)可以根据这个位来判断是否允许该事务的进行,从而实现了安全与非安全区域的隔离和数据访问控制。

接下来就要说TrustZone的TZASC(Trustzone Address Space Controller) IP了,也就是上面说的内存filter。TZASC是 ARM TrustZone 技术的关键组件,主要用于管理和保护系统(包括CPU和DMA设备)对内存的访问:

  • TZASC 通过检测总线事务的安全状态位来确定当前的传输事务是安全事务还是非安全事务。当处理器处于安全状态时,会发出安全访问事务(NS=0),允许访问安全世界的内存区域;处于非安全状态时,则会发出非安全访问事务(NS=1), 只允许访问非安全区域,限制对安全世界内存的访问。
  • 内存属性配置:系统开发者可以通过配置 TZASC 的配置寄存器,为不同的内存区域设置安全属性,包括安全 / 非安全属性、可读写权限等。这些配置信息被 TZASC 用于在内存访问时进行实时检查和控制。

TZASC IP是用来保护DDR内存的,也就是所有的master设备(包括CPU和DMA设备)对内存的访问都会经过TZASC的保护,那除了DDR以外的地址空间呢?

TrustZone系统中,DDR以外的地址空间都需要额外的安全IP来进行访问控制,主要包括ROM、SRAM和外设的MMIO。TrustZone提供了TZMA(TrustZone Memory Adaptor)IP对ROM或SRAM进行访问控制(可以简单理解TZMA是个轻量级的TZASC), 同时TrustZone也提供了AXI2APB桥 以及TZPC (TrustZone Protection Controller) IP对MMIO的访问进行权限管控。

以上便是实现TrustZone方案需要提供的几个核心的安全IP。当然,除了上述公共的安全IP以外,SoC在设计外设时然需要考量基于NS bit的访问控制,比如对于DMA设备,需要考虑该DMA设备是安全设备还是非安全设备,带来的影响就是该DMA设备在发出读写访问时需要携带NS信号以表明当前访问时安全访问(NS=0)还是非安全访问(NS=1),这需要在SoC设计时就静态绑定。

当然Arm也提供了一种更加灵活通用的方式来绑定DMA设备的安全属性,那就是在DMA设备后面再挂一个SMMU, 因为SMMU可以通过软件编程页表的方式动态地修改DMA的访问属性。这种方式的优点是DMA设备集成时不需要额外的改动,并且灵活性高;缺点就是需要集成SMMU,会增加额外的硅面积。

以上便是Arm TrustZone的核心内容,总结一下就是, TrustZone将资源划分成安全世界和非安全世界,并通过NS bit来标注资源的安全属性,最后通过slave侧(划重点, 关注和后面的RISC-V做对比)的安全IP(如TZASC, TZMA, AXI2APB bridge等)基于NS属性对地址空间的访问进行访问控制。当然,有些事务很可能是不会到达slave侧的安全IP的(如cpu cache hit的情况),这就需要事务的发起者(如 cpu)需要在内部实现访问的隔离,比如在cache中增加NS属性。

5 个赞