资讯详情

RISC-V机密计算实战:PMP隔离、安全监视器与CoVE扩展解析

📅 2026/11/4 6:01:34 | 华诺云谱 👁 阅读
RISC-V机密计算实战:PMP隔离、安全监视器与CoVE扩展解析
1. 先从“机密计算”这个被说烂的词讲起做 RISC-V 安全相关的工作这些年我每次对外讲“机密计算”对方第一反应基本是“和 TrustZone 差不多吧”。这句话其实把两个完全不同的东西混在一起了。传统意义上的安全世界Secure World解决的是“别让普通世界碰到我的钱”而机密计算解决的是“连操作系统本身都别想看到我的数据”。这两者的信任边界、硬件需求、软件栈复杂度完全不在一个量级上。RISC-V 平台这几年在服务器和云场景里开始频繁露面机密计算这个词也顺势成了架构师们绕不开的选项。RISC-V 好就好在指令集和特权架构是完全开放的你能拿到完整的 CPU RTL、完整的启动固件源码、完整的工具链控制权这意味着你可以从物理层面一点一点把信任边界收紧而不像闭源平台那样只能对着几个固定接口做文章。这篇文章我想从我自己实际做过的项目出发把 RISC-V 机密计算落地时最关键的三个环节讲透PMP 物理内存隔离、M 模式安全监视器、以及正在标准化进程中的 CoVE 机密虚机扩展。适合正在做 RISC-V 固件、TEE、或者想评估 RISC-V 能不能承载云上机密虚机的人参考。1.1 你以为的“安全隔离”和真正要的“机密计算”差别在哪先捋一下威胁模型。普通的安全启动、签名固件、权限位检查解决的是“防止攻击者篡改软件”。但机密计算面对的场景更狠你的宿主机操作系统、云平台管理员、甚至隔壁虚机的 Root 用户都可能成为对手。虚拟机监视器Hypervisor是宿主内核的一部分如果它能直接读虚机的物理内存那么虚机里的业务数据就等于裸奔。机密计算的思路是把可信计算基TCB压缩到很小的一块CPU 核心本身、一小段固件、以及专门负责隔离的内存管理单元。这个 TCB 之外的任何代码包括宿主 OS都被当成“不可信”来处理。宿主 OS 可以继续管理 CPU、调度虚机、处理 I/O但它永远没有权限去访问被标记为“机密”的物理页面。这不仅仅是安全设计的审美偏好而是等保、隐私合规、以及云服务商对客户承诺“数据不出租户”时硬性的技术保底。在 x86 上有 TDX/SEV在 ARM 上有 CCARISC-V 上对应的就是 PMP 安全监视器 CoVE 这套组合。区别在于x86 和 ARM 把大部分机制固化在硬件微码和 EL2 固件里而 RISC-V 给了你一大片可以自主实现的软件空间。一方面这是好事灵活、可控、能针对真实硬件做裁剪另一方面这也是难事很多在 x86 上不用你操心的边界切换、中断路由、物理地址保护在 RISC-V 上都需要自己在安全监视器里一点点写出来。1.2 为什么我会在 RISC-V 上做这件事我最早接触 RISC-V 安全是从一块自研 SoC 开始的当时项目里要跑 Linux还要给一个金融场景的应用提供端到端加密隔离。预算有限买不起商业 TEE 授权最后只能从 RISC-V 特权规范里翻方案。翻了几天发现PMP 机制其实已经足够在硬件层面完成“物理内存分区”关键是上层怎么组织安全监视器和访问策略——这部分完全没有标准答案全靠自己设计。后来 CoVE 规范在 RISC-V 基金会里推进我才算把之前的碎片化思路串起来了。CoVE 想做的事情是给“机密虚机”提供一套体系化的 RISC-V 架构支持让监控器、客户虚机、宿主 OS、I/O 设备之间的边界有统一规范。虽然它还很新但设计方向和我自己在项目里踩坑后总结的经验几乎一致先靠 PMP 把物理内存隔离成一个个保护区再由 M 模式安全监视器管理保护区之间的切换最后让客户虚机运行在受控的虚拟地址空间里。这篇文章的主体就是沿着这条实践路径展开。2. PMP 隔离机密空间的物理级铁丝网PMPPhysical Memory Protection是 RISC-V 特权架构里的物理内存保护单元说白了它就是一种每个硬件线程Hart自带的 L1 级访问控制门卫。它不看虚拟地址只认物理地址它不依赖软件维护页表而是由硬件在每次访问时直接检查。这意味着即使宿主 OS 的内核被完全攻破只要它不通过安全监视器就没办法跨过 PMP 划定的红线。2.1 PMP 工作机制每个 Hart 都是你的门卫PMP 的配置寄存器分为两类PMPADDR 负责描述区域的物理地址范围PMPCFG 负责描述区域的权限属性。RISC-V 规范允许每个 hart 最多实现 16 个 PMP 条目部分低端核可能更少RVA 配置文档一般要求至少 16 个。每个条目由 8 位配置信息加上一个可变长的地址字段组成。以 RV64 为例PMPCFG0 这个 CSR 的位 0 到 7 对应 pmp0 的配置位 8 到 15 对应 pmp1依此类推。配置位里最关键的是 R/W/X 三个权限位以及 A[1:0] 地址匹配模式和 L 锁定位的组合配置位含义说明R可读1 表示允许读W可写1 表示允许写X可执行1 表示允许取指A[1:0]模式选择OFF / TOR / NA4 / NAPOTL锁定置 1 后直到复位前该条目不能被软件修改A 字段里有四种模式实际用得最多的是 TOR 和 NAPOT。TORTop of Range用两个相邻 PMP 条目的地址值夹出一个区间NAPOTNaturally Aligned Power-of-Two则直接把一个地址字段的高位解释为区域的基址连续的低位 1 的个数就是区域大小。比如一个 NAPOT 条目里的地址值看起来是 0x8000_0000 到 0x8000_0FFF 对齐、低 12 位全 1它代表的保护区就是 4KB。优先级规则也容易记错如果多个 PMP 条目同时命中同一个访问编号最小的那个条目说了算。所以你在设计区域时最好按物理地址从小到大排列条目并始终预留一个“默认拒绝”兜底条目避免漏配某块区域时出现静默放行。2.2 第一个要注意的问题颗粒度和 M 模式豁免做 PMP 配置时最容易栽的坑是颗粒度。RISC-V 规范允许实现自定义 PMP 最小颗粒度常见实现是 4KB 对齐但也有芯片给出 16 字节甚至 64KB 的颗粒度。如果你的安全区间只打算保护一个 2KB 的敏感结构体而硬件颗粒度是 4KB那么 PMP 匹配范围会自动扩大到你没预期的地方导致相邻内存也被纳入保护或产生额外故障。规范的做法是在驱动初始化时读取 misa/实现相关寄存器或平台设备树里暴露的 pmp 颗粒度信息再做一次地址对齐裁剪。一味在软件里假设 4KB 是危险的。另一个老生常谈但必须强调的点M 模式默认豁免所有 PMP 检查。也就是说PMP 主要限制的是 S 模式和 U 模式但有一条特殊规则一旦某个条目的 L 位被置 1那么 M 模式下访问这块区域也必须遵守该条目的权限。这个特性是做安全监视器最关键的武器把关键固件代码段用 L 位锁定为只读保证即使 M 模式侧发生内存破坏攻击者也没办法改写可信代码。如果你关注 RISC-V 安全扩展的发展还会看到 Smepmp 这类扩展它提供 M 模式只读S 模式按权限访问的灵活配置适合想进一步收紧 M 模式但又不想完全锁死的场景。小规模芯片上不建议一上来就追新扩展先把基础 PMP 的锁定语义和颗粒度摸清楚后面加扩展只是锦上添花。2.3 link.ld、内存布局和 PMP 的第一次握手PMP 设计不是一个独立的寄存器配置问题它必须和固件的链接脚本link.ld配合。裸机固件启动时_start符号所在的物理地址、所有代码段/数据段的位置、栈顶的偏移这些组成内存布局而 PMP 的保护区就是围绕布局来划分的。如果你的链接脚本把内核镜像堆在一个物理地址上但 PMP 保护区只覆盖了前半段后半段就会变成未受保护的可写区被宿主进程改掉之后再去执行后果不用我多说了。下面是一个常见的最小链接脚本骨架来源是我在一个 QEMU RISC-V 原型项目里实际用过的版本OUTPUT_ARCH(riscv) ENTRY(_start) SECTIONS { . 0x80000000; /* 固定链接地址QEMU virt 的 DDR 起点 */ .text : { KEEP(*(.text.init)); *(.text .text.*); } .rodata : { *(.rodata .rodata.*); } .data : ALIGN(4096) { __data_start .; *(.data .data.*); __data_end .; } .bss : ALIGN(4096) { __bss_start .; *(.bss .bss.*); __bss_end .; } . ALIGN(4096); __heap_start .; __heap_end . 0x100000; /* 1MB 堆 */ }这个脚本故意把_start放在 0x80000000目的是让它落在 QEMU virt 平台的物理内存起点和平台的内存映射吻合。你在写自己的脚本时要注意__heap_start和__heap_end的地址必须暴露给 PMP 配置代码否则你后面设保护区的时候不知道边界在哪。配套的 PMP 初始化逻辑在 M 模式固件里大概是这样的思路先把所有 M 模式核心镜像用的区域配置成读写执行等固件完成自检和镜像度量之后立即把代码段改成只读再把 S 模式启动要用的内存区域单独划成一个窗口权限只给读和写不给执行。这样即使 S 模式内核某一页被写坏它也没法直接跳到那块物理地址去执行恶意代码。还有一点锁定的 L 位不是越早置越好。如果固件还要支持 OTA 升级而你把整个固件区域用 L1 锁死后续就无法改写固件分区。我一般的设计是把引导固件Boot ROM 和 BL1设为 L1 只读把可升级的 BL2/BL3 区域留一个可更新窗口更新完成后由安全监视器切换 PMP 条目并作重新度量。3. 安全监视器藏在 M 模式的“可信管家”PMP 只负责“拦截”真正做决策的是安全监视器Security Monitor。在 RISC-V 特权架构里M 模式拥有最终控制权它能操作所有 CSR、能改 PMP、能接管所有异常和中断。所以安全监视器的正统设计位置是 M 模式它的职责是作为不可信 S 模式代码与可信资源之间的唯一仲裁点。3.1 为什么需要一个安全监视器而不是直接在 OS 内做事RISC-V 的软件运行模式分为 U用户、S操作系统/虚拟机监视器、M机器/固件带虚拟化扩展还有 VS/VU 两层。如果你只是想在普通 Linux 上跑一个加密算法库直接在用户态应用里做就够了但如果你要保护一段完整虚机的运行过程宿主操作系统本身就不可信那就不能在宿主内核里放任何安全逻辑。你会需要一个独立于宿主内核运行的、自己在内存里留了一片锁死区域的监视器代码它负责管理宿主和机密虚机之间的切换而宿主内核没有能力改写它。这里也解释了一个常见的困惑为什么不直接让客户虚机运行在 M 模式这样权限最高什么都不用怕。答案是 M 模式没有虚拟内存、没有分页、没有中断控制器的高级抽象直接跑一个完整 OS 在 M 模式下不仅性能灾难而且一旦 OS 出了漏洞就直接破坏整个机器级安全边界。机密虚机仍然运行在受控的虚拟地址空间里只不过它的物理页被 PMP 保护起来并且地址翻译被二次封装——这种设计才符合机密计算的模型。3.2 监控器必须做好的四件事我自己做安全监视器的时候发现它的核心工作可以收敛成四件事第一上下文切换管理。从普通世界切到机密世界时要把宿主的所有通用寄存器、CSR、活跃状态都压到指定内存然后把机密虚机的现场恢复出来。这里最容易出错的是 mstatus 的 MPP 位和 mret 行为切错了直接导致返回地址错误。第二异常和中断路由。物理中断来了之后安全监视器要先判断这个中断属于宿主还是机密虚机再决定要不要注入。机密虚机的中断不能直接透传给宿主因为宿主一旦看到虚拟中断号就可能推断出客户正在进行的敏感操作所以你要设计一套中断隔离表格。第三SBI 服务。RISC-V 的 S 模式通过 ecall 调用 M 模式固件这套接口标准叫 SBISupervisor Binary Interface。安全监视器会扩展一些自定义的 SBI 功能比如“申请一段机密内存”、“创建机密虚机上下文”、“切换 VP”。这些功能是宿主 OS 唯一能触及安全监视器的方式必须做严格入参校验。第四策略检查。每次主机想打开新一轮内存窗口监控器都要校验请求是否来自合法组件、目标地址是否落在白名单内、权限组合是否合理。我见过不少团队把策略写死到代码里后来改需求还得刷整个固件。合理的做法是把策略做成配置表放在独立区域用哈希签名保护。3.3 与 OpenSBI / 自研监控器的选型取舍大多数 RISC-V 开发板都自带 OpenSBI它实现了标准 SBI 调用和部分平台初始化。如果只是跑 Linux直接用 OpenSBI 足够但如果是做机密计算OpenSBI 的默认实现就不够了因为它没有“机密内存管理”和“攻击面最小化”的概念。你可以选择基于 OpenSBI 做二次开发也可以自己写一个极简监控器只保留重塑安全边界所需的最小逻辑然后把其余平台初始化交给上一级固件。我实际经验是先跑通 OpenSBI 了解所有调用路径再决定哪些模块能剪掉。剪掉的原则是凡是和中断、PMP、虚拟化、内存申请无关的代码都可以踢出 TCB。最终留下来的安全监视器代码量可能只有几千行但它的正确性要求比一个全功能 SBI 高一个数量级因为它是信任根连内核都没资格怀疑它。4. CoVE 机密虚机给云原生加一个可信执行层PMP 和安全监视器解决的是物理内存隔离的问题但现代虚机场景还有虚拟化地址翻译、I/O 隔离、中断注入等一大堆新活儿。RISC-V 社区针对这些需求推动了一个扩展叫 CoVEConfidential VM Extension目标是让 RISC-V 平台能像 x86 TDX、ARM CCA 一样在标准虚拟化栈里直接支持机密虚机。4.1 追溯为什么需要 CoVE如果你只是在自家板卡上跑一个裸机安全应用PMP 已经够用但云端场景不一样云上跑的是标准 Linux 虚拟化虚机的生命周期由 HypervisorVMM管理客户机也是标准 OS。如果把宿主 Hypervisor 拉出 TCB那么现有虚拟化代码全都要重写。CoVE 的意义就是定义一个标准机制让现有的 Hypervisor 几乎可以继续跑只不过它对机密虚机的控制权被安全监视器大幅收回变成了“只提供资源调度不触摸客户数据”的角色。CoVE 规范还在演进但基本思路已经比较清晰客户虚机运行在 VS/VU 模式由安全监视器在 M 模式作为 TEE 管理方负责物理页面的加密保护、虚拟 CPU 上下文隔离、以及客户内存的搬入搬出。宿主 OS 和普通虚机运行在非机密世界只能通过规定的 TEE 管理调用接口向安全监视器发起能力受限的请求。4.2 CoVE 架构里的关键角色划分我按自己的理解画个角色表方便你对比角色所在模式职责安全监视器M 模式管理 PMP、执行上下文切换、验证启动镜像、处理 TEE 管理调用机密虚机TVMVS/VU 模式运行机密负载宿主不可见其内部状态宿主 HypervisorS 模式调度 CPU、管理 I/O但不允许读取机密虚机物理页普通虚机VS/VU 模式按传统虚拟化方式运行不享受机密保护机密虚机的物理内存来源是安全监视器掌握的保护区。宿主只能向监视器“申请”内存页监视器分配后会把对应区域的 PMP 条目切给机密世界同时用两阶段地址翻译satp 负责客户虚拟地址到客户物理地址hgatp 负责客户物理地址到宿主物理地址把客户物理地址隐藏起来。任何来自设备的中断也要先经过监视器判断再决定要不要注入虚拟中断防止宿主通过中断信号侧信道偷看客户行为。I/O 这一侧同样重要。RISC-V 配套的还有 IOMMU 扩展用来管理设备对物理内存的 DMA 访问。只有 DMA 被限制在非机密内存时PMP 对 CPU 的隔离才真正闭环否则攻击者可以直接控制一张网卡去 DMA 读机密内存页。这个点在我的项目里是最后补上的最初只用 PMP 保护 CPU 侧导致设备侧是一个巨大的安全漏洞。4.3 与 ARM CCA、x86 TDX 的横向对照做技术选型时免不了横向比较。我的经验是x86 TDX 胜在生态成熟、工具链齐全但硬件黑盒多安全边界难审计ARM CCA 有 Realm Management Extension理念相近但在中小团队手里封闭度偏高RISC-V CoVE 最大的优势是开源可审计、硬件和固件你都能拿到源码劣势也很明显——规范新、硬件支持少、工具链不完善很多东西要靠自己实现。实际上现阶段在 RISC-V 上部署“完整 CoVE”还是超前的事。更多团队的做法是参照 CoVE 的角色划分先用安全监视器和 PMP 搭一个最小可用的机密执行环境等芯片 IP 跟进 CoVE 之后再平滑升级。这个路径我在项目里走过一遍可行。5. 手把手最小可信执行原型的搭建路径理论讲得再多不如亲手跑一个最小系统。我给出一个可以在 QEMU 上完整实验的路径你不需要一块真实开发板就能理解整个框架。目标很简单让 S 模式内核访问一块被 PMP 保护的“机密内存”访问失败并且安全监视器把这个失败事件记录到共享内存里。5.1 平台与工具链选择推荐用 QEMU 的 RISC-V virt 机器它模拟了足够完整的特权架构和 PLIC/CLINT不需要额外硬件。工具链用riscv64-unknown-elf-系列交叉编译裸机固件。如果不想从零折腾启动可以先用opensbi跑通 SBI 启动路径再替换成你自定义的安全监视器。编译时注意-march参数建议用rv64gc_zicsr_zifencei只启用基础扩展和 CSR/栅栏指令。不要无脑开rv64gcv浮点和向量扩展如果在固件里没有处理状态切换会在 trap 时把现场搞乱。5.2 第一步建立一个可度量的启动链安全系统的第一条铁律是信任必须从真实固件开始度量。启动链一般分为几级第一级是 Boot ROM放在 SoC 内部的只读存储里不可更改它的职责是加载第二级固件到固定地址计算哈希和 OTP一次性可编程区里预置的期望值比较不一致就停在这里。二级固件再加载安全监视器和客户镜像继续做度量。每一级的哈希测量对象都包括代码和数据代码地址由链接脚本固定。我的经验是链接脚本里最好把所有段的起始地址都以 4KB 对齐这样既能满足 PMP 颗粒度也为后续哈希计算提供对齐的块边界。5.3 第二步在 trap 处理里观察 PMP 生效下面的代码演示了安全监视器在 M 模式 trap 处理的一小段逻辑。它先读mcause如果是加载或存储访问故障就把mtval出问题的物理地址记录到一块共享内存方便宿主内核查看.section .text.trap .globl mtrap_handler mtrap_handler: csrr t0, mcause li t1, 5 # 0x5 Load access fault beq t0, t1, save_fault li t1, 7 # 0x7 Store access fault beq t0, t1, save_fault # 其他异常统一走默认逻辑 j default_handler save_fault: csrr t0, mtval la t1, __fault_log sd t0, 0(t1) li t2, 1 sd t2, 8(t1) default_handler: # 恢复 mstatus / mepc返回 mret这段代码虽然朴素但它验证了 PMP 拦截是否生效。你可以在 S 模式写一段代码故意去读那块受保护地址然后回来查看__fault_log里记录的物理地址是不是你想保护的那个。真正的安全监视器当然不能这么简单但这个最小验证能帮你快速确认 PMP 配置、异常路由、CSR 读写三条链路都没问题。5.4 第三步一个可以实验的安全内存隔离实例在 QEMU 上做实验时我建议分三步配置 PMP第一步启动阶段所有内存都放行保证 Boot ROM 可以把镜像搬到 DDR。第二步安全监视器初始化完成后把包含监视器自身代码和数据的物理区间设为 RX或 RWX取决于是否允许监视器用栈并设置 L1。第三步定义一个独立的机密区间S 模式对它一律拒绝。下面是一个极简 C 风格的 PMP 配置思路不同硬件平台会有寄存器布局差异但思路通用// 伪代码把 pmp_idx 配置成 TOR 模式保护区间 [start, end) void pmp_set_tor(int idx, uintptr_t start, uintptr_t end, int rw) { // 依赖前一条 pmpaddr 作为 start因此要按地址递增顺序设置 csr_write(CSR_PMPADDR(idx - 1), start 2); // 实际写前一条的结束地址 csr_write(CSR_PMPADDR(idx), end 2); uint8_t cfg (rw 1) ? (PMP_R | PMP_W) : PMP_R; cfg | (PMP_A_TOR | PMP_L); csr_write(CSR_PMPCFG(idx / 4), 读旧值 | ((uint64_t)cfg ((idx % 4) * 8))); }跑通这个实例之后你可以再做几个扩展实验锁死固件区域后尝试写它看是否触发异常把机密内存从 PMP 列表里临时移除再验证安全监视器能否恢复模拟设备 DMA 访问机密内存配合 IOMMU 看是否被拦截。这些实验会逼你注意很多实现细节比如 CSR 读写时旧值不能直接覆盖csrrw和csrs的语义差别等。6. 常见问题与排查思路从自己的开发记录里翻了一些高频问题整理成一张速查表覆盖了 PMP、安全监视器、CoVE 原型三个阶段比较典型的坑。现象可能原因排查方向启动后 S 模式访问任意内存都触发故障PMP 默认拒绝策略生效但你没有给 S 模式分配合法区域检查是否存在一个覆盖整个非安全内存的放行 PMP 条目设置 PMP 后 M 模式自己也访问失败有 L1 条目且权限不足看 L 位是否误置确认要不要在初始化时临时跳过 L固件更新后 PMP 配置被重置锁定的 PMP 在复位后失效更新流程用了未经锁定的代码矢量把更新入口放在 Boot ROM 里而不是依赖可被覆盖的 SRAM 代码trap 进入监视器后现场恢复崩溃mstatus.MPP 保存错误或没有先做寄存器保存检查 ecall 进来时的 mstatus 快照和返回时 MPP 还原interrupt 总被宿主抢占中断路由表里机密中断没有被安全监视器接管确认 PLIC 的中断优先级和监视器的中断注入逻辑CoVE 原型机上无法加载标准 LinuxVMM 没有适配 TEE 管理调用接口或内存申请失败先用 OpenSBI 默认接口跑通最小 Linux再逐层加 TEE 管理调用PMP 配置失败是我见过最多的坑尤其是 TOR 模式下“前后两条 PMP 地址必须递增”这个约束。很多人把两条地址写反结果区域完全错乱。另一个容易出问题的地方是 64 位下 PMPCFG 的字段偏移pmp0cfg 在字节 0、pmp1cfg 在字节 1不是按位排列的写代码时一定要按字节索引计算而不是简单左移一位。调试安全监视器时建议从一开始就把日志写到一段独立于栈和堆的物理区域并且这段区域不在 PMP 保护范围之外被改写。遇到 trap 崩溃时先看mcause的数字5 是 load 访问故障7 是 store 访问故障1 是指令访问故障2 是非法指令。这三个基本能覆盖 80% 的 PMP 配置问题。最后再分享一个我个人的经验不要一开始就追求把安全监视器的代码压到最小。保持它的模块化把上下文切换、PMP 配置、TEE 管理调用、中断路由分开写然后在每一层之间加校验。等测试用例跑透了再考虑合并函数、压缩体积。机密计算这种系统里代码越少越容易审计但如果没有清晰的模块边界代码少反而意味着错误集中后期修复更痛苦。后来我把这套模块化的监视器设计延展到了 CoVE 原型的实验里发现它和 CoVE 规定的 TEE 管理调用天然能对上迁移成本低得多。整个过程中最值钱的资产其实是 QEMU 上那套最小验证环境它能让你在还没有真实硬件支持 CoVE 的情况下先把隔离、切换、度量这些底层机制全部跑通为后续芯片选型和企业级落地留出足够提前量。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑