target/riscv: menvcfg/henvcfg LPE and SSE bits not writable on RV32
Host environment
-
Operating system:
Linux (WSL2)
-
Architecture:
x86
-
QEMU flavor:
qemu-system-riscv32 / qemu-system-riscv64
-
QEMU version:
qemu.git master (commit b8337166)
-
QEMU command line:
qemu-system-riscv32 -machine virt \ -cpu rv32,zicfilp=true,zicfiss=true,zimop=true,zcmop=true \ -nographic -bios menvcfg-cfi-rv32.bin
Emulated/Virtualized environment
-
Operating system:
Linux (WSL2)
-
Architecture:
RISC-V (RV32)
Description of problem
On RV32, the Zicfilp landing-pad-enable bit menvcfg.LPE (bit 2) and the
Zicfiss shadow-stack-enable bit menvcfg.SSE (bit 3) are not writable:
writes are silently dropped and the bits always read back as 0. The same
program on RV64 works. As a result, CFI (shadow stack + landing pads)
cannot be enabled at all on RV32.
Both bits live in the low 32 bits of menvcfg and the RISC-V CFI spec
(Zicfilp/Zicfiss) defines them for RV32 and RV64 alike. The cause is in
write_menvcfg()/write_henvcfg() in target/riscv/csr.c, where LPE/SSE are
only added to the writable mask inside the `riscv_cpu_mxl(env) ==
MXL_RV64` block. This is also inconsistent with write_senvcfg(), which
already handles SENVCFG_LPE/SENVCFG_SSE regardless of MXLEN.
Steps to reproduce
-
Build the attached M-mode program (source below) for rv32 and rv64.
-
Run it under qemu-system-riscv32 with zicfilp+zicfiss enabled: the
sifive_test finisher reports FAIL (QEMU exits with code 42), meaning
the LPE/SSE bits did not stick.
-
Run the same program under qemu-system-riscv64: it reports PASS
(exit code 0).
Additional information
Minimal, auditable reproducer (writes menvcfg.{LPE,SSE}, reads back,
reports via the virt machine's sifive_test device):
/*
* Minimal M-mode reproducer for QEMU RISC-V bug:
* menvcfg.LPE (bit 2) and menvcfg.SSE (bit 3) are not writable on RV32.
*
* The program runs in M-mode (reset state), sets menvcfg.{LPE,SSE}, reads
* the value back and reports the result through the 'virt' machine's
* sifive_test finisher (MMIO @ 0x100000):
* - all requested bits stick -> write 0x5555 (QEMU exits with code 0)
* - any requested bit is lost -> write 0x2a3333 (QEMU exits with code 42)
*
* sifive_test exit code == bits [31:16] of the written value; the low 16
* bits select PASS (0x5555) / FAIL (0x3333).
*
* CSR 0x30a == menvcfg. We use the raw CSR number so the file assembles
* without needing experimental Zicfilp/Zicfiss assembler support.
*
* Build (RV32):
* riscv64-unknown-elf-gcc -march=rv32i_zicsr -mabi=ilp32 -nostdlib -nostartfiles \
* -Wl,-Ttext=0x80000000 -o menvcfg-cfi-rv32.elf menvcfg-cfi.S
* riscv64-unknown-elf-objcopy -O binary menvcfg-cfi-rv32.elf menvcfg-cfi-rv32.bin
* Build (RV64):
* riscv64-unknown-elf-gcc -march=rv64i_zicsr -mabi=lp64 -nostdlib -nostartfiles \
* -Wl,-Ttext=0x80000000 -o menvcfg-cfi-rv64.elf menvcfg-cfi.S
* riscv64-unknown-elf-objcopy -O binary menvcfg-cfi-rv64.elf menvcfg-cfi-rv64.bin
*/
.equ MENVCFG, 0x30a
.equ CFI_BITS, 0xc /* (1<<2) LPE | (1<<3) SSE */
.equ SIFIVE_TEST, 0x100000
.equ FINISHER_PASS, 0x5555 /* exit code 0 */
.equ FINISHER_FAIL, 0x2a3333 /* exit code 42 */
.section .text
.globl _start
_start:
li t0, CFI_BITS
csrw MENVCFG, t0 /* try to enable LPE + SSE */
csrr t1, MENVCFG /* read the value back */
andi t1, t1, CFI_BITS /* isolate the two CFI bits */
li t3, SIFIVE_TEST
li t0, CFI_BITS
bne t1, t0, 1f /* bits lost -> fail */
li t4, FINISHER_PASS
sw t4, 0(t3)
2: j 2b
1:
li t4, FINISHER_FAIL
sw t4, 0(t3)
3: j 3b