[PATCH v2 3/4] mm: Factor __pfn_valid() out of pfn_valid()

From: Viorel Cernateanu via B4 Relay

Date: Sun Nov 01 2026 - 12:05:37 EST


From: Viorel Cernateanu <vrilutza@xxxxxxxxx>

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@xxxxxxxxxx>
Signed-off-by: Viorel Cernateanu <vrilutza@xxxxxxxxx>
---
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