arm: Assertion failure in do_ats_write() when executing ATS instructions with two-stage translation
## Host environment - Operating system: Rocky Linux 8 / AlmaLinux 8 - OS/kernel version: 4.18.0-348.el8.x86_64 - Architecture: x86_64 - QEMU flavor: qemu-system-aarch64 - QEMU version: 10.0.2 - QEMU command line: qemu-system-aarch64 -machine virt,virtualization=on,secure=on,gic-version=4 -cpu max -smp 4 -kernel my_elf_image ## Emulated/Virtualized environment - Operating system: Proprietary bare-meta (guest with EL2 support) - OS/kernel version: N/A (affects any guest using ATS instructions) - Architecture: AArch64 ## Description of problem QEMU crashes with assertion failure when executing ATS (Address Translation Service) instructions in guests with two-stage translation enabled: ``` ../target/arm/helper.c:3512: do_ats_write: Assertion `!res.cacheattrs.is_s2_format' failed. Aborted (core dumped) ``` ## Steps to reproduce 1. Configure QEMU with nested virtualization enabled 2. Boot a guest that uses EL2 (hypervisor mode) 3. Guest executes an ATS instruction (e.g., `AT S1E1R` or `AT S1E1W`) 4. QEMU crashes with assertion failure ## Additional information **GDB Backtrace:** ``` #4 do_ats_write (env=0x55bfbec48000, value=52776665415888, access_type=MMU_DATA_STORE, mmu_idx=ARMMMUIdx_E10_1, ss=ARMSS_Realm) at ../target/arm/helper.c:3512 #5 ats_write64 (env=0x55bfbec48000, ri=0x55bfbecf8760, value=52776665415888) at ../target/arm/helper.c:3838 ``` **GDB Analysis at crash:** ``` (gdb) p ret $1 = true (gdb) p res.cacheattrs.is_s2_format $2 = true (gdb) p fi $3 = {type = ARMFault_Permission, gpcf = GPCF_None, s2addr = 52776665415888, paddr = 0, paddr_space = ARMSS_Secure, level = 3, domain = 0, stage2 = true, s1ptw = false, s1ns = false, ea = false} ``` The fault info shows `stage2 = true` and `type = ARMFault_Permission`, indicating Stage 2 page table walk completed but returned a permission fault. **Versions tested:** - v9.2.4 (affected) - target/arm/helper.c line 3512 - v10.0.2 (affected) - target/arm/helper.c line 3512 - master branch as of 2026-03-10 (affected) - target/arm/tcg/cpregs-at.c line 41 **Root cause:** In `target/arm/ptw.c`, when a two-stage translation's Stage 2 fails (e.g., permission fault), `get_phys_addr_twostage()` returns early without calling `combine_cacheattrs()`: ```c ret = get_phys_addr_nogpc(env, ptw, ipa, access_type, memop, result, fi); fi->s2addr = ipa; /* Combine the S1 and S2 perms. */ result->f.prot &= s1_prot; /* If S2 fails, return early. */ if (ret) { return ret; // <-- Bug: is_s2_format still true from S2 page walk! } // ... combine_cacheattrs() called here on success path ... result->cacheattrs = combine_cacheattrs(hcr, cacheattrs1, result->cacheattrs); ``` During Stage 2 page table walks, `is_s2_format` is set to `true`. When the walk succeeds, `combine_cacheattrs()` resets it to `false`. But when Stage 2 fails, the early return skips this reset, leaving `is_s2_format = true` in the result returned to `do_ats_write()`. **GDB trace showing Stage 2 permission fault:** ``` fi.type = ARMFault_Permission fi.stage2 = true fi.level = 3 res.cacheattrs.is_s2_format = true // <-- Never reset to false! ``` **Proposed fix:** ``` diff --git a/target/arm/ptw.c b/target/arm/ptw.c index 4330900acd..32e8d9e5c1 100644 --- a/target/arm/ptw.c +++ b/target/arm/ptw.c @@ -3320,6 +3320,7 @@ static bool get_phys_addr_twostage(CPUARMState *env, S1Translate *ptw, result->f.prot &= s1_prot; /* If S2 fails, return early. */ if (ret) { + result->cacheattrs.is_s2_format = false; return ret; } ``` ```
issue