Summary
I found a bug in OpenC910's PMP check for page-table walks. Since page-table walks read PTE data, PMP should require read permission on the page-table physical address. However, OpenC910 appears to reuse the original fetch permission: X-only page-table memory succeeds, while R-only memory faults.
The minimal software-visible POC is an S-mode instruction fetch whose root page table is protected by an X-only PMP entry:
- root page-table page is PMP protected as
R=0,W=0,X=1
- first S-mode fetch triggers a PTW read of that page-table page
Expected result:
- PTW memory access should require PMP read permission on the page-table physical address
- X-only page-table memory should not be readable by PTW
- R-only page-table memory should be readable by PTW
Actual result on OpenC910:
- the fetch translation succeeds with X-only PMP (
BG)
- the same translation faults with R-only PMP (
T)
Test Case
The testcase builds a one-entry Sv39 root page table with a 1GiB identity leaf, then protects the root page-table page with pmp0. The first S-mode instruction fetch must read root_pt[0] through PTW.
.include "openc910_min_common.inc"
.equ PTE_FLAGS_RWXAD, 0x0cf
.equ SATP_MODE_SV39, 0x8000000000000000
.equ PMP_CFG_ALLOW_ALL_E1, 0x1f00
.equ PTW_PMP_CFG, 0x1c # X-only NAPOT; use 0x19 for R-only control
.text
.align 6
.global main
main:
la t0, trap_handler
csrw mtvec, t0
csrw satp, zero
sfence.vma
la t0, root_pt
li t1, PTE_FLAGS_RWXAD
sd t1, 0(t0) # 1GiB identity leaf
fence
la t0, root_pt
srli t0, t0, 2
li t1, 0x1ff
or t0, t0, t1 # NAPOT page-table page
csrw pmpaddr0, t0
li t0, -1
csrw pmpaddr1, t0 # allow all non-page-table memory
li t0, PMP_CFG_ALLOW_ALL_E1 | PTW_PMP_CFG
csrw pmpcfg0, t0
fence
la t0, root_pt
srli t0, t0, 12
li t1, SATP_MODE_SV39
or t0, t0, t1
csrw satp, t0
sfence.vma
li t0, 0x1800
csrc mstatus, t0
li t0, 0x800
csrs mstatus, t0
la t0, smode_target
csrw mepc, t0
mret
smode_target:
li a0, 'B'
jal ra, oracle_putc
ecall
trap_handler:
csrw satp, zero
sfence.vma
csrw pmpcfg0, zero
csrr t0, mcause
li t1, 9
beq t0, t1, s_ecall
li t1, 1
beq t0, t1, inst_access_fault
li a0, 'W'
jal ra, oracle_putc
1:
j 1b
s_ecall:
li a0, 'G'
jal ra, oracle_putc
2:
j 2b
inst_access_fault:
li a0, 'T'
jal ra, oracle_putc
3:
j 3b
.data
.align 12
root_pt:
.zero 4096
If PTW uses PMP read permission correctly, the X-only case (PTW_PMP_CFG=0x1c) should fault and the R-only case (PTW_PMP_CFG=0x19) should be allowed. OpenC910 shows the reverse for fetch-driven PTW.
appendix.zip
Summary
I found a bug in OpenC910's PMP check for page-table walks. Since page-table walks read PTE data, PMP should require read permission on the page-table physical address. However, OpenC910 appears to reuse the original fetch permission: X-only page-table memory succeeds, while R-only memory faults.
The minimal software-visible POC is an S-mode instruction fetch whose root page table is protected by an X-only PMP entry:
R=0,W=0,X=1Expected result:
Actual result on OpenC910:
BG)T)Test Case
The testcase builds a one-entry Sv39 root page table with a 1GiB identity leaf, then protects the root page-table page with
pmp0. The first S-mode instruction fetch must readroot_pt[0]through PTW.If PTW uses PMP read permission correctly, the X-only case (
PTW_PMP_CFG=0x1c) should fault and the R-only case (PTW_PMP_CFG=0x19) should be allowed. OpenC910 shows the reverse for fetch-driven PTW.appendix.zip