target/riscv: matching sspopchk corrupts following PC-relative instructions in CF_PCREL mode
Host environment
-
Operating system:
Ubuntu on WSL2 (Windows host)
-
OS/kernel version:
Linux 6.6.114.1-microsoft-standard-WSL2
-
Architecture:
x86_64
-
QEMU flavor:
qemu-system-riscv64
-
QEMU version:
11.0.92 (v11.1.0-rc2), also reproduces on git master
-
QEMU command line:
qemu-system-riscv64 -machine virt
-cpu rv64,zicfiss=true,zimop=true,zcmop=true,mmu=false,pmp=false
-nographic -bios sspopchk-pcsync.bin ; echo "exit=$?"Description of problem
trans_sspopchk()intarget/riscv/tcg/insn_trans/trans_rvzicfiss.c.incemits thegen_update_pc(ctx, 0)that synchronizescpu_pconly on the shadow-stack mismatch (exception) path, after thetcg_gen_brcond_tl()that skips to the success label. InCF_PCRELtranslation modegen_update_pc()also updates the translator'spc_saveat translation time. Because the runtimecpu_pcwrite is placed on the mismatch path only, a matchingsspopchk(the normal case) skips that write, leavingcpu_pcstale while the translator believes it is up to date. Every subsequent PC-relative instruction in the same translation block (auipc,lla, PC-relative branches, etc.) then computes a wrong address. The visible effect ranges from wrongauipcresults to corrupted branch targets (hangs / wild jumps) in any CFI-compiled guest that uses shadow stacks. A fix has already been posted upstream by Max Chou (SiFive) but has not yet been reviewed or merged: https://lore.kernel.org/qemu-devel/20251105134331.2865581-1-max.chou@sifive.com/ It movesgen_update_pc(ctx, 0)before the shadow-stack load so the PC is synchronized on both paths. I have verified that this patch fixes the reproducer below.Steps to reproduce
1. Save this self-contained bare-metal reproducer as
sspopchk-pcsync.S. It runs in S-mode withmmu=false(so the shadow-stack access is identity mapped to normal RAM, no page tables needed), sets up a one-entry shadow stack, and, inside a single translation block, runs:run_test: auipc t2, 0 # t2 = &run_test (before sspopchk, always correct) sspopchk t0 # shadow stack matches -> takes the skip path here: auipc t1, 0 # t1 must equal &hereIt then checks
t1 == &hereusing register-absolute arithmetic (no PC-relative branch, which would itself be corrupted) and reports via thesifive_testdevice: exit code 0 on success, 42 on failure..equ MENVCFG, 0x30a .equ MENVCFG_SSE, (1 << 3) .equ CSR_SSP, 0x011 .equ MSTATUS_MPP, (3 << 11) .equ MSTATUS_MPP_S, (1 << 11) .equ SIFIVE_TEST, 0x100000 .equ FINISHER_PASS, 0x5555 .equ FINISHER_FAIL, 0x2a3333 .equ COOKIE, 0x123456789abcdef0 .section .text .globl _start _start: li t0, MENVCFG_SSE csrs MENVCFG, t0 # enable S-mode shadow stack li t0, MSTATUS_MPP csrc mstatus, t0 li t0, MSTATUS_MPP_S csrs mstatus, t0 # mstatus.MPP = S la t0, s_entry csrw mepc, t0 mret # drop to S-mode s_entry: la t1, ss_slot li t2, COOKIE sd t2, 0(t1) # memory[ss_slot] = COOKIE csrw CSR_SSP, t1 # ssp = &ss_slot mv t0, t2 # t0 (x5) = COOKIE == [ssp] j run_test .align 2 run_test: auipc t2, 0 # t2 = &run_test (correct) .word 0xCDC2C073 # sspopchk t0 -> match, skip path here: auipc t1, 0 # t1 = &here (buggy: 4 bytes low) addi t3, t2, 8 # expected &here = &run_test + 8 xor t5, t1, t3 # 0 if correct snez t5, t5 neg t5, t5 # mismatch ? -1 : 0 li t4, FINISHER_PASS li t6, (FINISHER_FAIL - FINISHER_PASS) and t6, t6, t5 add t4, t4, t6 # 0x5555 if ok else 0x2a3333 li t3, SIFIVE_TEST sw t4, 0(t3) # exit QEMU 1: j 1b .section .bss .align 3 ss_slot: .space 82. Build (any binutils works;
sspopchk t0is emitted as.word 0xCDC2C073since older assemblers lack Zicfiss):riscv64-unknown-elf-gcc -march=rv64i_zicsr -mabi=lp64 -nostdlib \ -nostartfiles -Wl,-Ttext=0x80000000 -o sspopchk-pcsync.elf sspopchk-pcsync.S riscv64-unknown-elf-objcopy -O binary sspopchk-pcsync.elf sspopchk-pcsync.binAdditional information
The root cause is the conditional PC synchronization in
trans_sspopchk();gen_update_pc(ctx, 0)must be emitted before the shadow-stack load socpu_pc/pc_savestay consistent regardless of whether the check matches. This is exactly what the pending patch does./label \\\~"kind::Bug"