RISC-V IOMMU: reserved bits in a valid second-stage (G-stage) leaf PTE are not checked
<!--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>
<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.--></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_g_stage_pte_reserved.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
A leaf PTE has a reserved field at bits \[60:54\] that must be 0. Per the RISC-V privileged/IOMMU specs, a valid (V=1) leaf PTE with any reserved bit set must raise a page fault. The IOMMU translation walk does not check these bits. For a second-stage (G-stage) leaf PTE with a reserved bit set, no fault is raised.
Worse, because the walk extracts the PPN as `pte >> PTE_PPN_SHIFT` without masking, the reserved bits bleed into the computed PPN and corrupt the translated address.
## Steps to reproduce
1. [iommu_g_stage_pte_reserved.c](/uploads/38c6a8ce5d272320b117db638fc93bc6/iommu_g_stage_pte_reserved.c)
2. Built with: `riscv64-unknown-elf-gcc -march=rv64gc -mabi=lp64 -mcmodel=medany -nostartfiles -nostdlib`.
3. [iommu_g_stage_pte_reserved.elf](/uploads/0c3fd4c674f255575cf23f8fcd044727/iommu_g_stage_pte_reserved.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_g_stage_pte_reserved.elf
```
## Additional information
The guest program (`tests/iommu_g_stage_pte_reserved.c`):
1. Registers a DC (G-stage Sv39x4, VS-stage Sv39) and builds a correct two-stage mapping for the source and destination buffers.
2. Sets reserved bit 54 in the G-stage leaf PTEs for both buffers (V stays 1).
3. Issues EDU DMA (RAM -\> EDU -\> RAM).
## Expected behaviour (per spec)
The access through a G-stage leaf PTE with a reserved bit set must raise a guest page fault; no data should be moved.
Spec references:
- RISC-V Privileged Architecture: a leaf PTE with reserved bits \[60:54\] non-zero raises a page fault. (The reserved-bit check is part of the page-table walk defined by the RISC-V Privileged specification.)
- RISC-V IOMMU Architecture Specification, Section 3.3 (Process to translate an IOVA), step 19: "Use the second-stage address translation process specified in Section "Two-Stage Address Translation" of the RISC-V Privileged specification to translate the GPA A to determine the SPA accessed by the transaction. If a fault is detected by the address translation process then stop and report the fault."
## Observed behaviour
```
src G-stage leaf PTE written = 0x00400000200248DF (reserved bit 00000036 set)
DMA: RAM to EDU internal buffer
DMA: EDU internal buffer to RAM
Result
DMA moved data (buffers match) : NO
Any fault logged : NO
BUG: a valid G-stage leaf PTE with reserved bit set raised no fault.
The reserved field is not checked. The reserved bit also corrupts the
computed PPN, so the translated address is wrong - see the DMA trace.
```
QEMU `riscv_iommu_dma` trace - the reserved bit (54) propagates into the translated address as bit 56 (no fault is raised):
```
riscv_iommu_dma (null): translate 0000:01.0 #0 RO 0x100000 -> 0x100000080092000
riscv_iommu_dma (null): translate 0000:01.0 #0 RO 0x100004 -> 0x80092000
riscv_iommu_dma (null): translate 0000:01.0 #0 WR 0x200000 -> 0x100000080093000
riscv_iommu_dma (null): translate 0000:01.0 #0 WR 0x200004 -> 0x80093000
```
The correct SPA would be 0x80092000 / 0x80093000; the extra bit 56 (0x0100000000000000) comes from reserved PTE bit 54. The fault queue is empty. (The DMA then targets non-existent RAM, which is why the buffers do not match; the spec violation is the absence of a fault.)
## Root cause
In `hw/riscv/riscv-iommu.c`, `riscv_iommu_spa_fetch()`:
- The PPN is extracted without masking the reserved bits:
```c
hwaddr ppn = pte >> PTE_PPN_SHIFT; /* line 467 */
```
- The leaf-PTE check chain (lines 469-487) checks V, the R/W/X combination, PPN alignment, R/W permission, and A/D, then completes the translation. It never checks `PTE_RESERVED`:
```c
if (!(pte & PTE_V)) { ...
} else if (!(pte & (PTE_R | PTE_W | PTE_X))) { ... /* non-leaf */
} else if ((pte & (PTE_R | PTE_W | PTE_X)) == PTE_W) { ...
} else if ((pte & (PTE_R | PTE_W | PTE_X)) == (PTE_W | PTE_X)) { ...
} else if (ppn & ((1ULL << (va_skip - TARGET_PAGE_BITS)) - 1)) { ...
} else if ((iotlb->perm & IOMMU_RO) && !(pte & PTE_R)) { ...
} else if ((iotlb->perm & IOMMU_WO) && !(pte & PTE_W)) { ...
} else if ((iotlb->perm & IOMMU_RO) && !ade && !(pte & PTE_A)) { ...
} else if ((iotlb->perm & IOMMU_WO) && !ade && !(pte & PTE_D)) { ...
} else {
/* Leaf PTE, translation completed. */
```
`PTE_RESERVED` is not referenced anywhere in `hw/riscv/riscv-iommu.c`.
<!--The line below ensures that proper tags are added to the issue.
Please do not remove it.-->
issue