keystone框架分析
1.RISC-V的PMP权限
RISC-V PMP(Physical Memory Protection)是一种硬件保护机制,用于保护处理器的物理内存免受非授权访问。它可以为每个特权级别和每个地址范围设置访问权限,从而提供更细粒度的内存保护。
1.1 PMP机制
地址匹配模式
PMP支持两种地址匹配模式:地址范围和地址匹配。地址范围模式允许设置一段地址范围的访问权限,而地址匹配模式则允许设置具体地址的访问权限。访问权限
PMP支持4种访问权限:读、写、执行和访问控制。访问控制权限用于控制对PMP寄存器的访问,只有特权级别为M级别的代码才能访问PMP寄存器。特权级别
PMP可以为每个特权级别设置不同的访问权限。RISC-V支持3个特权级别:M级别(最高特权级别)、S级别(次高特权级别)和U级别(用户特权级别)。PMP寄存器
PMP机制通过PMP寄存器来实现。RISC-V架构中有16个PMP寄存器,每个寄存器可以设置一段地址范围的访问权限。PMP寄存器包括以下字段:
- PMPADDR:地址范围的起始地址。
- PMPADDRLEN:地址范围的长度。
- PMPCFG:访问权限和地址匹配模式。
- PMP配置
PMP配置指的是将PMP寄存器中的配置信息加载到处理器中。在RISC-V中,PMP配置可以通过以下方式实现:
- 1.在处理器启动时,将PMP寄存器中的配置信息加载到处理器中。
- 2.在程序运行时,通过特定的指令将PMP寄存器中的配置信息加载到处理器中。
1.2 PMP 权限控制
PMP 权限控制分为以下几个权限
- 1.S-mode (Supervisor特权权限)
次最高用户权限,权限仅次于machine,系统的驱动,以及内核都运行再这一用户权限 - 2.U-mode(User用户权限)
最低的用户权限,用户的应用程序一般都运行再这一层 - 3.M-mode(Machine,系统权限)
- 最高的用户权限,bootloader,firmware等都运行在这一用户权限
其中,M(machine mode)可以访问全部的地址。为了禁止不可信的代码执行特权指令,引入了U(User mode)。为了限制不可信的代码使其只能访问自己的那部分内存,处理器可以提供一个物理内存保护(PMP,Physical Memory Protection)功能,以提供在各种模式下的内存保护。
总之,PMP机制是一种硬件保护机制,用于保护处理器的物理内存免受非授权访问。它可以为每个特权级别和每个地址范围设置访问权限,从而提供更细粒度的内存保护。
1.3 源码分析
keystone关于PMP的源码分析
1 |
|
这里可以看出pmp_set_keystone干了几件事
- 1.pmp_set_keystone函数通过传进的perm参数计算实际需要设置的perm参数
- 2.根据传入的region_idx对应的 pmp_region对应结构体的信息,计算需要写入PMP条目的PMP配置寄存器和PMP地址寄存器的值。
2.Keystone运行机制
一个简单的demo程序一般由两个程序构成
- 1.host程序,作为enclave的runtime,其中内存分配以及内存映射都是通过host程序进行系统调用实现
- 2.eapp程序,作为实际运行的程序,依靠host程序作为runtime
一个简单的demo1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#host.cpp
//******************************************************************************
// Copyright (c) 2018, The Regents of the University of California (Regents).
// All Rights Reserved. See LICENSE for license details.
//------------------------------------------------------------------------------
#include "edge/edge_call.h"
#include "host/keystone.h"
using namespace Keystone;
int main(int argc, char** argv) {
Enclave enclave;
Params params;
params.setFreeMemSize(1024 * 1024);//申请空间
params.setUntrustedMem(DEFAULT_UNTRUSTED_PTR, 1024 * 1024); //设置eapp的内存地址
enclave.init(argv[1], argv[2], params); //初始化enclave
enclave.registerOcallDispatch(incoming_call_dispatch);
edge_call_init_internals(
(uintptr_t)enclave.getSharedBuffer(), enclave.getSharedBufferSize());
enclave.run(); //启动enclave
return 0;
}
1 |
|
根据官方文档介绍
一个enclave程序首先有一个连续的物理地址范围,成为EPM(enclave private memory),不受信任的主机要运行enclave程序首先申请分配EPM,并用PT(page table) 和RT(Runtime table)进行初始化epm,一旦主机调用SM创建一个enclave,SM(security monitory) 就会使用PMP保护EPM,并对EPM进行权限控制,并且PMP都是通过内核进行传递再创建成功之后,SM将会对enclave初始化状态进行验证
1 |
|
我们首先看run函数执行了什么
1 |
|
我们发现run中调用了ioctl函数
1 |
|
接着看ioctl函数
1 |
|
接着看enclave_run_enclave函数
1 |
|
我们看一下sbi_sm_run_enclave函数
1 |
|
pmp_set
这里我们可以清楚的看到,sbi_sm_run_enclave函数先是调用了run_enclave函数,然后将返回结果存储到a0,然后调用sbi_trap_exit(regs)函数返回,因此我们这里基本可以推测,eapp程序是作为host程序中的一个中断程序。
run_enclave中,完成以下操作:
1.修改寄存器组的值,对应需要run的那个enclave,并且把当前的寄存器组的值保存起来,(就像函数调用或者中断一样,调用函数或者执行中断函数首先需要保留现场,以便于执行完成之后可以正确的返回)
2.翻转pmp的权限。
每个eapp拥有自己的运行权限,由于host程序最终要运行到eapp,因此这里通过反转pmp权限实现控制eapp的运行权限,可以这么理解,eapp的运行环境是host程序创建的,因此eapp运行之前需要通过host进行初始化。
3。保存一些信息,用于之后的一些操作,例如检查之类的。比如保存当前的hart(硬件线程)对应的eid,以及是否在enclave中,用于之后的操作。
4.sbi_trap_exit:
这函数调用了opensbi的接口,功能是执行中断,并且重新加载寄存器组regs。
因为在之前的函数中修改了寄存器组regs,配套到了eapp,所以执行完这个之后,执行流就到了eapp当中。
参考:
https://zhuanlan.zhihu.com/p/139695407
https://www.cnblogs.com/bows7ring/p/14775208.html