Be a security in 20, avoid 30 years of detours
RISC-V Access Protection Introduction
In a RISC-V core, we have three types of access control unit, the PMP (Physical Memory Protection) and MPU (Memory Protection Unit) and APU (Access Protection Unit) to distinguish & protect regions between User/Machine mode and Privilege/Unprivilege mode. In a general use case, the roles of PMP/MPU/APU are as follows:
- PMP: Protect machine mode region in user mode
- MPU: Separate privilege and unprivilege in user mode
- APU: Access control for RISC-V handling outside master access
PMP
Introduction
PMPs control the access privileges of physical memory including R/W and execute, normally it is used to regulate supervisor or user mode.
In a general RISC-V core, there are normally 16 PMP entries, which means we can divide 16 resource groups.
PMP configure
Address & range calculation
When we talk about memory protection, the first thing we consider is the protection range, the setting for PMP range is quite not initiative, here we basically introduce the range setting called NAPOT, it setting the range to an amount of Power of Two, it will first calculate a pmpaddr using some bit operations, then check the lower 1 number n
in this addr, and it will protect the region starting from pmpaddr & LOWER_MASK
with a range of 2n+3 bytes. The calculation rules are:
pmpaddr | pmpcfg.A | Match type and size |
---|---|---|
aaaaa…aaaa | NA4 | 4-byte NAPOT range |
aaaaa…aaa0 | NAPOT | 8-byte NAPOT range |
aaaaa…aa01 | NAPOT | 16-byte NAPOT range |
aaaaa…a011 | NAPOT | 32-byte NAPOT range |
aaa01…1111 | NAPOT | 2XLEN byte NAPOT range |
Here we have a range of global uninit data which resides in .bss
section that we want to protect
1 |
|
Now we start to calculate the pmpaddr
and range, assume that the protected_global
is at 0x30415880
, so we are going to protect the range 0x30415880 -- 0x304158FF
For the PMP addr reg, since it is 4bytes aligned, so first we omit the least two bits in the addr, and since the range is 2n+3, so we clear the bits corresponding with alignment.
1 | /* PMP address are 4-bytes aligned, drop the bottom 2 bits */ |
Finally we get an address of 0x3041588F, according to the NAPOT rule, we will protect 24+3 bytes.
Exception handler register
The next step is to handle an exception handler so that we could go and handle the exception after exception happened, here please refer to spec 3.1.20 for the exception code.
1 | /* Register a handler for the store access fault exception */ |
Initialization and region config
Finally we do initialization and region config to finally open up the PMP module. The pmp will be sequentially used for protecting the physical region.
1 | /* Initialize PMPs */ |
Attempting to write the protected region will cause an exception:
1 | protected_global[0] = 6; |
An actual case of PMP to protect the whole DDR region
In our chip, DDR lower 2GB region is 0x80000000 - 0xFFFFFFFF
, first we use TOR range config, it will use two PMP entries to determine the protected retion, it’s
(If we use pmp entry0 as TOR mode, then 0≤addr<pmp0). The configuration code is
1 | /* Configure PMP 0 to allow access to all memory */ |
Since our PMP addr is 4bytes granularity, it means that we are not able to access to last 4 bytes if we set it as TOR mode. For example, if we set pmp0 addr as 0xFFFFFFFF
, according to RISCV privileged doc, we will record address bit from 2 to 33, so our pmp addr is 0xFFFFFFFF >> 2 = 0x3FFFFFFF
, and when 0x3FFFFFFF lsh 2, we get 0xFFFFFFFC, so our right boundary is 0xFFFFFFFC, we will encounter a load access fault if access 0xFC - 0xFF
.
When we switch to user mode and access DDR region, if we visit last four bytes, we will get the following MCAUSE:
From the spec, we know that we have a load access fault when access the last 4 bytes of DDR region.
MPU
Introduction
MPU is used to separate the privilege and unprivilege in user mode, it is used to protect the region between user and supervisor mode. Though MPU can be used to protect memory region under machine mode.
Execution Fault
APU
Introduction
In our trust engine, there’s an APU which protectes the access from masters to RISC-V, we can setting the start/end address and RD/WR enable bit to control the access behavior.
Configure
To configure the APU, we need to assign the left and right boundary (al and ar) to define the region, given an access region a
, it should satisfy that al≤a≤ar.
If at least one APU is configured, then the space will become a white list mechanism, when master try to access the unprotected region, it will face an error.
1 | /* |