Skip to content

OpenC910: Higher-priority locked PMP NA4 deny entries are bypassed by lower-priority NAPOT allow entries #55

Description

@KnightGOKU

Summary

OpenC910 accepts A=NA4 in pmpcfg, but a higher-priority locked NA4 deny entry can be bypassed by a lower-priority broader allow entry.

The minimal software-visible POC is a locked M-mode lw:

  • PMP0 = locked NA4 deny on one word
  • PMP1 = locked 4KB NAPOT RWX allow on the containing page
  • lw denied_word

Expected result:

  • load access fault

Actual result on OpenC910:

  • the load retires successfully

Testcase idea

This testcase constructs two overlapping PMP entries on the same target word:

  1. PMP0: a higher-priority NA4 entry that denies the 4-byte word
  2. PMP1: a lower-priority 4KB NAPOT entry that allows the whole page

Then it executes exactly one instruction on the denied word:

lw t1, 0(t0)

If NA4 is implemented correctly, the load must trap with mcause = 5 (load access fault), because PMP0 has higher priority.

If the bug is present, the load retires successfully and the testcase prints BUG.

Seed Code

.include "openc910_min_common.inc"

.equ PMP0_NA4_DENY_LOCK,   0x90
.equ PMP1_NAPOT_RWX_LOCK,  0x9f

.text
.align 6
.global main
main:
  la    t0, trap_handler
  csrw  mtvec, t0

  # PMP0: higher-priority locked NA4 deny on denied_word.
  la    t0, denied_word
  srli  t0, t0, 2
  csrw  pmpaddr0, t0

  # PMP1: lower-priority locked 4KB NAPOT RWX allow on the containing page.
  la    t0, target_page
  srli  t0, t0, 2
  li    t1, 0x1ff
  or    t0, t0, t1
  csrw  pmpaddr1, t0

  li    t0, (PMP1_NAPOT_RWX_LOCK << 8) | PMP0_NA4_DENY_LOCK
  csrw  pmpcfg0, t0
  fence

  # This load should raise mcause=5 if NA4 is implemented correctly.
  la    t0, denied_word
  lw    t1, 0(t0)

  la    a0, msg_bug
  jal   ra, oracle_puts
1:
  j     1b

trap_handler:
  csrr  t0, mcause
  li    t1, 5
  beq   t0, t1, expected_trap

  la    a0, msg_wrong
  jal   ra, oracle_puts
2:
  j     2b

expected_trap:
  la    a0, msg_trap
  jal   ra, oracle_puts
3:
  j     3b

.section .rodata
msg_bug:
  .asciz "BUG\n"
msg_trap:
  .asciz "TRAP\n"
msg_wrong:
  .asciz "WRONG\n"

.data
.align 12
target_page:
  .space 2048
denied_word:
  .word 0x55667788
  .space 4096 - 2048 - 4

How to reproduce

Build OpenC910's native Verilator simulator

Run this once:

cd openc910/smart_run
make compile SIM=verilator THREADS=8
make buildVerilator

Compile the testcase

/opt/riscv/bin/riscv64-unknown-elf-gcc -c -march=rv64gc_zicsr_zifencei -mabi=lp64d -O2 openc910_min_crt0.S -o openc910_min_crt0.o
/opt/riscv/bin/riscv64-unknown-elf-gcc -c -march=rv64gc_zicsr_zifencei -mabi=lp64d -O2 openc910_na4_load_bypass_min.S -o openc910_na4_load_bypass_min.o
/opt/riscv/bin/riscv64-unknown-elf-gcc -nostdlib -nostartfiles -march=rv64gc_zicsr_zifencei -mabi=lp64d -T openc910_min_linker.ld openc910_min_crt0.o openc910_na4_load_bypass_min.o -o openc910_na4_load_bypass_min.elf

Convert the ELF into OpenC910's inst.pat / data.pat

/opt/riscv/bin/riscv64-unknown-elf-objcopy -O srec openc910_na4_load_bypass_min.elf openc910_na4_load_bypass_min_inst.hex -j .text* -j .rodata* -j .eh_frame*
/opt/riscv/bin/riscv64-unknown-elf-objcopy -O srec openc910_na4_load_bypass_min.elf openc910_na4_load_bypass_min_data.hex -j .data* -j .bss -j .sbss -j .COMMON
../tests/bin/Srec2vmem openc910_na4_load_bypass_min_inst.hex inst.pat
../tests/bin/Srec2vmem openc910_na4_load_bypass_min_data.hex data.pat

Run OpenC910's native simulator

Observed output:

  • BUG

Meaning:

  • BUG = the denied load succeeded instead of trapping
  • TRAP = correct behavior; the load raised mcause = 5
  • WRONG = some unexpected trap happened, but not the expected load access fault

The seed intentionally self-spins after printing one of these strings, so a local run typically ends at the simulation cutoff afterward.

Test Case: test-case.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions