RISC-V IOMMU: first-stage leaf PTE U bit is not checked for unprivileged access
<!--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: Linux x86_64 - OS/kernel version: Linux centos7 6.6.87.2-microsoft-standard-WSL2 - Architecture: x86 - QEMU flavor: qemu-system-riscv64 - QEMU version: v11.0.0-1713-gde5d8bfd61\`, reported version 11.0.50 - QEMU command line: <!--Give the smallest, complete command line that exhibits the problem. </li> </ul> </li> </ul> </li> </ul> <p data-sourcepos="39:1-40:62">If you are using libvirt, virsh, or vmm, you can likely find the QEMU command line arguments in /var/log/libvirt/qemu/$GUEST.log.--&gt;</p> <pre data-sourcepos="42:3-51:5"><code>qemu-system-riscv64 \ -M virt,iommu-sys=on,aia=aplic-imsic \ -cpu rv64,smstateen=true \ -m 8G \ -trace riscv_iommu_dma \ -nographic \ -device edu,dma_mask=0xFFFFFFFFFFFFFFFF \ -bios bin/iommu_fs_pte_u0_unpriv.elf </code></pre> <h2 id="user-content-emulatedvirtualized-environment" data-sourcepos="53:1-53:35">Emulated/Virtualized environment<a href="#emulatedvirtualized-environment" aria-label="Link to heading 'Emulated/Virtualized environment'" data-heading-content="Emulated/Virtualized environment" class="anchor"></a></h2> <ul data-sourcepos="55:1-63:28"> <li data-sourcepos="55:1-57:41"> <p data-sourcepos="55:3-55:19">Operating system:</p> <!--Windows 10 21H1, Fedora 37, etc.--> - OS/kernel version: <!--For POSIX guests, use `uname -a`.--> - Architecture: <!--x86, ARM, s390x, etc.--> ## Description of problem The first-stage (S/VS-stage) page-table walk never checks the leaf PTE U bit. A transaction with no valid process_id is an unprivileged (U-mode) access; an unprivileged access through a first-stage leaf PTE with U = 0 (a supervisor page) must raise a page fault. The IOMMU instead translates the access and the DMA completes. ## Steps to reproduce 1. [iommu_fs_pte_u0_unpriv.c](/uploads/e83b76a98f085e410d3796670dcd87dc/iommu_fs_pte_u0_unpriv.c) 2. Built with: `riscv64-unknown-elf-gcc -march=rv64gc -mabi=lp64 -mcmodel=medany -nostartfiles -nostdlib` 3. [iommu_fs_pte_u0_unpriv.elf](/uploads/fe452d2301dfa77636ae11455e72f606/iommu_fs_pte_u0_unpriv.elf) ```` ``` qemu-system-riscv64 \ -M virt,iommu-sys=on,aia=aplic-imsic \ -cpu rv64,smstateen=true \ -m 8G \ -trace riscv_iommu_dma \ -nographic \ -device edu,dma_mask=0xFFFFFFFFFFFFFFFF \ -bios bin/iommu_fs_pte_u0_unpriv.elf ``` ```` ## Additional information The guest program (`tests/iommu_fs_pte_u0_unpriv.c`): 1. Registers a single-stage DC (iohgatp = Bare, iosatp = Sv39, PDTV = 0), so the fsc field is the iosatp and the first stage translates IOVA directly to SPA. 2. Builds first-stage leaf PTEs with U = 0 for the source and destination buffers (R = W = X = 1, A = 1, D = 1, U = 0). 3. Issues an EDU DMA read (RAM -\> EDU) and an EDU DMA write (EDU -\> RAM). The EDU device issues DMA without a PASID, so the access has no valid process_id. ## Expected behaviour (per spec) The accesses are unprivileged (no valid process_id). A first-stage leaf PTE with U = 0 is a supervisor page, so an unprivileged access must raise a page fault; no data should be moved. Spec references: - RISC-V IOMMU Architecture Specification, Section 3.3, step 17: "Use the process specified in Section 'Two-Stage Address Translation' of the RISC-V Privileged specification to determine the GPA accessed by the transaction. If a fault is detected by the first stage address translation process then stop and report the fault." First-stage permission checks, including the U (user) bit, are performed per the RISC-V Privileged specification referenced by this step. - RISC-V Privileged specification, page-table permission checks: a user-mode access to a leaf PTE with U = 0 raises a page fault. ## Observed behaviour ``` Registering DC (iohgatp = Bare, iosatp = Sv39, single-stage, no PASID)... Built single-stage leaf PTEs with U=0 for src (gva 0x100000) and dst (gva 0x200000) DMA: RAM(GVA 0x100000, U=0) to EDU (read) DMA: EDU to RAM(GVA 0x200000, U=0) (write) Result DMA moved data (buffers match) : YES Any fault logged : NO BUG: unprivileged access through a U=0 first-stage PTE succeeded. The U bit was not checked; no page fault was raised. ``` Both the read and the write completed; the destination buffer matched the source; the fault queue is empty. ## Root cause In `hw/riscv/riscv-iommu.c`, `riscv_iommu_spa_fetch()` leaf-PTE check chain (master, around lines 469-487): ```c if (!(pte & PTE_V)) { break; /* Invalid PTE */ } else if (!(pte & (PTE_R | PTE_W | PTE_X))) { base = PPN_PHYS(ppn); /* Inner PTE, continue walking */ } else if ((pte & (PTE_R | PTE_W | PTE_X)) == PTE_W) { break; } else if ((pte & (PTE_R | PTE_W | PTE_X)) == (PTE_W | PTE_X)) { break; } else if (ppn & ((1ULL << (va_skip - TARGET_PAGE_BITS)) - 1)) { break; /* Misaligned PPN */ } else if ((iotlb->perm & IOMMU_RO) && !(pte & PTE_R)) { break; } else if ((iotlb->perm & IOMMU_WO) && !(pte & PTE_W)) { break; } else if ((iotlb->perm & IOMMU_RO) && !ade && !(pte & PTE_A)) { break; } else if ((iotlb->perm & IOMMU_WO) && !ade && !(pte & PTE_D)) { break; } else { /* Leaf PTE, translation completed. */ ... } ``` The chain checks V, the R/W/X combination, PPN alignment, R/W permission, and A/D, then completes the translation. It never examines `PTE_U`, and the function carries no notion of the access privilege (U-mode vs S-mode) for the first stage. As a result the U bit has no effect on first-stage translation. ## Notes - The same first-stage path serves read, write, and read-for-execute requests; none of them check U. - This test uses a single-stage configuration so the U-bit checked page is unambiguously a first-stage leaf PTE. <!--The line below ensures that proper tags are added to the issue. Please do not remove it.-->
issue