target/riscv: SFENCE.INVAL.IR in U-mode does not raise illegal-instruction exception
<!--This is the upstream QEMU issue tracker. If you are able to, it will greatly facilitate bug triage if you attempt to reproduce the problem with the latest qemu.git master built from source. See https://www.qemu.org/download/#source for instructions on how to do this. QEMU generally supports the last two releases advertised on https://www.qemu.org/. Problems with distro-packaged versions of QEMU older than this should be reported to the distribution instead. See https://www.qemu.org/contribute/report-a-bug/ for additional guidance. If this is a security issue, please consult https://www.qemu.org/contribute/security-process/--> ## Host environment - Operating system: Ubuntu 24.04.4 LTS - OS/kernel version: Linux aster-MS-7D31 6.17.0-35-generic #35\~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 19:30:42 UTC 2 x86_64 x86_64 x86_64 GNU/Linux - Architecture: x86_64 - QEMU flavor: qemu-system-riscv64 - QEMU version: QEMU emulator version 11.0.50 (v11.0.0-1713-gde5d8bfd61) - QEMU command line: <!--Give the smallest, complete command line that exhibits the problem. If you are using libvirt, virsh, or vmm, you can likely find the QEMU command line arguments in /var/log/libvirt/qemu/$GUEST.log.--> ``` ./qemu-system-x86_64 -M q35 -m 4096 -enable-kvm -hda fedora32.qcow2 ``` ## Emulated/Virtualized environment - Operating system: <!--Windows 10 21H1, Fedora 37, etc.--> - OS/kernel version: <!--For POSIX guests, use `uname -a`.--> - Architecture: <!--x86, ARM, s390x, etc.--> ## Description of problem Executing `SFENCE.INVAL.IR` in User mode should raise an illegal-instruction exception, but QEMU appears to execute it as a no-op. The RISC-V privileged specification states: ```text Attempting to execute SFENCE.W.INVAL or SFENCE.INVAL.IR in U-mode raises an illegal-instruction exception. ``` With the test case below, the hart is switched from Machine mode to User mode, then `sfence.inval.ir` is executed. The expected trap cause is illegal instruction, i.e., `mcause = 2`. Observed behavior in QEMU: ```text mcause = 0 ``` Expected behavior: ```text mcause = 2 ``` This means QEMU accepts `SFENCE.INVAL.IR` in U-mode instead of raising the required illegal-instruction exception. The issue appears to be in the Svinval translation path. The current handler checks that the Svinval and Supervisor extensions are enabled, but does not check the current privilege mode: `target/riscv/insn_trans/trans_svinval.c.inc` ```c static bool trans_sfence_inval_ir(DisasContext *ctx, arg_sfence_inval_ir *a) { REQUIRE_SVINVAL(ctx); REQUIRE_EXT(ctx, RVS); /* Do nothing currently */ return true; } ``` As a result, the instruction is translated successfully even when `ctx->priv == PRV_U`. ## Steps to reproduce 1. Build this bare-metal test case: ```asm call machine_to_user sfence.inval.ir j . ``` 2. Build command used: ```bash riscv64-unknown-elf-gcc \ -march=rv64imafdch_zicfiss_zicbom_zicboz_v_zicsr_zca_zimop_zcmop_zbb_zbs_zkne_zbkb_zabha_zacas_zawrs_zkr_smepmp_zcb_zicond_zba_zknd_zbc_zbkc_zfh_zfbfmin_zfhmin_zfa_zifencei_zvfbfmin_zbkx_zvksed_zvksh_zvknha_zvknhb_zvkg_zvfbfwma_zvbc_zvbb_zvkned_zksed_zksh_zknh_zvkb_zicbop_zicfilp_svinval_zve32f \ -mabi=lp64 \ -mcmodel=medany \ -nostdlib \ -nostartfiles \ -T linker.ld \ code.S machine_to_supervisor.S machine_to_user.S \ -o code.elf ``` 3. Run QEMU with: ```bash qemu-system-riscv64 \ -machine virt \ -m 256M \ -bios none \ -kernel code.elf \ -serial null \ -display none \ -S -s \ -cpu rv64,v=on,smstateen=on,sscofpmf=on,smcsrind=on,sscsrind=on,smaia=on,ssaia=on,ssccfg=on,smcdeleg=on,zicfiss=on,zimop=on,zcmop=on,zaamo=on,zca=on,zbb=on,zbs=on,zkne=on,zbkb=on,zabha=on,zacas=on,zawrs=on,smdbltrp=on,zkr=on,smepmp=on,zcb=on,zicond=on,i=on,m=on,a=on,f=on,d=on,c=on,h=on,zicsr=on,zicbom=on,zicboz=on,zba=on,zknd=on,zbc=on,zbkc=on,zfh=on,zfbfmin=on,zfhmin=on,zfa=on,zifencei=on,zvfbfmin=on,zbkx=on,zvksed=on,zvksh=on,zvknha=on,zvknhb=on,zvkg=on,zvfbfwma=on,zvbc=on,zvbb=on,zvkned=on,zksed=on,zksh=on,zknh=on,zvkb=on,zicbop=on,zicfilp=on,svinval=on,zve32f=on ``` 4. Connect with GDB: ```bash riscv64-unknown-elf-gdb code.elf set pagination off target remote :1234 c Ctrl-C info registers ``` 5. Observe that QEMU reports: ```text mcause = 0 ``` Expected result: ```text mcause = 2 ``` === TEST HARNESS NOTE === The bare-metal test starts in Machine mode. The helper calls such as `machine_to_user` is part of the test harness. They are used only to switch privilege mode before returning to the test code. For this test, `call machine_to_user` switches execution from Machine mode to User mode before executing `sfence.inval.ir`. I verified that the helper works correctly by checking the privilege state after the call. For example, after `call machine_to_user`, the execution state reports User mode. So the tested `SFENCE.INVAL.IR` instruction is executed in U-mode as intended. <details> <summary>Privilege-switch helper code</summary> ```asm .globl machine_to_user machine_to_user: li t6, 3 << 11 csrrc x0, mstatus, t6 la t6, branch_user_from_machine csrw mepc, t6 li t6, 0 mret branch_user_from_machine: ret ``` </details> ## Additional information Specification reference: ```text Attempting to execute SFENCE.W.INVAL or SFENCE.INVAL.IR in U-mode raises an illegal-instruction exception. ``` Observed behavior: * QEMU executes `sfence.inval.ir` in U-mode without trapping. * `mcause` remains `0`. Expected behavior: * QEMU should raise an illegal-instruction exception. * `mcause` should be `2`. Suspected source location: * `target/riscv/insn_trans/trans_svinval.c.inc` * `trans_sfence_inval_ir()` Current code: ```c static bool trans_sfence_inval_ir(DisasContext *ctx, arg_sfence_inval_ir *a) { REQUIRE_SVINVAL(ctx); REQUIRE_EXT(ctx, RVS); /* Do nothing currently */ return true; } ``` This handler currently accepts the instruction after checking the Svinval and Supervisor extensions. It does not reject U-mode execution. A possible fix would be to add a shared helper for Svinval fence instructions that rejects U-mode execution: ```c static bool gen_svinval_fence(DisasContext *ctx) { if (ctx->priv == PRV_U) { /* * U-mode raises illegal-instruction. * VU-mode raises virtual-instruction. */ ctx->virt_inst_excp = ctx->virt_enabled; gen_exception_illegal(ctx); } /* * In M/S/VS modes these instructions have no functional effect in QEMU. * They are also not affected by mstatus.TVM or hstatus.VTVM. */ return true; } static bool trans_sfence_inval_ir(DisasContext *ctx, arg_sfence_inval_ir *a) { REQUIRE_SVINVAL(ctx); REQUIRE_EXT(ctx, RVS); return gen_svinval_fence(ctx); } ``` This is the same root issue as `SFENCE.W.INVAL`: both instructions are currently treated as unconditional no-ops after extension checks, so the U-mode privilege legality rule is skipped. Real impact: * Severity: Low to Medium. * This is not a direct host-security vulnerability, since the instruction is modeled as a no-op in QEMU. * However, it is an architectural correctness issue. * Conformance tests expecting `mcause = 2` for U-mode `SFENCE.INVAL.IR` fail under QEMU. * Guest software or test harnesses may incorrectly conclude that `SFENCE.INVAL.IR` is legal from U-mode. * Such software can then fail on real hardware or stricter simulators that correctly raise an illegal-instruction exception. * The current behavior also weakens the guest architectural privilege boundary because an instruction that should be rejected in U-mode is silently accepted. * If VU-mode is supported, the same path should also avoid silently treating the instruction as a no-op where the architecture requires a virtual-instruction exception. <!--The line below ensures that proper tags are added to the issue. Please do not remove it.-->
issue