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. Without appropriate cleanup, the _PAGE_UNUSED bit will linger around and cause guest corruption when a used page is instead thrown out. While set_ptes() checks and clears the bit, ptep_xchg_direct(), ptep_xchg_lazy(), and ptep_modify_prot_commit() did not. This led to used pages being thrown out as if they were unused, causing guest corruption. This patch fixes the issue by introducing the missing checks in the above functions. 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. Signed-off-by: Claudio Imbrenda Fixes: c98175b7917f ("KVM: s390: Add gmap_helper_set_unused()") --- arch/s390/mm/gmap_helpers.c | 4 ++-- arch/s390/mm/pgtable.c | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c index 1cfe4724fbe2..5a7d6b9790e2 100644 --- a/arch/s390/mm/gmap_helpers.c +++ b/arch/s390/mm/gmap_helpers.c @@ -180,8 +180,8 @@ void gmap_helper_try_set_pte_unused(struct mm_struct *mm, unsigned long vmaddr) ptep = try_get_locked_pte(mm, vmaddr, &ptl); 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); diff --git a/arch/s390/mm/pgtable.c b/arch/s390/mm/pgtable.c index 4acd8b140c4b..2acc79383e7d 100644 --- a/arch/s390/mm/pgtable.c +++ b/arch/s390/mm/pgtable.c @@ -122,6 +122,8 @@ pte_t ptep_xchg_direct(struct mm_struct *mm, unsigned long addr, preempt_disable(); old = ptep_flush_direct(mm, addr, ptep, 1); + if (pte_present(new)) + new = clear_pte_bit(new, __pgprot(_PAGE_UNUSED)); set_pte(ptep, new); preempt_enable(); return old; @@ -160,6 +162,8 @@ pte_t ptep_xchg_lazy(struct mm_struct *mm, unsigned long addr, preempt_disable(); old = ptep_flush_lazy(mm, addr, ptep, 1); + if (pte_present(new)) + new = clear_pte_bit(new, __pgprot(_PAGE_UNUSED)); set_pte(ptep, new); preempt_enable(); return old; @@ -175,6 +179,8 @@ pte_t ptep_modify_prot_start(struct vm_area_struct *vma, unsigned long addr, void ptep_modify_prot_commit(struct vm_area_struct *vma, unsigned long addr, pte_t *ptep, pte_t old_pte, pte_t pte) { + if (pte_present(pte)) + pte = clear_pte_bit(pte, __pgprot(_PAGE_UNUSED)); set_pte(ptep, pte); } -- 2.54.0