]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
s390/mm: Fix handling of _PAGE_UNUSED pte bit
authorClaudio Imbrenda <imbrenda@linux.ibm.com>
Tue, 23 Jun 2026 15:33:22 +0000 (17:33 +0200)
committerClaudio Imbrenda <imbrenda@linux.ibm.com>
Wed, 24 Jun 2026 08:08:56 +0000 (10:08 +0200)
The _PAGE_UNUSED softbit should not really be lying around. Its sole
purpose is to signal to try_to_unmap_one() and try_to_migrate_one()
that the page can be discarded instead of being moved / swapped.

KVM has no way to know why a page is being unmapped, so it sets the bit
on userspace ptes corresponding to unused guest pages every time they
get unmapped. KVM has no reasonable way to clear the bit once the page
is in use again.

While set_ptes() checks and clears the bit, other paths that set new
ptes did not. This led to used pages being thrown out as if they were
unused, causing guest corruption.

Fix the issue by clearing the _PAGE_UNUSED bit for present ptes in
set_pte(), i.e. whenever a present pte is getting set. The check in
set_ptes() is then redundant and can be removed.

Also fix gmap_helper_try_set_pte_unused() to only set the bit if the
pte is present; the _PAGE_UNUSED bit is only defined for present ptes
and thus should not be set for non-present ptes.

Fixes: c98175b7917f ("KVM: s390: Add gmap_helper_set_unused()")
Acked-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Message-ID: <20260623153331.233784-2-imbrenda@linux.ibm.com>

arch/s390/include/asm/pgtable.h
arch/s390/mm/gmap_helpers.c

index 2c6cee8241e044792d3649e96570a8635b785b82..4740c75649ebb59e0ae7c77781b916e4658547a1 100644 (file)
@@ -980,6 +980,8 @@ static inline void set_pmd(pmd_t *pmdp, pmd_t pmd)
 
 static inline void set_pte(pte_t *ptep, pte_t pte)
 {
+       if (pte_present(pte))
+               pte = clear_pte_bit(pte, __pgprot(_PAGE_UNUSED));
        WRITE_ONCE(*ptep, pte);
 }
 
@@ -1332,8 +1334,6 @@ pgprot_t pgprot_writecombine(pgprot_t prot);
 static inline void set_ptes(struct mm_struct *mm, unsigned long addr,
                              pte_t *ptep, pte_t entry, unsigned int nr)
 {
-       if (pte_present(entry))
-               entry = clear_pte_bit(entry, __pgprot(_PAGE_UNUSED));
        page_table_check_ptes_set(mm, addr, ptep, entry, nr);
        for (;;) {
                set_pte(ptep, entry);
index 1cfe4724fbe2a69fd4a038059eea9034899741ac..60023b6fdcb1387234f71b75e5529575668a1f67 100644 (file)
@@ -181,7 +181,8 @@ void gmap_helper_try_set_pte_unused(struct mm_struct *mm, unsigned long vmaddr)
        if (IS_ERR_OR_NULL(ptep))
                return;
 
-       __atomic64_or(_PAGE_UNUSED, (long *)ptep);
+       if (pte_present(*ptep))
+               __atomic64_or(_PAGE_UNUSED, (long *)ptep);
        pte_unmap_unlock(ptep, ptl);
 }
 EXPORT_SYMBOL_GPL(gmap_helper_try_set_pte_unused);