* [PATCH v2 1/4] x86/mm: Don't randomize the CPU entry area with KMSAN
2026-11-01 17:04 [PATCH v2 0/4] x86/kmsan: Fix two bugs in the metadata lookup Viorel Cernateanu via B4 Relay
@ 2026-11-01 17:04 ` Viorel Cernateanu via B4 Relay
2026-11-01 17:04 ` [PATCH v2 2/4] x86/kmsan: Use the per-CPU area base for metadata lookup Viorel Cernateanu via B4 Relay
` (2 subsequent siblings)
3 siblings, 0 replies; 7+ messages in thread
From: Viorel Cernateanu via B4 Relay @ 2026-11-01 17:04 UTC (permalink / raw)
To: Dave Hansen, Andy Lutomirski, Peter Zijlstra, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, x86, H. Peter Anvin,
Alexander Potapenko, Marco Elver, Dmitry Vyukov, Andrew Morton,
Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Kees Cook
Cc: Borislav Petkov, linux-kernel, kasan-dev, linux-mm,
Ilya Leoshkevich, Viorel Cernateanu
From: Viorel Cernateanu <vrilutza@gmail.com>
arch_kmsan_get_meta_or_null() derives the CPU number from the offset
of an address within the CPU entry area. This assumes that the areas
are laid out in CPU order.
With KASLR, init_cea_offsets() assigns each CPU a random slot. The
metadata lookup treats that slot number as a CPU number and can pass
an out-of-range index to get_cpu_entry_area().
On an Ivy Bridge laptop, this causes a KMSAN kernel to oops during
boot while the hard lockup detector sets up its perf event. A QEMU
test that reads one byte from the current CPU's GDT alias reproduces
the out-of-bounds access:
UBSAN: array-index-out-of-bounds in arch/x86/mm/cpu_entry_area.c:25:9
index 2100006 is out of range for type 'unsigned long[64]'
RIP: 0010:get_cpu_entry_area+0x14/0x50
Keep the CPU entry areas in CPU order when KMSAN is enabled. This
allows the metadata lookup to identify the CPU directly, without
searching every CPU's area on each lookup.
This disables CPU entry area randomization for KMSAN kernels.
Kernel text randomization remains enabled.
Fixes: 97e3d26b5e5f ("x86/mm: Randomize per-cpu entry area")
Suggested-by: Alexander Potapenko <glider@google.com>
Signed-off-by: Viorel Cernateanu <vrilutza@gmail.com>
---
arch/x86/mm/cpu_entry_area.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/arch/x86/mm/cpu_entry_area.c b/arch/x86/mm/cpu_entry_area.c
index 575f863f3c75..ce9b5d0e968d 100644
--- a/arch/x86/mm/cpu_entry_area.c
+++ b/arch/x86/mm/cpu_entry_area.c
@@ -30,7 +30,11 @@ static __init void init_cea_offsets(void)
unsigned int max_cea;
unsigned int i, j;
- if (!kaslr_enabled()) {
+ /*
+ * KMSAN works out the CPU of a CPU entry area address from its offset,
+ * see arch_kmsan_get_meta_or_null(), so keep the areas in CPU order.
+ */
+ if (!kaslr_enabled() || IS_ENABLED(CONFIG_KMSAN)) {
for_each_possible_cpu(i)
per_cpu(_cea_offset, i) = i;
return;
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v2 2/4] x86/kmsan: Use the per-CPU area base for metadata lookup
2026-11-01 17:04 [PATCH v2 0/4] x86/kmsan: Fix two bugs in the metadata lookup Viorel Cernateanu via B4 Relay
2026-11-01 17:04 ` [PATCH v2 1/4] x86/mm: Don't randomize the CPU entry area with KMSAN Viorel Cernateanu via B4 Relay
@ 2026-11-01 17:04 ` Viorel Cernateanu via B4 Relay
2026-11-01 17:04 ` [PATCH v2 3/4] mm: Factor __pfn_valid() out of pfn_valid() Viorel Cernateanu via B4 Relay
2026-11-01 17:04 ` [PATCH v2 4/4] x86/kmsan: Don't call instrumented code from kmsan_virt_addr_valid() Viorel Cernateanu via B4 Relay
3 siblings, 0 replies; 7+ messages in thread
From: Viorel Cernateanu via B4 Relay @ 2026-11-01 17:04 UTC (permalink / raw)
To: Dave Hansen, Andy Lutomirski, Peter Zijlstra, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, x86, H. Peter Anvin,
Alexander Potapenko, Marco Elver, Dmitry Vyukov, Andrew Morton,
Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Kees Cook
Cc: Borislav Petkov, linux-kernel, kasan-dev, linux-mm,
Ilya Leoshkevich, Viorel Cernateanu
From: Viorel Cernateanu <vrilutza@gmail.com>
The per-CPU entry areas start at CPU_ENTRY_AREA_PER_CPU, one page
above CPU_ENTRY_AREA_BASE, after the read-only IDT page.
arch_kmsan_get_meta_or_null() computes the CPU number relative to
CPU_ENTRY_AREA_BASE. With the areas laid out in CPU order, this
attributes the last page of each CPU's area to the next CPU. The
subsequent offset check rejects the address, so KMSAN cannot find
its metadata. At the end of the populated area, the lookup can also
select a CPU that is not possible.
Compute the CPU number relative to CPU_ENTRY_AREA_PER_CPU and
exclude the IDT page from the lookup. Check that the resulting CPU
is within nr_cpu_ids and possible before accessing its per-CPU data.
Remove the always-false negative check on the unsigned offset.
Fixes: ce732a7520b0 ("x86: kmsan: handle CPU entry area")
Signed-off-by: Viorel Cernateanu <vrilutza@gmail.com>
---
arch/x86/include/asm/kmsan.h | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/arch/x86/include/asm/kmsan.h b/arch/x86/include/asm/kmsan.h
index d91b37f5b4bb..c6715b2db0dc 100644
--- a/arch/x86/include/asm/kmsan.h
+++ b/arch/x86/include/asm/kmsan.h
@@ -13,6 +13,7 @@
#include <asm/cpu_entry_area.h>
#include <asm/processor.h>
+#include <linux/cpumask.h>
#include <linux/mmzone.h>
DECLARE_PER_CPU(char[CPU_ENTRY_AREA_SIZE], cpu_entry_area_shadow);
@@ -32,14 +33,17 @@ static inline void *arch_kmsan_get_meta_or_null(void *addr, bool is_origin)
unsigned long addr64 = (unsigned long)addr;
char *metadata_array;
unsigned long off;
- int cpu;
+ unsigned int cpu;
- if ((addr64 < CPU_ENTRY_AREA_BASE) ||
- (addr64 >= (CPU_ENTRY_AREA_BASE + CPU_ENTRY_AREA_MAP_SIZE)))
+ /* The per-CPU areas start after the read-only IDT page. */
+ if (addr64 < CPU_ENTRY_AREA_PER_CPU ||
+ addr64 >= CPU_ENTRY_AREA_BASE + CPU_ENTRY_AREA_MAP_SIZE)
+ return NULL;
+ cpu = (addr64 - CPU_ENTRY_AREA_PER_CPU) / CPU_ENTRY_AREA_SIZE;
+ if (cpu >= nr_cpu_ids || !cpu_possible(cpu))
return NULL;
- cpu = (addr64 - CPU_ENTRY_AREA_BASE) / CPU_ENTRY_AREA_SIZE;
off = addr64 - (unsigned long)get_cpu_entry_area(cpu);
- if ((off < 0) || (off >= CPU_ENTRY_AREA_SIZE))
+ if (off >= CPU_ENTRY_AREA_SIZE)
return NULL;
metadata_array = is_origin ? cpu_entry_area_origin :
cpu_entry_area_shadow;
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v2 3/4] mm: Factor __pfn_valid() out of pfn_valid()
2026-11-01 17:04 [PATCH v2 0/4] x86/kmsan: Fix two bugs in the metadata lookup Viorel Cernateanu via B4 Relay
2026-11-01 17:04 ` [PATCH v2 1/4] x86/mm: Don't randomize the CPU entry area with KMSAN Viorel Cernateanu via B4 Relay
2026-11-01 17:04 ` [PATCH v2 2/4] x86/kmsan: Use the per-CPU area base for metadata lookup Viorel Cernateanu via B4 Relay
@ 2026-11-01 17:04 ` Viorel Cernateanu via B4 Relay
2026-11-01 18:03 ` David Hildenbrand (Arm)
2026-11-01 20:02 ` Mike Rapoport
2026-11-01 17:04 ` [PATCH v2 4/4] x86/kmsan: Don't call instrumented code from kmsan_virt_addr_valid() Viorel Cernateanu via B4 Relay
3 siblings, 2 replies; 7+ messages in thread
From: Viorel Cernateanu via B4 Relay @ 2026-11-01 17:04 UTC (permalink / raw)
To: Dave Hansen, Andy Lutomirski, Peter Zijlstra, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, x86, H. Peter Anvin,
Alexander Potapenko, Marco Elver, Dmitry Vyukov, Andrew Morton,
Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Kees Cook
Cc: Borislav Petkov, linux-kernel, kasan-dev, linux-mm,
Ilya Leoshkevich, Viorel Cernateanu
From: Viorel Cernateanu <vrilutza@gmail.com>
KMSAN needs to validate a PFN during metadata lookup without entering
instrumented code. Calling pfn_valid() also calls
rcu_read_lock_sched(), which can enter instrumented code and recurse
into the metadata lookup.
Extract the PFN checks into __pfn_valid(), whose caller must already
be in an RCU-sched read-side critical section. Keep pfn_valid() as
the wrapper that acquires and releases that protection.
This lets KMSAN reuse the existing PFN validation while providing
the required protection without instrumented helpers.
The wrapper now also holds the RCU-sched read lock during the
initial range checks, which previously ran before acquiring it.
Suggested-by: Alexander Potapenko <glider@google.com>
Signed-off-by: Viorel Cernateanu <vrilutza@gmail.com>
---
include/linux/mmzone.h | 42 +++++++++++++++++++++++++-----------------
1 file changed, 25 insertions(+), 17 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index ecd1ebd49756..819042264088 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2266,21 +2266,13 @@ static inline bool pfn_section_first_valid(struct mem_section *ms, unsigned long
#endif
#ifndef CONFIG_HAVE_ARCH_PFN_VALID
-/**
- * pfn_valid - check if there is a valid memory map entry for a PFN
- * @pfn: the page frame number to check
- *
- * Check if there is a valid memory map entry aka struct page for the @pfn.
- * Note, that availability of the memory map entry does not imply that
- * there is actual usable memory at that @pfn. The struct page may
- * represent a hole or an unusable page frame.
- *
- * Return: 1 for PFNs that have memory map entries and 0 otherwise
+/*
+ * Like pfn_valid(), but the caller must be in an RCU-sched read-side critical
+ * section, for example by disabling preemption.
*/
-static inline int pfn_valid(unsigned long pfn)
+static inline int __pfn_valid(unsigned long pfn)
{
struct mem_section *ms;
- int ret;
/*
* Ensure the upper PAGE_SHIFT bits are clear in the
@@ -2294,16 +2286,32 @@ static inline int pfn_valid(unsigned long pfn)
if (pfn_to_section_nr(pfn) >= NR_MEM_SECTIONS)
return 0;
ms = __pfn_to_section(pfn);
- rcu_read_lock_sched();
- if (!valid_section(ms)) {
- rcu_read_unlock_sched();
+ if (!valid_section(ms))
return 0;
- }
/*
* Traditionally early sections always returned pfn_valid() for
* the entire section-sized span.
*/
- ret = early_section(ms) || pfn_section_valid(ms, pfn);
+ return early_section(ms) || pfn_section_valid(ms, pfn);
+}
+
+/**
+ * pfn_valid - check if there is a valid memory map entry for a PFN
+ * @pfn: the page frame number to check
+ *
+ * Check if there is a valid memory map entry aka struct page for the @pfn.
+ * Note, that availability of the memory map entry does not imply that
+ * there is actual usable memory at that @pfn. The struct page may
+ * represent a hole or an unusable page frame.
+ *
+ * Return: 1 for PFNs that have memory map entries and 0 otherwise
+ */
+static inline int pfn_valid(unsigned long pfn)
+{
+ int ret;
+
+ rcu_read_lock_sched();
+ ret = __pfn_valid(pfn);
rcu_read_unlock_sched();
return ret;
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH v2 3/4] mm: Factor __pfn_valid() out of pfn_valid()
2026-11-01 17:04 ` [PATCH v2 3/4] mm: Factor __pfn_valid() out of pfn_valid() Viorel Cernateanu via B4 Relay
@ 2026-11-01 18:03 ` David Hildenbrand (Arm)
2026-11-01 20:02 ` Mike Rapoport
1 sibling, 0 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-01 18:03 UTC (permalink / raw)
To: vrilutza, Dave Hansen, Andy Lutomirski, Peter Zijlstra,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86,
H. Peter Anvin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Andrew Morton, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Kees Cook
Cc: Borislav Petkov, linux-kernel, kasan-dev, linux-mm, Ilya Leoshkevich
On 11/1/26 18:04, Viorel Cernateanu via B4 Relay wrote:
> From: Viorel Cernateanu <vrilutza@gmail.com>
>
> KMSAN needs to validate a PFN during metadata lookup without entering
> instrumented code. Calling pfn_valid() also calls
> rcu_read_lock_sched(), which can enter instrumented code and recurse
> into the metadata lookup.
>
> Extract the PFN checks into __pfn_valid(), whose caller must already
> be in an RCU-sched read-side critical section. Keep pfn_valid() as
> the wrapper that acquires and releases that protection.
>
> This lets KMSAN reuse the existing PFN validation while providing
> the required protection without instrumented helpers.
>
> The wrapper now also holds the RCU-sched read lock during the
> initial range checks, which previously ran before acquiring it.
>
> Suggested-by: Alexander Potapenko <glider@google.com>
> Signed-off-by: Viorel Cernateanu <vrilutza@gmail.com>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 3/4] mm: Factor __pfn_valid() out of pfn_valid()
2026-11-01 17:04 ` [PATCH v2 3/4] mm: Factor __pfn_valid() out of pfn_valid() Viorel Cernateanu via B4 Relay
2026-11-01 18:03 ` David Hildenbrand (Arm)
@ 2026-11-01 20:02 ` Mike Rapoport
1 sibling, 0 replies; 7+ messages in thread
From: Mike Rapoport @ 2026-11-01 20:02 UTC (permalink / raw)
To: vrilutza
Cc: Dave Hansen, Andy Lutomirski, Peter Zijlstra, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, x86, H. Peter Anvin,
Alexander Potapenko, Marco Elver, Dmitry Vyukov, Andrew Morton,
Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Suren Baghdasaryan, Michal Hocko, Kees Cook, Borislav Petkov,
linux-kernel, kasan-dev, linux-mm, Ilya Leoshkevich
On Sun, Nov 01, 2026 at 07:04:38PM +0200, Viorel Cernateanu via B4 Relay wrote:
> From: Viorel Cernateanu <vrilutza@gmail.com>
>
> KMSAN needs to validate a PFN during metadata lookup without entering
> instrumented code. Calling pfn_valid() also calls
> rcu_read_lock_sched(), which can enter instrumented code and recurse
> into the metadata lookup.
>
> Extract the PFN checks into __pfn_valid(), whose caller must already
> be in an RCU-sched read-side critical section. Keep pfn_valid() as
> the wrapper that acquires and releases that protection.
>
> This lets KMSAN reuse the existing PFN validation while providing
> the required protection without instrumented helpers.
>
> The wrapper now also holds the RCU-sched read lock during the
> initial range checks, which previously ran before acquiring it.
>
> Suggested-by: Alexander Potapenko <glider@google.com>
> Signed-off-by: Viorel Cernateanu <vrilutza@gmail.com>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
> include/linux/mmzone.h | 42 +++++++++++++++++++++++++-----------------
> 1 file changed, 25 insertions(+), 17 deletions(-)
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 4/4] x86/kmsan: Don't call instrumented code from kmsan_virt_addr_valid()
2026-11-01 17:04 [PATCH v2 0/4] x86/kmsan: Fix two bugs in the metadata lookup Viorel Cernateanu via B4 Relay
` (2 preceding siblings ...)
2026-11-01 17:04 ` [PATCH v2 3/4] mm: Factor __pfn_valid() out of pfn_valid() Viorel Cernateanu via B4 Relay
@ 2026-11-01 17:04 ` Viorel Cernateanu via B4 Relay
3 siblings, 0 replies; 7+ messages in thread
From: Viorel Cernateanu via B4 Relay @ 2026-11-01 17:04 UTC (permalink / raw)
To: Dave Hansen, Andy Lutomirski, Peter Zijlstra, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, x86, H. Peter Anvin,
Alexander Potapenko, Marco Elver, Dmitry Vyukov, Andrew Morton,
Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Kees Cook
Cc: Borislav Petkov, linux-kernel, kasan-dev, linux-mm,
Ilya Leoshkevich, Viorel Cernateanu
From: Viorel Cernateanu <vrilutza@gmail.com>
With CONFIG_DEBUG_PREEMPT enabled, a KMSAN kernel hangs during boot
immediately after KMSAN is enabled.
kmsan_virt_addr_valid() calls preempt_disable() and pfn_valid(), which
in turn calls rcu_read_lock_sched(). With CONFIG_DEBUG_PREEMPT, these
paths call the instrumented preempt_count_add(). Its instrumentation
requests metadata and re-enters kmsan_virt_addr_valid(), causing
recursion.
Use preempt_disable_notrace() and __pfn_valid() to validate the PFN
without entering those instrumented helpers. Disabling preemption
provides the RCU-sched read-side protection required by __pfn_valid().
Use preempt_enable_no_resched_notrace() on exit to avoid both
instrumentation and a reschedule from the metadata lookup.
Fixes: f6564fce256a ("mm, kmsan: fix infinite recursion due to RCU critical section")
Link: https://lore.kernel.org/20240308043448.masllzeqwht45d4j@M910t
Suggested-by: Alexander Potapenko <glider@google.com>
Signed-off-by: Viorel Cernateanu <vrilutza@gmail.com>
---
arch/x86/include/asm/kmsan.h | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/arch/x86/include/asm/kmsan.h b/arch/x86/include/asm/kmsan.h
index c6715b2db0dc..e36df95439df 100644
--- a/arch/x86/include/asm/kmsan.h
+++ b/arch/x86/include/asm/kmsan.h
@@ -85,18 +85,21 @@ static inline bool kmsan_virt_addr_valid(void *addr)
}
/*
- * pfn_valid() relies on RCU, and may call into the scheduler on exiting
- * the critical section. However, this would result in recursion with
- * KMSAN. Therefore, disable preemption here, and re-enable preemption
- * below while suppressing reschedules to avoid recursion.
+ * pfn_valid() takes rcu_read_lock_sched(), which can call instrumented
+ * code (preempt_count_add() with CONFIG_DEBUG_PREEMPT, lock_acquire()
+ * with CONFIG_DEBUG_LOCK_ALLOC). That code looks up metadata itself and
+ * would recurse here. Disable preemption with the notrace helpers
+ * instead, which keeps this an RCU-sched read-side critical section,
+ * and re-enable it without a reschedule to avoid entering the scheduler
+ * from the metadata lookup.
*
* Note, this sacrifices occasionally breaking scheduling guarantees.
* Although, a kernel compiled with KMSAN has already given up on any
* performance guarantees due to being heavily instrumented.
*/
- preempt_disable();
- ret = pfn_valid(x >> PAGE_SHIFT);
- preempt_enable_no_resched();
+ preempt_disable_notrace();
+ ret = __pfn_valid(x >> PAGE_SHIFT);
+ preempt_enable_no_resched_notrace();
return ret;
}
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread