Skip to contentSkip to conversation

s390/mm: Batch PTE updates in lazy MMU mode

PATCH Superseded Alexander Gordeev 2 participants 9 messages Jul 13 – Jul 15

Subsystems (9)

v5 of 8 Superseded Latest is v8 →

lore
Share & tools
lore

Alexander Gordeev

author maintainer
#0 lore raw

v5 cover s390/mm: Batch PTE updates in lazy MMU mode

Hi All!

This is v5 of the batched PTE updates in lazy MMU mode rework.

The presented implementation sets up per-cpu caches in the s390-specific hotplug callbacks as opposed to CPUHP_BP_PREPARE_DYN hooks. I like this approach better, since the boot CPU setup is architecture-specific anyway and the whole SMP-related lowcore initialization is handled in one place.

Changes since v4:
- verified that a presumable sashiko performance regression finding [2] although appears valid does not really degrade performance
- applied sashiko suggestion [3] and added "direct-pte-access" kasan bug type
2. https://lore.kernel.org/linux-s390/[email protected]/#r
3. https://lore.kernel.org/linux-s390/[email protected]/

Changes since v3:
- all prerequisite patches are landed in -next and removed from the series

Changes since v2:
- lazy_mmu_mode_enable_for_pte_range() renamed to lazy_mmu_mode_enable_with_ptes() (David Hildenbrand)
- patch "mm/pgtable: Fix bogus comment to clear_not_present_full_ptes()" is dropped (David Hildenbrand)
- direct PTE dereferencing KASAN sanitizer added (Heiko Carstens)
- CONFIG_IPTE_BATCH option is dropped (Heiko Carstens)
- PTE_POISON changed from zero to 0x800 (Heiko Carstens)
- allocate per-cpu caches on CPU hot-plug (Heiko Carstens)
- introduced a lowcore field for fast lazy mode checking (Heiko Carstens)
- few minor code changes (Heiko Carstens)

Changes since v1:
- lazy_mmu_mode_enable_pte() renamed to lazy_mmu_mode_enable_for_pte_range()
- lazy_mmu_mode_enable_for_pte_range() semantics clarified
- some sashiko comments addressed [1] including one bug fix [1]
- patches 2-4 added
1. https://sashiko.dev/#/patchset/cover.1774420056.git.agordeev%40linux.ibm.com

This series addresses an s390-specific aspect of how page table entries are modified. In many cases, changing a valid PTE (for example, setting or clearing a hardware bit) requires issuing an Invalidate Page Table Entry (IPTE) instruction beforehand.

A disadvantage of the IPTE instruction is that it may initiate a machine-wide quiesce state. This state acts as an expensive global hardware lock and should be avoided whenever possible.

Currently, IPTE is invoked for each individual PTE update in most code paths. However, the instruction itself supports invalidating multiple PTEs at once, covering up to 256 entries. Using this capability can significantly reduce the number of quiesce events, with a positive impact on overall system performance. At present, this feature is not utilized.

An effort was therefore made to identify kernel code paths that update large numbers of consecutive PTEs. Such updates can be batched and handled by a single IPTE invocation, leveraging the hardware support described above.

A natural candidate for this optimization is page-table walkers that change attributes of memory ranges and thus modify contiguous ranges of PTEs. Many memory-management system calls enter lazy MMU mode while updating such ranges.

This lazy MMU mode can be leveraged to build on the already existing infrastructure and implement a software-level lazy MMU mechanism, allowing expensive PTE invalidations on s390 to be batched.

Alexander Gordeev (4):
mm: Make lazy MMU mode context-aware
s390/mm: Batch PTE updates in lazy MMU mode mm/kasan: Introduce helpers for lazy MMU mode sanitizer s390/mm: Lazy MMU mode sanitizer

 arch/s390/Kconfig                |   1 +
 arch/s390/include/asm/lazy_mmu.h |   9 +
 arch/s390/include/asm/lowcore.h  |   2 +-
 arch/s390/include/asm/pgtable.h  | 163 +++++++++++--
 arch/s390/kernel/setup.c         |   2 +
 arch/s390/kernel/smp.c           |   7 +
 arch/s390/mm/Makefile            |   2 +-
 arch/s390/mm/lazy_mmu.c          | 401 +++++++++++++++++++++++++++++++
 arch/s390/mm/pgtable.c           |   8 +-
 fs/proc/task_mmu.c               |   2 +-
 include/linux/kasan.h            |  16 ++
 include/linux/pgtable.h          |  46 ++++
 mm/kasan/common.c                |  10 +
 mm/kasan/kasan.h                 |   2 +
 mm/kasan/report_generic.c        |   3 +
 mm/madvise.c                     |   8 +-
 mm/memory.c                      |   8 +-
 mm/mprotect.c                    |   2 +-
 mm/mremap.c                      |   2 +-
 mm/vmalloc.c                     |   6 +-
 20 files changed, 662 insertions(+), 38 deletions(-)
 create mode 100644 arch/s390/include/asm/lazy_mmu.h
 create mode 100644 arch/s390/mm/lazy_mmu.c
Signature · 1 line
2.53.0
author maintainer
#1 lore raw

PATCH v5 1/4 mm: Make lazy MMU mode context-aware

Lazy MMU mode is assumed to be context-independent, in the sense that it does not need any additional information while operating. However, the s390 architecture benefits from knowing the exact page table entries being modified.

Introduce lazy_mmu_mode_enable_with_ptes(), which is provided with the process address space and the page table being operated on. This information is required to enable s390-specific optimizations.

The function takes parameters that are typically passed to page- table level walkers, which implies that the span of PTE entries never crosses a page table boundary.

Architectures that do not require such information simply do not need to define the lazy_mmu_mode_enable_with_ptes() callback.

---
 fs/proc/task_mmu.c      |  2 +-
 include/linux/pgtable.h | 46 +++++++++++++++++++++++++++++++++++++++++
 mm/madvise.c            |  8 +++----
 mm/memory.c             |  8 +++----
 mm/mprotect.c           |  2 +-
 mm/mremap.c             |  2 +-
 mm/vmalloc.c            |  6 +++---
 7 files changed, 60 insertions(+), 14 deletions(-)
fs/proc/task_mmu.c +1 -1
index d32408f7cd5e..750f6095147f 100644
@@ -2842,7 +2842,7 @@ static int pagemap_scan_pmd_entry(pmd_t *pmd, unsigned long start,
 		return 0;
 	}
 
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(vma->vm_mm, start, end, start_pte);
 
 	if ((p->arg.flags & PM_SCAN_WP_MATCHING) && !p->vec_out) {
 		/* Fast path for performing exclusive WP */
include/linux/pgtable.h +46 -0

58 lines not on this page · raw message

mm/madvise.c +4 -4
index 77552b03d318..4419d16b39f9 100644
@@ -452,7 +452,7 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,
 	if (!start_pte)
 		return 0;
 	flush_tlb_batched_pending(mm);
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(mm, addr, end, start_pte);
 	for (; addr < end; pte += nr, addr += nr * PAGE_SIZE) {
 		nr = 1;
 		ptent = ptep_get(pte);
@@ -507,7 +507,7 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,
 				if (!start_pte)
 					break;
 				flush_tlb_batched_pending(mm);
-				lazy_mmu_mode_enable();
+				lazy_mmu_mode_enable_with_ptes(mm, addr, end, start_pte);
 				if (!err)
 					nr = 0;
 				continue;
@@ -674,7 +674,7 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,
 	if (!start_pte)
 		return 0;
 	flush_tlb_batched_pending(mm);
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(mm, addr, end, start_pte);
 	for (; addr != end; pte += nr, addr += PAGE_SIZE * nr) {
 		nr = 1;
 		ptent = ptep_get(pte);
@@ -734,7 +734,7 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,
 				if (!start_pte)
 					break;
 				flush_tlb_batched_pending(mm);
-				lazy_mmu_mode_enable();
+				lazy_mmu_mode_enable_with_ptes(mm, addr, end, pte);
 				if (!err)
 					nr = 0;
 				continue;
mm/memory.c +4 -4
index ff338c2abe92..ee1770ff4a64 100644
@@ -1272,7 +1272,7 @@ copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma,
 	spin_lock_nested(src_ptl, SINGLE_DEPTH_NESTING);
 	orig_src_pte = src_pte;
 	orig_dst_pte = dst_pte;
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(src_mm, addr, end, src_pte);
 
 	do {
 		nr = 1;
@@ -1922,7 +1922,7 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb,
 		return addr;
 
 	flush_tlb_batched_pending(mm);
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(mm, addr, end, start_pte);
 	do {
 		bool any_skipped = false;
 
@@ -2919,7 +2919,7 @@ static int remap_pte_range(struct mm_struct *mm, pmd_t *pmd,
 	mapped_pte = pte = pte_alloc_map_lock(mm, pmd, addr, &ptl);
 	if (!pte)
 		return -ENOMEM;
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(mm, addr, end, mapped_pte);
 	do {
 		BUG_ON(!pte_none(ptep_get(pte)));
 		if (!pfn_modify_allowed(pfn, prot)) {
@@ -3330,7 +3330,7 @@ static int apply_to_pte_range(struct mm_struct *mm, pmd_t *pmd,
 			return -EINVAL;
 	}
 
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(mm, addr, end, mapped_pte);
 
 	if (fn) {
 		do {
mm/mprotect.c +1 -1
index 9cbf932b028c..3fc26418e837 100644
@@ -337,7 +337,7 @@ static long change_pte_range(struct mmu_gather *tlb,
 		is_private_single_threaded = vma_is_single_threaded_private(vma);
 
 	flush_tlb_batched_pending(vma->vm_mm);
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(vma->vm_mm, addr, end, pte);
 	do {
 		nr_ptes = 1;
 		oldpte = ptep_get(pte);
mm/mremap.c +1 -1
index e9c8b1d05832..0dfe3de39ccc 100644
@@ -260,7 +260,7 @@ static int move_ptes(struct pagetable_move_control *pmc,
 	if (new_ptl != old_ptl)
 		spin_lock_nested(new_ptl, SINGLE_DEPTH_NESTING);
 	flush_tlb_batched_pending(vma->vm_mm);
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(mm, old_addr, old_end, old_ptep);
 
 	for (; old_addr < old_end; old_ptep += nr_ptes, old_addr += nr_ptes * PAGE_SIZE,
 		new_ptep += nr_ptes, new_addr += nr_ptes * PAGE_SIZE) {
mm/vmalloc.c +3 -3
index 1afca3568b9b..b5ed2b05771f 100644
@@ -108,7 +108,7 @@ static int vmap_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
 	if (!pte)
 		return -ENOMEM;
 
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(&init_mm, addr, end, pte);
 
 	do {
 		if (unlikely(!pte_none(ptep_get(pte)))) {
@@ -371,7 +371,7 @@ static void vunmap_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
 	unsigned long size = PAGE_SIZE;
 
 	pte = pte_offset_kernel(pmd, addr);
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(&init_mm, addr, end, pte);
 
 	do {
 #ifdef CONFIG_HUGETLB_PAGE
@@ -538,7 +538,7 @@ static int vmap_pages_pte_range(pmd_t *pmd, unsigned long addr,
 	if (!pte)
 		return -ENOMEM;
 
-	lazy_mmu_mode_enable();
+	lazy_mmu_mode_enable_with_ptes(&init_mm, addr, end, pte);
 
 	do {
 		struct page *page = pages[*nr];
Signature · 1 line
2.53.0
author maintainer
#2 lore raw

PATCH v5 2/4 s390/mm: Batch PTE updates in lazy MMU mode

In reply to Alexander Gordeev

Make use of the IPTE instruction's "Additional Entries" field to invalidate multiple PTEs in one go while in lazy MMU mode. This is the mode in which many memory-management system calls (like mremap(), mprotect(), etc.) update memory attributes.

To achieve that, the set_pte() and ptep_get() primitives use a per-CPU cache to store and retrieve PTE values and apply the cached values to the real page table once lazy MMU mode is left.

The same is done for memory-management platform callbacks that would otherwise cause intense per-PTE IPTE traffic, reducing the number of IPTE instructions from up to PTRS_PER_PTE to a single instruction in the best case. The average reduction is of course smaller.

Since all existing page table iterators called in lazy MMU mode handle one table at a time, the per-CPU cache does not need to be larger than PTRS_PER_PTE entries. That also naturally aligns with the IPTE instruction, which must not cross a page table boundary.

Before this change, the system calls did:

	lazy_mmu_mode_enable_pte()
	...
	<update PTEs>		// up to PTRS_PER_PTE single-IPTEs
	...
	lazy_mmu_mode_disable()

With this change, the system calls do:

lazy_mmu_mode_enable_pte()
...
<store new PTE values in the per-CPU cache>
...
lazy_mmu_mode_disable() // apply cache with one multi-IPTE

When applied to large memory ranges, some system calls show significant speedups:

    mprotect()    ~15x
    munmap()      ~3x
    mremap()      ~28x

The overall results depend on memory size and access patterns, but the change generally does not degrade performance.

In addition to a process-wide impact, the rework affects the whole Central Electronics Complex (CEC). Each (global) IPTE instruction initiates a quiesce state in a CEC, so reducing the number of IPTE calls relieves CEC-wide quiesce traffic.

In an extreme case of mprotect() contiguously triggering the quiesce state on four LPARs in parallel, measurements show ~25x fewer quiesce events.

---
 arch/s390/Kconfig                |   1 +
 arch/s390/include/asm/lazy_mmu.h |   9 +
 arch/s390/include/asm/lowcore.h  |   2 +-
 arch/s390/include/asm/pgtable.h  | 157 +++++++++++--
 arch/s390/kernel/setup.c         |   2 +
 arch/s390/kernel/smp.c           |   7 +
 arch/s390/mm/Makefile            |   2 +-
 arch/s390/mm/lazy_mmu.c          | 382 +++++++++++++++++++++++++++++++
 arch/s390/mm/pgtable.c           |   8 +-
 9 files changed, 546 insertions(+), 24 deletions(-)
 create mode 100644 arch/s390/include/asm/lazy_mmu.h
 create mode 100644 arch/s390/mm/lazy_mmu.c
arch/s390/Kconfig +1 -0
index 84404e6778d5..7846332dcd0a 100644
@@ -97,6 +97,7 @@ config S390
 	select ARCH_HAS_GCOV_PROFILE_ALL
 	select ARCH_HAS_GIGANTIC_PAGE
 	select ARCH_HAS_KCOV
+	select ARCH_HAS_LAZY_MMU_MODE
 	select ARCH_HAS_MEMBARRIER_SYNC_CORE
 	select ARCH_HAS_MEM_ENCRYPT
 	select ARCH_HAS_NMI_SAFE_THIS_CPU_OPS
arch/s390/include/asm/lazy_mmu.h new file +9 -0
new file mode 100644
index 000000000000..98366e9de9bc
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LAZY_MMU_H
+#define __LAZY_MMU_H
+
+void lazy_mmu_online_boot_cpu(void);
+int lazy_mmu_online_cpu(gfp_t gfp, unsigned int cpu);
+void lazy_mmu_offline_cpu(unsigned int cpu);
+
+#endif /* __LAZY_MMU_H */
arch/s390/include/asm/lowcore.h +1 -1
index 3b3ecc647993..dba236664da9 100644
@@ -163,7 +163,7 @@ struct lowcore {
 	__s32	preempt_count;			/* 0x03a8 */
 	__u32	spinlock_lockval;		/* 0x03ac */
 	__u32	spinlock_index;			/* 0x03b0 */
-	__u8	pad_0x03b4[0x03b8-0x03b4];	/* 0x03b4 */
+	__s32	lazy_mmu_count;			/* 0x03b4 */
 	__u64	percpu_offset;			/* 0x03b8 */
 	__u8	percpu_register;		/* 0x03c0 */
 	__u8	pad_0x03c1[0x0400-0x03c1];	/* 0x03c1 */
arch/s390/include/asm/pgtable.h +139 -18

222 lines not on this page · raw message

arch/s390/kernel/setup.c +2 -0
index b60284328fe3..f5a3c9e1b6b8 100644
@@ -77,6 +77,7 @@
 #include <asm/maccess.h>
 #include <asm/uv.h>
 #include <asm/asm-offsets.h>
+#include <asm/lazy_mmu.h>
 #include "entry.h"
 
 /*
@@ -1012,5 +1013,6 @@ void __init setup_arch(char **cmdline_p)
 
 void __init arch_cpu_finalize_init(void)
 {
+	lazy_mmu_online_boot_cpu();
 	sclp_init();
 }
arch/s390/kernel/smp.c +7 -0
index 0ba7f89b8161..0a826bbaf1dd 100644
@@ -59,6 +59,7 @@
 #include <asm/topology.h>
 #include <asm/vdso.h>
 #include <asm/maccess.h>
+#include <asm/lazy_mmu.h>
 #include "entry.h"
 
 enum {
@@ -866,6 +867,11 @@ int __cpu_up(unsigned int cpu, struct task_struct *tidle)
 	rc = pcpu_alloc_lowcore(pcpu, cpu);
 	if (rc)
 		return rc;
+	rc = lazy_mmu_online_cpu(GFP_KERNEL, cpu);
+	if (rc) {
+		pcpu_free_lowcore(pcpu, cpu);
+		return rc;
+	}
 	/*
 	 * Make sure global control register contents do not change
 	 * until new CPU has initialized control registers.
@@ -921,6 +927,7 @@ void __cpu_die(unsigned int cpu)
 	pcpu = per_cpu_ptr(&pcpu_devices, cpu);
 	while (!pcpu_stopped(pcpu))
 		cpu_relax();
+	lazy_mmu_offline_cpu(cpu);
 	pcpu_free_lowcore(pcpu, cpu);
 	cpumask_clear_cpu(cpu, mm_cpumask(&init_mm));
 	cpumask_clear_cpu(cpu, &init_mm.context.cpu_attach_mask);
arch/s390/mm/Makefile +1 -1
index 193899c39ca7..26e9fc11543a 100644
@@ -3,7 +3,7 @@
 # Makefile for the linux s390-specific parts of the memory manager.
 #
 
-obj-y		:= init.o fault.o extmem.o mmap.o vmem.o maccess.o
+obj-y		:= init.o fault.o extmem.o mmap.o vmem.o maccess.o lazy_mmu.o
 obj-y		+= page-states.o pageattr.o pgtable.o pgalloc.o extable.o
 
 obj-$(CONFIG_CMM)		+= cmm.o
arch/s390/mm/lazy_mmu.c new file +382 -0

382 lines not on this page · raw message

arch/s390/mm/pgtable.c +4 -4
index 4acd8b140c4b..df36523bcbbb 100644
@@ -166,14 +166,14 @@ pte_t ptep_xchg_lazy(struct mm_struct *mm, unsigned long addr,
 }
 EXPORT_SYMBOL(ptep_xchg_lazy);
 
-pte_t ptep_modify_prot_start(struct vm_area_struct *vma, unsigned long addr,
-			     pte_t *ptep)
+pte_t ___ptep_modify_prot_start(struct vm_area_struct *vma, unsigned long addr,
+				pte_t *ptep)
 {
 	return ptep_flush_lazy(vma->vm_mm, addr, ptep, 1);
 }
 
-void ptep_modify_prot_commit(struct vm_area_struct *vma, unsigned long addr,
-			     pte_t *ptep, pte_t old_pte, pte_t pte)
+void ___ptep_modify_prot_commit(struct vm_area_struct *vma, unsigned long addr,
+				pte_t *ptep, pte_t old_pte, pte_t pte)
 {
 	set_pte(ptep, pte);
 }
Signature · 1 line
2.53.0
#3 lore raw
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [Critical] Violation of Ultravisor architecture constraints by deferring PTE invalidation while calling…

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] Violation of Ultravisor architecture constraints by deferring PTE invalidation while calling uv_convert_from_secure_pte().
- [High] ptep_get_and_clear_full() bypasses the full optimization during process teardown, causing massive redundant IPTE traffic.
- [High] Deferred page table memory updates break lockless page table walkers (like GUP-fast).
- [Low] Dead declarations for IPTE range modify protection functions.
--

--- Patch [2]: [PATCH v5 2/4] s390/mm: Batch PTE updates in lazy MMU mode --- commit dedb613a05cdcf9218ef386e46c14b5cce4aa714
Author: Alexander Gordeev <[email protected]>

s390/mm: Batch PTE updates in lazy MMU mode

This commit introduces a per-CPU cache to batch PTE updates while in lazy MMU mode. It intercepts PTE updates to store them in a software cache, flushing them to the hardware page tables in a single operation upon exiting lazy MMU mode.

Quoted 5 lines
arch/s390/include/asm/pgtable.h +0 -0
[ ... ]

arch/s390/include/asm/pgtable.h static inline pte_t ptep_get_and_clear(struct mm_struct *mm, · line 1315

Quoted 9 lines
arch/s390/include/asm/pgtable.h +2 -1
@@ -1220,7 +1308,8 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
 {
 	pte_t res;
 
-	res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
+	if (!lazy_mmu_ptep_get_and_clear(addr, ptep, &res))
+		res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
 	page_table_check_pte_clear(mm, addr, res);
 	/* At this point the reference through the mapping is still present */
 	if (mm_is_protected(mm) && pte_present(res))

[Severity: Critical]
Does this sequence violate Ultravisor constraints?

When unmapping secure execution guest memory, this path caches the invalidation but leaves the hardware page table unmodified. The function then immediately calls uv_convert_from_secure_pte() while the real PTE in memory is still present.

Could concurrent hardware walks encounter a secure PTE mapping a now non-secure page and trigger a hardware fault?

arch/s390/include/asm/pgtable.h static inline pte_t ptep_get_and_clear(struct mm_struct *mm, · line 1348

Quoted 8 lines

[ ... ]

arch/s390/include/asm/pgtable.h +5 -0
@@ -1229,9 +1318,34 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
[ ... ]
+bool ipte_range_ptep_modify_prot_start(struct vm_area_struct *vma,
+				       unsigned long addr, pte_t *ptep, pte_t *res);
+bool ipte_range_ptep_modify_prot_commit(struct vm_area_struct *vma,
+					unsigned long addr, pte_t *ptep,
+					pte_t old_pte, pte_t pte);

[Severity: Low]
Are these declarations for ipte_range_ptep_modify_prot_start() and ipte_range_ptep_modify_prot_commit() used anywhere?

It looks like they are declared here but never implemented, as the code instead provides __lazy_mmu_ptep_modify_prot_start().

Quoted 8 lines

[ ... ]

arch/s390/include/asm/pgtable.h +0 -3
@@ -1261,11 +1375,13 @@ static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm,
 {
 	pte_t res;
 
-	if (full) {
-		res = ptep_get(ptep);
-		set_pte(ptep, __pte(_PAGE_INVALID));

arch/s390/include/asm/pgtable.h static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm, · line 1385

Quoted 10 lines
arch/s390/include/asm/pgtable.h +7 -2
-	} else {
-		res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
+	if (!lazy_mmu_ptep_get_and_clear(addr, ptep, &res)) {
+		if (full) {
+			res = __ptep_get(ptep);
+			__set_pte(ptep, __pte(_PAGE_INVALID));
+		} else {
+			res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
+		}
 	}

[Severity: High]
Does this bypass the full optimization during process teardown?

During exit_mmap(), zap_pte_range() passes full=1 to skip individual IPTE flushes since the entire address space is being destroyed.

By unconditionally intercepting the call via lazy_mmu_ptep_get_and_clear(), this bypasses the full optimization fast path. Will this cause leave_ipte_range() to subsequently issue redundant IPTE instructions for every single valid PTE?

Quoted 10 lines
arch/s390/mm/lazy_mmu.c new file +6 -0
[ ... ]
+bool __lazy_mmu_ptep_get_and_clear(unsigned long addr, pte_t *ptep, pte_t *res)
+{
+	struct ipte_range *range;
+	pte_t pte, old;
+
+	range = this_ipte_range(ptep);

arch/s390/mm/lazy_mmu.c line 310

Quoted 10 lines
arch/s390/mm/lazy_mmu.c new file +10 -0
+	if (!range)
+		return false;
+
+	old = __ipte_range_ptep_get(range, ptep);
+	pte = __pte(_PAGE_INVALID);
+	__ipte_range_set_pte(range, ptep, pte);
+	*res = old;
+
+	return true;
+}

[Severity: High]
Can this deferred page table update cause issues with lockless page table walkers like get_user_pages_fast()?

When mprotect() uses lazy MMU mode and calls ptep_modify_prot_start(), this function updates the local software cache to invalid but leaves the hardware PTE fully valid and writable.

Since lockless walkers read the hardware page tables directly without taking the page table lock, could they see a present, writable PTE and successfully pin the page for writing, completely ignoring the ongoing mprotect()?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author maintainer
#4 lore raw
Quoted 36 lines · On Mon, Jul 13, 2026 at 02:33:46PM +0000, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] Violation of Ultravisor architecture constraints by deferring PTE invalidation while calling uv_convert_from_secure_pte().
- [High] ptep_get_and_clear_full() bypasses the full optimization during process teardown, causing massive redundant IPTE traffic.
- [High] Deferred page table memory updates break lockless page table walkers (like GUP-fast).
- [Low] Dead declarations for IPTE range modify protection functions.
--

--- Patch [2]: [PATCH v5 2/4] s390/mm: Batch PTE updates in lazy MMU mode --- commit dedb613a05cdcf9218ef386e46c14b5cce4aa714
Author: Alexander Gordeev <[email protected]>

s390/mm: Batch PTE updates in lazy MMU mode

This commit introduces a per-CPU cache to batch PTE updates while in lazy MMU mode. It intercepts PTE updates to store them in a software cache, flushing them to the hardware page tables in a single operation upon exiting lazy MMU mode.

Quoted 5 lines
arch/s390/include/asm/pgtable.h +0 -0
[ ... ]

arch/s390/include/asm/pgtable.h static inline pte_t ptep_get_and_clear(struct mm_struct *mm, · line 1315

Quoted 9 lines
arch/s390/include/asm/pgtable.h +2 -1
@@ -1220,7 +1308,8 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
 {
 	pte_t res;
 
-	res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
+	if (!lazy_mmu_ptep_get_and_clear(addr, ptep, &res))
+		res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
 	page_table_check_pte_clear(mm, addr, res);
 	/* At this point the reference through the mapping is still present */
 	if (mm_is_protected(mm) && pte_present(res))

[Severity: Critical]
Does this sequence violate Ultravisor constraints?

Quoted 7 lines

When unmapping secure execution guest memory, this path caches the invalidation but leaves the hardware page table unmodified. The function then immediately calls uv_convert_from_secure_pte() while the real PTE in memory is still present.

Could concurrent hardware walks encounter a secure PTE mapping a now non-secure page and trigger a hardware fault?

Concurrent walks are expected to be protected with a mm semaphore and/or page table lock. Yet, it makes sense to exclude UV protected MMs from the IPTE optimization altogether - I will do that in the next version.

Quoted 13 lines

arch/s390/include/asm/pgtable.h static inline pte_t ptep_get_and_clear(struct mm_struct *mm, · line 1348

Quoted 8 lines

[ ... ]

arch/s390/include/asm/pgtable.h +5 -0
@@ -1229,9 +1318,34 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
[ ... ]
+bool ipte_range_ptep_modify_prot_start(struct vm_area_struct *vma,
+				       unsigned long addr, pte_t *ptep, pte_t *res);
+bool ipte_range_ptep_modify_prot_commit(struct vm_area_struct *vma,
+					unsigned long addr, pte_t *ptep,
+					pte_t old_pte, pte_t pte);

[Severity: Low]
Are these declarations for ipte_range_ptep_modify_prot_start() and ipte_range_ptep_modify_prot_commit() used anywhere?

Quoted 2 lines

It looks like they are declared here but never implemented, as the code instead provides __lazy_mmu_ptep_modify_prot_start().

This is a leftover, will remove it in the next version.

Quoted 22 lines
Quoted 8 lines

[ ... ]

arch/s390/include/asm/pgtable.h +0 -3
@@ -1261,11 +1375,13 @@ static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm,
 {
 	pte_t res;
 
-	if (full) {
-		res = ptep_get(ptep);
-		set_pte(ptep, __pte(_PAGE_INVALID));

arch/s390/include/asm/pgtable.h static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm, · line 1385

Quoted 10 lines
arch/s390/include/asm/pgtable.h +7 -2
-	} else {
-		res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
+	if (!lazy_mmu_ptep_get_and_clear(addr, ptep, &res)) {
+		if (full) {
+			res = __ptep_get(ptep);
+			__set_pte(ptep, __pte(_PAGE_INVALID));
+		} else {
+			res = ptep_xchg_lazy(mm, addr, ptep, __pte(_PAGE_INVALID));
+		}
 	}

[Severity: High]
Does this bypass the full optimization during process teardown?

Quoted 7 lines

During exit_mmap(), zap_pte_range() passes full=1 to skip individual IPTE flushes since the entire address space is being destroyed.

By unconditionally intercepting the call via lazy_mmu_ptep_get_and_clear(), this bypasses the full optimization fast path. Will this cause leave_ipte_range() to subsequently issue redundant IPTE instructions for every single valid PTE?

Yes, but a fix I tried does not bring any performance difference (as described in the cover letter), but instead makes the code more complicated).

Quoted 25 lines
Quoted 10 lines
arch/s390/mm/lazy_mmu.c new file +6 -0
[ ... ]
+bool __lazy_mmu_ptep_get_and_clear(unsigned long addr, pte_t *ptep, pte_t *res)
+{
+	struct ipte_range *range;
+	pte_t pte, old;
+
+	range = this_ipte_range(ptep);

arch/s390/mm/lazy_mmu.c line 310

Quoted 10 lines
arch/s390/mm/lazy_mmu.c new file +10 -0
+	if (!range)
+		return false;
+
+	old = __ipte_range_ptep_get(range, ptep);
+	pte = __pte(_PAGE_INVALID);
+	__ipte_range_set_pte(range, ptep, pte);
+	*res = old;
+
+	return true;
+}

[Severity: High]
Can this deferred page table update cause issues with lockless page table walkers like get_user_pages_fast()?

Quoted 8 lines

When mprotect() uses lazy MMU mode and calls ptep_modify_prot_start(), this function updates the local software cache to invalid but leaves the hardware PTE fully valid and writable.

Since lockless walkers read the hardware page tables directly without taking the page table lock, could they see a present, writable PTE and successfully pin the page for writing, completely ignoring the ongoing mprotect()?

This (and any other GUP-fast related concerns) were answered in v4:
https://lore.kernel.org/linux-s390/[email protected]/

Quoted 2 lines
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2

Thanks!

author maintainer
#5 lore raw

PATCH v5 3/4 mm/kasan: Introduce helpers for lazy MMU mode sanitizer

In reply to Alexander Gordeev

Provide helpers that allow architectures implement illegitimate PTE direct accesses while the lazy MMU mode is enabled, such as:

	pte_t pte = *ptep;
	*ptep = pte;

By contrast, these would have to be:

	pte_t pte = ptep_get(ptep);
	set_pte(ptep, pte);

The direct PTE accesses pose a real issue on s390.

---
 include/linux/kasan.h     | 16 ++++++++++++++++
 mm/kasan/common.c         | 10 ++++++++++
 mm/kasan/kasan.h          |  2 ++
 mm/kasan/report_generic.c |  3 +++
 4 files changed, 31 insertions(+)
include/linux/kasan.h +16 -0
index bf233bde68c7..deadf566b84a 100644
@@ -134,6 +134,20 @@ static __always_inline void kasan_poison_slab(struct slab *slab)
 		__kasan_poison_slab(slab);
 }
 
+void __kasan_poison_pte(pte_t *pte, int nr);
+static __always_inline void kasan_poison_pte(pte_t *pte, int nr)
+{
+	if (kasan_enabled())
+		__kasan_poison_pte(pte, nr);
+}
+
+void __kasan_unpoison_pte(pte_t *pte, int nr);
+static __always_inline void kasan_unpoison_pte(pte_t *pte, int nr)
+{
+	if (kasan_enabled())
+		__kasan_unpoison_pte(pte, nr);
+}
+
 void __kasan_unpoison_new_object(struct kmem_cache *cache, void *object);
 /**
  * kasan_unpoison_new_object - Temporarily unpoison a new slab object.
@@ -414,6 +428,8 @@ static inline bool kasan_unpoison_pages(struct page *page, unsigned int order,
 	return false;
 }
 static inline void kasan_poison_slab(struct slab *slab) {}
+static inline void kasan_poison_pte(pte_t *pte, int nr) {}
+static inline void kasan_unpoison_pte(pte_t *pte, int nr) {}
 static inline void kasan_unpoison_new_object(struct kmem_cache *cache,
 					void *object) {}
 static inline void kasan_poison_new_object(struct kmem_cache *cache,
mm/kasan/common.c +10 -0
index b7d05c2a6d93..cbf68680614e 100644
@@ -163,6 +163,16 @@ void __kasan_poison_slab(struct slab *slab)
 		     KASAN_SLAB_REDZONE, false);
 }
 
+void __kasan_poison_pte(pte_t *pte, int nr)
+{
+	kasan_poison(pte, sizeof(*pte) * nr, KASAN_LAZY_MMU_PTE, false);
+}
+
+void __kasan_unpoison_pte(pte_t *pte, int nr)
+{
+	kasan_unpoison(pte, sizeof(*pte) * nr, false);
+}
+
 void __kasan_unpoison_new_object(struct kmem_cache *cache, void *object)
 {
 	kasan_unpoison(object, cache->object_size, false);
mm/kasan/kasan.h +2 -0
index fc9169a54766..1a2d18cdb21d 100644
@@ -144,12 +144,14 @@ static inline bool kasan_requires_meta(void)
 #define KASAN_PAGE_REDZONE	0xFE  /* redzone for kmalloc_large allocation */
 #define KASAN_SLAB_REDZONE	0xFC  /* redzone for slab object */
 #define KASAN_SLAB_FREE		0xFB  /* freed slab object */
+#define KASAN_LAZY_MMU_PTE	0xFD  /* direct pte access in lazy mmu mode */
 #define KASAN_VMALLOC_INVALID	0xF8  /* inaccessible space in vmap area */
 #else
 #define KASAN_PAGE_FREE		KASAN_TAG_INVALID
 #define KASAN_PAGE_REDZONE	KASAN_TAG_INVALID
 #define KASAN_SLAB_REDZONE	KASAN_TAG_INVALID
 #define KASAN_SLAB_FREE		KASAN_TAG_INVALID
+#define KASAN_LAZY_MMU_PTE	KASAN_TAG_INVALID
 #define KASAN_VMALLOC_INVALID	KASAN_TAG_INVALID /* only used for SW_TAGS */
 #endif
 
mm/kasan/report_generic.c +3 -0
index f5b8e37b3805..489d4a8d6902 100644
@@ -113,6 +113,9 @@ static const char *get_shadow_bug_type(struct kasan_report_info *info)
 	case KASAN_SLAB_FREE_META:
 		bug_type = "slab-use-after-free";
 		break;
+	case KASAN_LAZY_MMU_PTE:
+		bug_type = "lazy-mmu-pte-access";
+		break;
 	case KASAN_ALLOCA_LEFT:
 	case KASAN_ALLOCA_RIGHT:
 		bug_type = "alloca-out-of-bounds";
Signature · 1 line
2.53.0
#6 lore raw

Re: [PATCH v5 3/4] mm/kasan: Introduce helpers for lazy MMU mode sanitizer

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Medium] The newly introduced KASAN PTE helpers lack `EXPORT_SYMBOL`, which will cause module build…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] The newly introduced KASAN PTE helpers lack EXPORT_SYMBOL, which will cause module build failures when utilized in inline page table accessors.
- [Medium] The generic kasan_[un]poison_pte API inherently conflicts with KASAN tracking requirements on architectures where sizeof(pte_t) < KASAN_GRANULE_SIZE (e.g., 32-bit architectures), causing WARN_ON splats and silent shadow memory corruption.
- [Medium] KASAN poisoning of PTEs during lazy MMU mode fundamentally breaks concurrent lockless PTE readers (e.g., GUP-fast), causing false-positive KASAN splats.
--

--- Patch [3]: [PATCH v5 3/4] mm/kasan: Introduce helpers for lazy MMU mode sanitizer --- commit ad7a1a1292ef9676ee173915cfe50c0da8ea27a4
Author: Alexander Gordeev <[email protected]>

mm/kasan: Introduce helpers for lazy MMU mode sanitizer

This commit introduces KASAN helpers to allow architectures to catch illegitimate direct PTE accesses while lazy MMU mode is enabled. It provides kasan_poison_pte() and kasan_unpoison_pte() routines, along with a new KASAN_LAZY_MMU_PTE shadow bug type.

Quoted 4 lines
mm/kasan/common.c +0 -0
index b7d05c2a6d93d..cbf68680614e5 100644

[ ... ]

mm/kasan/common.c void __kasan_poison_slab(struct slab *slab) · line 169

Quoted 8 lines
mm/kasan/common.c +4 -0
@@ -163,6 +163,16 @@ void __kasan_poison_slab(struct slab *slab)
 		     KASAN_SLAB_REDZONE, false);
 }
 
+void __kasan_poison_pte(pte_t *pte, int nr)
+{
+	kasan_poison(pte, sizeof(*pte) * nr, KASAN_LAZY_MMU_PTE, false);
+}

[Severity: Medium]
Since lazy MMU mode is generally a per-thread or per-CPU state, but KASAN shadow memory is a globally shared mapping, will poisoning a live PTE page here trigger false-positive KASAN splats for concurrent legitimate lockless page table walkers?

For instance, if get_user_pages_fast() or handle_pte_fault() executes a READ_ONCE() on another CPU against this PTE, it seems they would hit the globally poisoned shadow memory.

[Severity: Medium]
On 32-bit architectures where sizeof(pte_t) is 4 bytes, kasan_poison() will receive a size of 4 when nr is 1.

Since generic KASAN enforces an 8-byte alignment constraint, will this trigger the WARN_ON(size & KASAN_GRANULE_MASK) check?

[Severity: Medium]
If architectures call this helper from inline page table accessors like ptep_get() or set_pte() in header files, will loadable modules fail to build with undefined symbol errors?

It looks like this function and __kasan_unpoison_pte() are missing an EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL() declaration.

mm/kasan/common.c void __kasan_poison_slab(struct slab *slab) · line 174

Quoted 5 lines
mm/kasan/common.c +5 -0
+
+void __kasan_unpoison_pte(pte_t *pte, int nr)
+{
+	kasan_unpoison(pte, sizeof(*pte) * nr, false);
+}

[Severity: Medium]
kasan_unpoison() implicitly rounds the size up to the nearest KASAN_GRANULE_SIZE, which is 8 bytes.

On 32-bit architectures with 4-byte PTEs, could unpoisoning a single PTE inadvertently unpoison the adjacent PTE and corrupt its tracking state?

Quoted 4 lines
mm/kasan/common.c +1 -0
+
 void __kasan_unpoison_new_object(struct kmem_cache *cache, void *object)
 {
 	kasan_unpoison(object, cache->object_size, false);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
author maintainer
#7 lore raw

Re: [PATCH v5 3/4] mm/kasan: Introduce helpers for lazy MMU mode sanitizer

Quoted 37 lines · On Mon, Jul 13, 2026 at 02:30:23PM +0000, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] The newly introduced KASAN PTE helpers lack EXPORT_SYMBOL, which will cause module build failures when utilized in inline page table accessors.
- [Medium] The generic kasan_[un]poison_pte API inherently conflicts with KASAN tracking requirements on architectures where sizeof(pte_t) < KASAN_GRANULE_SIZE (e.g., 32-bit architectures), causing WARN_ON splats and silent shadow memory corruption.
- [Medium] KASAN poisoning of PTEs during lazy MMU mode fundamentally breaks concurrent lockless PTE readers (e.g., GUP-fast), causing false-positive KASAN splats.
--

--- Patch [3]: [PATCH v5 3/4] mm/kasan: Introduce helpers for lazy MMU mode sanitizer --- commit ad7a1a1292ef9676ee173915cfe50c0da8ea27a4
Author: Alexander Gordeev <[email protected]>

mm/kasan: Introduce helpers for lazy MMU mode sanitizer

This commit introduces KASAN helpers to allow architectures to catch illegitimate direct PTE accesses while lazy MMU mode is enabled. It provides kasan_poison_pte() and kasan_unpoison_pte() routines, along with a new KASAN_LAZY_MMU_PTE shadow bug type.

Quoted 4 lines
mm/kasan/common.c +0 -0
index b7d05c2a6d93d..cbf68680614e5 100644

[ ... ]

mm/kasan/common.c void __kasan_poison_slab(struct slab *slab) · line 169

Quoted 8 lines
mm/kasan/common.c +4 -0
@@ -163,6 +163,16 @@ void __kasan_poison_slab(struct slab *slab)
 		     KASAN_SLAB_REDZONE, false);
 }
 
+void __kasan_poison_pte(pte_t *pte, int nr)
+{
+	kasan_poison(pte, sizeof(*pte) * nr, KASAN_LAZY_MMU_PTE, false);
+}

[Severity: Medium]
Since lazy MMU mode is generally a per-thread or per-CPU state, but KASAN shadow memory is a globally shared mapping, will poisoning a live PTE page here trigger false-positive KASAN splats for concurrent legitimate lockless page table walkers?

Quoted 3 lines

For instance, if get_user_pages_fast() or handle_pte_fault() executes a READ_ONCE() on another CPU against this PTE, it seems they would hit the globally poisoned shadow memory.

Yes, and I learned it hard way. As result, ptep_get_lockless() is marked as __no_sanitize_address on s390. That is ugly, but that is the only way I came up with to avoid the above issue.

Quoted 6 lines

[Severity: Medium]
On 32-bit architectures where sizeof(pte_t) is 4 bytes, kasan_poison() will receive a size of 4 when nr is 1.

Since generic KASAN enforces an 8-byte alignment constraint, will this trigger the WARN_ON(size & KASAN_GRANULE_MASK) check?

I will exclude platforms with such pgtable entries altogether.

Quoted 5 lines

[Severity: Medium]
If architectures call this helper from inline page table accessors like ptep_get() or set_pte() in header files, will loadable modules fail to build with undefined symbol errors?

Quoted 8 lines

It looks like this function and __kasan_unpoison_pte() are missing an EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL() declaration.

mm/kasan/common.c void __kasan_poison_slab(struct slab *slab) · line 174

Quoted 5 lines
mm/kasan/common.c +5 -0
+
+void __kasan_unpoison_pte(pte_t *pte, int nr)
+{
+	kasan_unpoison(pte, sizeof(*pte) * nr, false);
+}

Will fix.

Quoted 11 lines

[Severity: Medium]
kasan_unpoison() implicitly rounds the size up to the nearest KASAN_GRANULE_SIZE, which is 8 bytes.

On 32-bit architectures with 4-byte PTEs, could unpoisoning a single PTE inadvertently unpoison the adjacent PTE and corrupt its tracking state?

Quoted 4 lines
mm/kasan/common.c +1 -0
+
 void __kasan_unpoison_new_object(struct kmem_cache *cache, void *object)
 {
 	kasan_unpoison(object, cache->object_size, false);

Same as for kasan_poison().

Quoted 2 lines
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3

Thanks!

author maintainer
#8 lore raw

PATCH v5 4/4 s390/mm: Lazy MMU mode sanitizer

In reply to Alexander Gordeev

Detect PTE entries access in lazy MMU mode by means other than set_pte() and ptep_get() primitives, which would be a read hazard.

The access to kasan shadow memory from ptep_get_lockless() mistakenly hits invalid access in case a concurrent lazy MMU access to the same PTE is happening. To avoid that disable instrumentation for ptep_get_lockless() altogether.

---
 arch/s390/include/asm/pgtable.h |  6 ++++++
 arch/s390/mm/lazy_mmu.c         | 27 +++++++++++++++++++++++----
 2 files changed, 29 insertions(+), 4 deletions(-)
arch/s390/include/asm/pgtable.h +6 -0
index 9c8ff55cb535..19b5b4277efc 100644
@@ -1049,6 +1049,12 @@ static inline void set_pte(pte_t *ptep, pte_t pte)
 		__set_pte(ptep, pte);
 }
 
+#define ptep_get_lockless ptep_get_lockless
+static inline __no_sanitize_address pte_t ptep_get_lockless(pte_t *ptep)
+{
+	return READ_ONCE(*ptep);
+}
+
 static inline pte_t __ptep_get(pte_t *ptep)
 {
 	return READ_ONCE(*ptep);
arch/s390/mm/lazy_mmu.c +23 -4
index d75b93d9b0de..ee2385897bc7 100644
@@ -63,10 +63,13 @@ static int invalidate_pte_range(struct mm_struct *mm, unsigned long addr,
 }
 
 static void set_pte_range(struct mm_struct *mm, unsigned long addr,
-			  pte_t *ptep, pte_t *end, pte_t *cache)
+			  pte_t *start, pte_t *end, pte_t *cache)
 {
-	int i, nr_ptes;
+	int nr_ptes, nr_total = end - start;
+	pte_t *ptep = start;
+	int i;
 
+	kasan_unpoison_pte(start, nr_total);
 	while (ptep < end) {
 		nr_ptes = invalidate_pte_range(mm, addr, ptep, end);
 
@@ -77,6 +80,7 @@ static void set_pte_range(struct mm_struct *mm, unsigned long addr,
 
 		addr += nr_ptes * PAGE_SIZE;
 	}
+	kasan_poison_pte(start, nr_total);
 }
 
 static void enter_ipte_norange(void)
@@ -94,6 +98,7 @@ static void enter_ipte_range(struct mm_struct *mm,
 			     unsigned long addr, unsigned long end, pte_t *pte)
 {
 	struct ipte_range *range;
+	unsigned int nr_ptes;
 
 	if (!test_facility(13))
 		return;
@@ -105,6 +110,9 @@ static void enter_ipte_range(struct mm_struct *mm,
 	range->base_addr = addr;
 	range->base_end = end;
 	range->base_pte = pte;
+
+	nr_ptes = (range->base_end - range->base_addr) / PAGE_SIZE;
+	kasan_poison_pte(range->base_pte, nr_ptes);
 }
 
 static void leave_ipte_range(void)
@@ -112,6 +120,7 @@ static void leave_ipte_range(void)
 	pte_t *ptep, *start, *start_cache, *cache;
 	unsigned long start_addr, addr;
 	struct ipte_range *range;
+	unsigned int nr_ptes;
 	int start_idx;
 
 	if (!test_facility(13))
@@ -148,6 +157,9 @@ static void leave_ipte_range(void)
 	range->end_pte = NULL;
 
 done:
+	nr_ptes = (range->base_end - range->base_addr) / PAGE_SIZE;
+	kasan_unpoison_pte(range->base_pte, nr_ptes);
+
 	range->mm = NULL;
 	range->base_addr = 0;
 	range->base_end = 0;
@@ -227,10 +239,17 @@ static void __ipte_range_set_pte(struct ipte_range *range, pte_t *ptep, pte_t pt
 static pte_t __ipte_range_ptep_get(struct ipte_range *range, pte_t *ptep)
 {
 	unsigned int idx = ptep - range->base_pte;
+	pte_t pte;
 
 	lockdep_assert_preemption_disabled();
-	if (pte_val(range->cache[idx]) == PTE_POISON)
-		return __ptep_get(ptep);
+	if (pte_val(range->cache[idx]) == PTE_POISON) {
+		kasan_unpoison_pte(ptep, 1);
+		pte = __ptep_get(ptep);
+		kasan_poison_pte(ptep, 1);
+
+		return pte;
+	}
+
 	return range->cache[idx];
 }
 
Signature · 1 line
2.53.0