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

  1. Build the attached M-mode program (source below) for rv32 and rv64.

  2. 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.

  3. 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
Edited by A-Shehab