@samitouri / QOSamiQemu / commits / 2d877bc02a

target/loongarch: Preserve PTE permission bits in LDPTE

The LDPTE helper loads a page table entry (or huge page entry) from guest memory and currently applies the PALEN mask to the whole 64-bit value. That mask is intended to constrain the physical address bits, but masking the full entry also clears upper permission bits in the PTE, including NX (bit 62). As a result, LoongArch TCG can incorrectly allow instruction fetches from NX mappings when translation is driven through software page-walk. Fix this by masking only the PPN/address field with PALEN while preserving permission bits, and by clearing any non-architectural (software) bits using a hardware PTE mask. LDDIR is unchanged since it returns the base address of the next page table level. Reported at: https://gitlab.com/qemu-project/qemu/-/issues/3319 Fixes: 56599a705f2 ("target/loongarch: Introduce loongarch_palen_mask()") Cc: qemu-stable@nongnu.org Signed-off-by: rail5 (Andrew S. Rightenburg) <andrew@rail5.org> Reviewed-by: Bibo Mao <maobibo@loongson.cn> Reviewed-by: Song Gao <gaosong@loongson.cn> Signed-off-by: Song Gao <gaosong@loongson.cn>

rail5 committed Mar 6, 2026 at 15:33 UTC 2d877bc02a3b94998cbdd784d194c173d308a98a
3 files changed +33 -3
target/loongarch/cpu.c
+11
@@ -596,6 +596,17 @@ static void loongarch_cpu_reset_hold(Object *obj, ResetType type)
596
597 #ifdef CONFIG_TCG
598 env->fcsr0_mask = FCSR0_M1 | FCSR0_M2 | FCSR0_M3;
599 +
600 + if (is_la64(env)) {
601 + env->hw_pte_mask = MAKE_64BIT_MASK(0, 9) |
602 + R_TLBENTRY_64_PPN_MASK |
603 + R_TLBENTRY_64_NR_MASK |
604 + R_TLBENTRY_64_NX_MASK |
605 + R_TLBENTRY_64_RPLV_MASK;
606 + } else {
607 + env->hw_pte_mask = MAKE_64BIT_MASK(0, 9) |
608 + R_TLBENTRY_32_PPN_MASK;
609 + }
610 #endif
611 env->fcsr0 = 0x0;
612
target/loongarch/cpu.h
+1
@@ -406,6 +406,7 @@ typedef struct CPUArchState {
406 uint64_t llval;
407 uint64_t llval_high; /* For 128-bit atomic SC.Q */
408 uint64_t llbit_scq; /* Potential LL.D+LD.D+SC.Q sequence in effect */
409 + uint64_t hw_pte_mask; /* Mask of architecturally-defined (hardware) PTE bits. */
410 #endif
411 #ifndef CONFIG_USER_ONLY
412 #ifdef CONFIG_TCG
target/loongarch/tcg/tlb_helper.c
+21 -3
@@ -686,6 +686,21 @@ bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
686 cpu_loop_exit_restore(cs, retaddr);
687 }
688
689 +static inline uint64_t loongarch_sanitize_hw_pte(CPULoongArchState *env,
690 + uint64_t pte)
691 +{
692 + uint64_t palen_mask = loongarch_palen_mask(env);
693 + uint64_t ppn_mask = is_la64(env) ? R_TLBENTRY_64_PPN_MASK : R_TLBENTRY_32_PPN_MASK;
694 +
695 + /*
696 + * Keep only architecturally-defined PTE bits. Guests may use some
697 + * otherwise-unused bits for software purposes.
698 + */
699 + pte &= env->hw_pte_mask;
700 +
701 + return (pte & ~ppn_mask) | ((pte & ppn_mask) & palen_mask);
702 +}
703 +
704 target_ulong helper_lddir(CPULoongArchState *env, target_ulong base,
705 uint32_t level, uint32_t mem_idx)
706 {
@@ -729,6 +744,7 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
744 {
745 CPUState *cs = env_cpu(env);
746 hwaddr phys, tmp0, ptindex, ptoffset0, ptoffset1;
747 + uint64_t pte_raw;
748 uint64_t badv;
749 uint64_t ptbase = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTBASE);
750 uint64_t ptwidth = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTWIDTH);
@@ -744,7 +760,6 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
760 * and the other is the huge page entry,
761 * whose bit 6 should be 1.
762 */
747 - base = base & palen_mask;
763 if (FIELD_EX64(base, TLBENTRY, HUGE)) {
764 /*
765 * Gets the huge page level and Gets huge page size.
@@ -768,7 +783,7 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
783 * when loaded into the tlb,
784 * so the tlb page size needs to be divided by 2.
785 */
771 - tmp0 = base;
786 + tmp0 = loongarch_sanitize_hw_pte(env, base);
787 if (odd) {
788 tmp0 += MAKE_64BIT_MASK(ps, 1);
789 }
@@ -780,12 +795,15 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
795 } else {
796 badv = env->CSR_TLBRBADV;
797
798 + base = base & palen_mask;
799 +
800 ptindex = (badv >> ptbase) & ((1 << ptwidth) - 1);
801 ptindex = ptindex & ~0x1; /* clear bit 0 */
802 ptoffset0 = ptindex << 3;
803 ptoffset1 = (ptindex + 1) << 3;
804 phys = base | (odd ? ptoffset1 : ptoffset0);
788 - tmp0 = ldq_le_phys(cs->as, phys) & palen_mask;
805 + pte_raw = ldq_le_phys(cs->as, phys);
806 + tmp0 = loongarch_sanitize_hw_pte(env, pte_raw);
807 ps = ptbase;
808 }
809