OpenSBI的PMP

概述

在RISC-V体系架构中,PMP是用于保护物理内存访问权限的机制。PMP机制允许为不同的物理内存区域指定访问权限(读、写、执行)。这种机制使得运行在RISC-V处理器上的软件只能访问被明确授权的物理地址空间,从而提高了系统的安全性和稳定性。

SMEPMP机制是一个增强型的PMP扩展,是为了防止M模式内存访问和执行的PMP增强。因为对于一段内存,PMP机制无法实现只有U/S模式访问,而M模式无法访问的情况,SMEPMP机制就是为了解决这一点。

PMP

在RISC-V体系架构中,M模式权限最高,可以访问全部系统内存资源。在复位时,S/U模式默认没有对内存的访问权限(包括指令预取和数据访问),其需要M模式进行PMP权限配置。当处理器处于S/U模式时,PMP会对每次的访问进行权限检查。另外,如果PMP配置为锁定状态,M模式下软件访问内存也需要进行PMP权限检查。设计者可以配置PMP保护的粒度,但标准的PMP保护区域至少为4个字节。

PMA可以配合PMA一起进行检查和保护。另外如果访问的地址是虚拟地址,也需要进行PMP权限检查。

CSR寄存器

PMP使用配置表项(8位宽,用pmpNcfg表示,N表示表项数)和地址寄存器,定义了一个访问控制区域的配置属性。RISC-V体系架构最大支持64个表项,芯片设计可以根据实际情况实现0个、16个或者64个PMP表项。

以RV32为例,有16个CSR寄存器用于配置PMP,即pmpcfg0~pmpcfg15,每个CSR寄存器又划分为4个PMP表项,因此总共支持64(16*4)个PMP表项。

PMP配置寄存器

每个PMP表项(pmpNcfg)的总共8位,定义如下:

PMP配置表项

每个字段的含义如下:

字段说明
RBit[0]配置读权限
WBit[1]配置写权限
XBit[2]配置执行权限
ABit[4:3]]地址匹配模式,支持OFF/TOR/NA4/NAPOT四种模式
LBit[7]锁定状态,如果为0表示PMP只对S/U模式生效;如果为1针对所有模式,即对M/S/U模式均生效

关于地址匹配模式,定义如下:

A名称说明
0OFF不进行PMP权限检查
1TOR地址边界模式
2NA4固定4字节的地址粒度
3NAPOT2n的地址粒度,最少8字节

同样,有64个PMP地址寄存器的CSR,即pmpadr0-pmpaddr63。以RV32为例,如下所示,使用Bit[31:0]存储访问地址的Bit[33:2]。

PMP地址寄存器

锁定和特权模式

如果PMP表项中的L字段置位,再次配置表项寄存器和地址寄存器将无效,直至硬件复位。

另外如果PMP表项中的L字段置位,M模式的访问也需要进行PMP检查;如果L字段清零,M模式的PMP表项均会成功,即不进行PMP检查。

优先级与匹配逻辑

如果一个地址匹配到多个PMP表项,那么编号小的PMP表项优先级最高。另外PMP检查也是基于地址范围的,例如如果PMP配置的地址范围为4字节区域[0xC-0xF],那么一个8字节的访问[0x8-0xF]将会失败。

如果M模式没有匹配到任何PMP表项,其访问均会成功。而对于S/U模式,如果没有匹配到任何PMP表项,但只要设置了一个表项,其访问均会失败。

SMEPMP

RISC-V体系架构通过sstatus中的SUM比特和页表中的U比特提供两种机制:SMAP(Supervisor Memory Access Prevention)和SMEP(Supervisor Memory Execution Prevention)。前者防止S模式访问U模式下的内存数据,后者防止S模式执行U模式下的内存代码。

SMAP和SMEP机制只是针对S模式,而对于M模式,此前的标准没有提供这些特性。SMEPMP机制就是为了给M模式提供类似SMAP/SMEP支持。

为了便于理解,定义了一些术语。

名称说明
PMP Entry一对pmpcfgpmpaddr寄存器
PMP Rule寄存器pmpcfgpmpaddr中的内容,表示保护的物理内存区域
Ignored忽略PMP Rule检查规则,允许所有访问请求
Enforced只允许PMP Rule中声明的访问权限请求,PMP检查失败会引发异常
Denied忽略PMP Rule检查规则,不允许任何访问请求
LockedPMP表项中的pmpcfg.L置位
PMP reset所有PMP配置复位,恢复成默认值

SMEPMP机制

新增一个M模式的CSR寄存器mseccfg(Machine Security Configuration),用于配置不同的安全特性。mseccfg目前引入三个bit:

  • MML:M模式锁定(Machine Mode Lockdown)
  • MMWP:M模式白名单策略(Machine-Mode alloWhlist Policy)
  • RLB:规则锁定绕过(Rule Locking Bypass)

主要是新增了msecfg.MML位,其和之前的PMP中的mppcfg.L位,一起配合使用。先考虑请求的地址匹配到PMP表项(下图上半部分):

  • msecfg.MML=0时,访问检查机制还是和之前没有SMEPMP一样,即下图左半部分。
  • msecfg.MML=1时,即如下图右半部分(先暂时不考虑最后三行的情形):
    • 如果mppcfg.L=0,S/U模式根据PMP表项进行权限检查(Enforced),而禁止M模式访问(Denied)
    • 如果mppcfg.L=1,禁止S/U模式模式访问(Denied&Locked),而M模式据PMP表项进行权限检查(Enforced&Locked)

针对最后三行的情形,主要是保证写和执行权限不会同时存在,解决不同特权等级的共享内存问题,因为SMEPMP机制造成M模式与S/U模式之间无法共享内存,会对性能造成影响。

如果mppcfg.L=1msecfg.MML=1,即配置的区域上锁后,S/U模式就不能再次访问该区域。SMEPMP引入一个msecfg.RLB位,关闭锁定效果,注意RISC-V建议这只能用于调试或者简单的环境。

SMEPMP机制

再考虑请求的地址没有匹配到PMP表项(上图下半部分):

  • msecfg.MMWP=1时,拒绝所有模式的访问
  • msecfg.MMWP=0时:
    • 如果mppcfg.MML=0,拒绝S/U模式访问(Denied),M模式忽略权限检查(Ignored)
    • 如果mppcfg.MML=1,拒绝S/U模式访问(Denied),M模式只允许读写权限(RW Ignored),拒绝执行权限(X Denied)

代码

在OpenSBI的初始化代码中,对PMP进行了配置,针对是否具有SMEPMP扩展,有两种方式:只有PMP机制调用sbi_hart_oldpmp_configure函数,有SMEPMP机制调用sbi_hart_smepmp_configure函数:

int sbi_hart_pmp_configure(struct sbi_scratch *scratch)
{
	int rc;
	unsigned int pmp_bits, pmp_log2gran;
	unsigned int pmp_count = sbi_hart_pmp_count(scratch);
	unsigned long pmp_addr_max;

	if (!pmp_count)
		return 0;

	pmp_log2gran = sbi_hart_pmp_log2gran(scratch);
	pmp_bits = sbi_hart_pmp_addrbits(scratch) - 1;
	pmp_addr_max = (1UL << pmp_bits) | ((1UL << pmp_bits) - 1);

	if (sbi_hart_has_extension(scratch, SBI_HART_EXT_SMEPMP))
		rc = sbi_hart_smepmp_configure(scratch, pmp_count,
						pmp_log2gran, pmp_addr_max);
	else
		rc = sbi_hart_oldpmp_configure(scratch, pmp_count,
						pmp_log2gran, pmp_addr_max);

	/*
	 * As per section 3.7.2 of privileged specification v1.12,
	 * virtual address translations can be speculatively performed
	 * (even before actual access). These, along with PMP traslations,
	 * can be cached. This can pose a problem with CPU hotplug
	 * and non-retentive suspend scenario because PMP states are
	 * not preserved.
	 * It is advisable to flush the caching structures under such
	 * conditions.
	 */
	if (misa_extension('S')) {
		__asm__ __volatile__("sfence.vma");

		/*
		 * If hypervisor mode is supported, flush caching
		 * structures in guest mode too.
		 */
		if (misa_extension('H'))
			__sbi_hfence_gvma_all();
	}

	return rc;
}

PMP

下面是前一种PMP机制的实现:

static int sbi_hart_oldpmp_configure(struct sbi_scratch *scratch,
				     unsigned int pmp_count,
				     unsigned int pmp_log2gran,
				     unsigned long pmp_addr_max)
{
	struct sbi_domain_memregion *reg;
	struct sbi_domain *dom = sbi_domain_thishart_ptr();
	unsigned int pmp_idx = 0;
	unsigned int pmp_flags;
	unsigned long pmp_addr;

	sbi_domain_for_each_memregion(dom, reg) {
		if (pmp_count <= pmp_idx)
			break;

		pmp_flags = 0;

		/*
		 * If permissions are to be enforced for all modes on
		 * this region, the lock bit should be set.
		 */
		if (reg->flags & SBI_DOMAIN_MEMREGION_ENF_PERMISSIONS)
			pmp_flags |= PMP_L;

		if (reg->flags & SBI_DOMAIN_MEMREGION_SU_READABLE)
			pmp_flags |= PMP_R;
		if (reg->flags & SBI_DOMAIN_MEMREGION_SU_WRITABLE)
			pmp_flags |= PMP_W;
		if (reg->flags & SBI_DOMAIN_MEMREGION_SU_EXECUTABLE)
			pmp_flags |= PMP_X;

		pmp_addr = reg->base >> PMP_SHIFT;
		if (pmp_log2gran <= reg->order && pmp_addr < pmp_addr_max) {
			pmp_set(pmp_idx++, pmp_flags, reg->base, reg->order);
		} else {
			sbi_printf("Can not configure pmp for domain %s because"
				   " memory region address 0x%lx or size 0x%lx "
				   "is not in range.\n", dom->name, reg->base,
				   reg->order);
		}
	}

	return 0;
}

上面代码根据Domain域中定义的内存区域进行PMP表项配置,即权限标志和地址范围,如果所有模式(M/S/U)都需要进行PMP权限检查,需要将PMP的L比特置位pmpcfg.L=1

SMEPMP

如果有SMEPMP机制,实现如下:

static int sbi_hart_smepmp_configure(struct sbi_scratch *scratch,
				     unsigned int pmp_count,
				     unsigned int pmp_log2gran,
				     unsigned long pmp_addr_max)
{
	struct sbi_domain_memregion *reg;
	struct sbi_domain *dom = sbi_domain_thishart_ptr();
	unsigned int pmp_idx, pmp_flags;

	/*
	 * Set the RLB so that, we can write to PMP entries without
	 * enforcement even if some entries are locked.
	 */
	csr_set(CSR_MSECCFG, MSECCFG_RLB);

	/* Disable the reserved entry */
	pmp_disable(SBI_SMEPMP_RESV_ENTRY);

	/* Program M-only regions when MML is not set. */
	pmp_idx = 0;
	sbi_domain_for_each_memregion(dom, reg) {
		/* Skip reserved entry */
		if (pmp_idx == SBI_SMEPMP_RESV_ENTRY)
			pmp_idx++;
		if (pmp_count <= pmp_idx)
			break;

		/* Skip shared and SU-only regions */
		if (!SBI_DOMAIN_MEMREGION_M_ONLY_ACCESS(reg->flags)) {
			pmp_idx++;
			continue;
		}

		pmp_flags = sbi_hart_get_smepmp_flags(scratch, dom, reg);
		if (!pmp_flags)
			return 0;

		sbi_hart_smepmp_set(scratch, dom, reg, pmp_idx++, pmp_flags,
				    pmp_log2gran, pmp_addr_max);
	}

	/* Set the MML to enforce new encoding */
	csr_set(CSR_MSECCFG, MSECCFG_MML);

	/* Program shared and SU-only regions */
	pmp_idx = 0;
	sbi_domain_for_each_memregion(dom, reg) {
		/* Skip reserved entry */
		if (pmp_idx == SBI_SMEPMP_RESV_ENTRY)
			pmp_idx++;
		if (pmp_count <= pmp_idx)
			break;

		/* Skip M-only regions */
		if (SBI_DOMAIN_MEMREGION_M_ONLY_ACCESS(reg->flags)) {
			pmp_idx++;
			continue;
		}

		pmp_flags = sbi_hart_get_smepmp_flags(scratch, dom, reg);
		if (!pmp_flags)
			return 0;

		sbi_hart_smepmp_set(scratch, dom, reg, pmp_idx++, pmp_flags,
				    pmp_log2gran, pmp_addr_max);
	}

	/*
	 * All entries are programmed.
	 * Keep the RLB bit so that dynamic mappings can be done.
	 */

	return 0;
}

首先调用csr_set(CSR_MSECCFG, MSECCFG_RLB):配置规则锁定绕过,即使一些PMP表项被锁定,还是可以进行重配

然后调用pmp_disable(SBI_SMEPMP_RESV_ENTRY):由于SMEPMP机制使能,会导致M模式也无法访问S/U模式的内存,因此OpenSBI保留第一个PMP表项,并在启动时关闭PMP检查,实现如下:

int pmp_disable(unsigned int n)
{
	int pmpcfg_csr, pmpcfg_shift;
	unsigned long cfgmask, pmpcfg;

	if (n >= PMP_COUNT)
		return SBI_EINVAL;

#if __riscv_xlen == 32
	pmpcfg_csr   = CSR_PMPCFG0 + (n >> 2);
	pmpcfg_shift = (n & 3) << 3;
#elif __riscv_xlen == 64
	pmpcfg_csr   = (CSR_PMPCFG0 + (n >> 2)) & ~1;
	pmpcfg_shift = (n & 7) << 3;
#else
# error "Unexpected __riscv_xlen"
#endif

	/* Clear the address matching bits to disable the pmp entry */
	cfgmask = ~(0xffUL << pmpcfg_shift);
	pmpcfg	= (csr_read_num(pmpcfg_csr) & cfgmask);

	csr_write_num(pmpcfg_csr, pmpcfg);

	return SBI_OK;
}

当需要在M与S/U模式之间共享内存,首先调用sbi_hart_map_saddr函数配置PMP表项地址区域为读写权限,由于sbi_hart_smepmp_configure会开启msecfg.MML=1,根据SMEPMP机制图,M模式此时具有读写权限(Enforced & Locked),S/U模式拒绝访问(Denied & Locked),这样之后M模式代码可以访问这块内存区域,函数实现如下:

int sbi_hart_map_saddr(unsigned long addr, unsigned long size)
{
	/* shared R/W access for M and S/U mode */
	unsigned int pmp_flags = (PMP_W | PMP_X);
	unsigned long order, base = 0;
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();

	/* If Smepmp is not supported no special mapping is required */
	if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SMEPMP))
		return SBI_OK;

	if (is_pmp_entry_mapped(SBI_SMEPMP_RESV_ENTRY))
		return SBI_ENOSPC;

	for (order = MAX(sbi_hart_pmp_log2gran(scratch), log2roundup(size));
	     order <= __riscv_xlen; order++) {
		if (order < __riscv_xlen) {
			base = addr & ~((1UL << order) - 1UL);
			if ((base <= addr) &&
			    (addr < (base + (1UL << order))) &&
			    (base <= (addr + size - 1UL)) &&
			    ((addr + size - 1UL) < (base + (1UL << order))))
				break;
		} else {
			return SBI_EFAIL;
		}
	}

	pmp_set(SBI_SMEPMP_RESV_ENTRY, pmp_flags, base, order);

	return SBI_OK;
}

当M模式完成对这块内存区域的读写操作后,需要调用sbi_hart_unmap_saddr函数,进而调用pmp_disable函数,将锁定pmpcfg.L=0,根据SMEPMP机制图,M模式拒绝访问(Denied),S/U模式根据PMP配置权限进行检查(Enforced),这样就实现了在M与S/U模式之间共享内存,同时也保证M模式不能随意访问S/U模式的内存区域:

int sbi_hart_unmap_saddr(void)
{
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();

	if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SMEPMP))
		return SBI_OK;

	return pmp_disable(SBI_SMEPMP_RESV_ENTRY);
}

然后当msecfg.MML=0时,配置只有M模式可以访问区域的PMP表项,接着使能csr_set(CSR_MSECCFG, MSECCFG_MML),即msecfg.MML=1,最后配置共享区域和只有S/U模式访问区域的PMP表项。

Qemu支持PMP

由于我们采用的qemu模拟器不支持SMEPMP(最新的版本支持SMEPMP),因此只会使用简单的PMP机制,PMP配置的内存区域,打印输出结果如下:

Domain0 Name              : root
Domain0 Boot HART         : 0
Domain0 HARTs             : 0*
Domain0 Region00          : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region01          : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region02          : 0x0000000080040000-0x000000008005ffff M: (R,W) S/U: ()
Domain0 Region03          : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region04          : 0x000000000c000000-0x000000000fffffff M: (I,R,W) S/U: (R,W)
Domain0 Region05          : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address      : 0x0000000080200000
Domain0 Next Arg1         : 0x0000000082200000
Domain0 Next Mode         : S-mode
Domain0 SysReset          : yes
Domain0 SysSuspend        : yes

参考

  1. RISC-V 安全拓展调研(Part 1)

欢迎关注“安全有理”微信公众号。

安全有理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值