public inbox for linux-arm-kernel@lists.infradead.org
 help / color / mirror / Atom feed
From: Kevin Brodsky <kevin.brodsky@arm.com>
To: linux-hardening@vger.kernel.org
Cc: Kevin Brodsky <kevin.brodsky@arm.com>,
	 Andrew Morton <akpm@linux-foundation.org>,
	 Andy Lutomirski <luto@kernel.org>,
	 Catalin Marinas <catalin.marinas@arm.com>,
	 Dave Hansen <dave.hansen@linux.intel.com>,
	 "David Hildenbrand (Arm)" <david@kernel.org>,
	 Ira Weiny <ira.weiny@intel.com>, Jann Horn <jannh@google.com>,
	 Jeff Xu <jeffxu@chromium.org>, Joey Gouly <joey.gouly@arm.com>,
	 Kees Cook <kees@kernel.org>, Linus Walleij <linusw@kernel.org>,
	 Marc Zyngier <maz@kernel.org>, Mark Brown <broonie@kernel.org>,
	 Matthew Wilcox <willy@infradead.org>,
	Maxwell Bland <mbland@motorola.com>,
	 "Mike Rapoport (IBM)" <rppt@kernel.org>,
	 Peter Zijlstra <peterz@infradead.org>,
	 Pierre Langlois <pierre.langlois@arm.com>,
	 Quentin Perret <qperret@google.com>,
	 Rick Edgecombe <rick.p.edgecombe@intel.com>,
	 Ryan Roberts <ryan.roberts@arm.com>,
	Will Deacon <will@kernel.org>,
	 Yang Shi <yang@os.amperecomputing.com>,
	Yeoreum Yun <yeoreum.yun@arm.com>,
	 linux-arm-kernel@lists.infradead.org, linux-mm@kvack.org,
	x86@kernel.org,  Lorenzo Stoakes <ljs@kernel.org>,
	Thomas Gleixner <tglx@kernel.org>,
	 Vlastimil Babka <vbabka@kernel.org>
Subject: [PATCH RFC v7 13/24] mm: kpkeys: Introduce early page table allocator
Date: Tue, 05 May 2026 17:06:02 +0100	[thread overview]
Message-ID: <20260505-kpkeys-v7-13-20c0bdd97197@arm.com> (raw)
In-Reply-To: <20260505-kpkeys-v7-0-20c0bdd97197@arm.com>

The kpkeys_hardened_pgtables feature aims to protect all page table
pages (PTPs) by mapping them with a privileged pkey. This is primarily
handled by kpkeys_pgtable_alloc(), called from pagetable_alloc().
However, this does not cover PTPs allocated early, before the
buddy allocator is available. These PTPs are allocated by architecture
code, either 1. from static pools or 2. using the memblock allocator,
and should also be protected.

This patch addresses the second category: PTPs allocated via memblock.
Such PTPs are notably used to create the linear map. Protecting them as
soon as they are allocated would require modifying the linear map while
it is being created, which seems at best difficult. Instead, a
simple allocator is introduced, obtaining pages from memblock and
keeping track of all allocated ranges to set their pkey once it is
safe to do so. PTPs allocated at that stage are not freed, so there
is no need to manage a free list.

Since kpkeys_hardened_pgtables currently requires the linear map to
be PTE-mapped, we can directly allocate page by page using memblock,
without intermediate cache. We rely on memblock allocating
contiguous pages to minimise the number of tracked ranges.

The number of PTPs required to create the linear map is proportional to
the amount of available memory, which means it may be large. At such
an early point, the memblock allocator may however only track a
limited number of regions, and we size the tracking array
(allocated_ranges) accordingly. The array may be quite large as a
result (16KB on arm64), but it is discarded once boot has completed.

Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
 include/linux/kpkeys.h        |   7 +++
 mm/kpkeys_hardened_pgtables.c | 116 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 123 insertions(+)

diff --git a/include/linux/kpkeys.h b/include/linux/kpkeys.h
index c9f63415162b..544a2d954bc1 100644
--- a/include/linux/kpkeys.h
+++ b/include/linux/kpkeys.h
@@ -140,6 +140,8 @@ void kpkeys_pgtable_free(struct page *page, unsigned int order);
  */
 void kpkeys_hardened_pgtables_init(void);
 
+phys_addr_t kpkeys_physmem_pgtable_alloc(void);
+
 #else /* CONFIG_KPKEYS_HARDENED_PGTABLES */
 
 static inline bool kpkeys_hardened_pgtables_enabled(void)
@@ -161,6 +163,11 @@ static inline void kpkeys_pgtable_free(struct page *page, unsigned int order) {}
 
 static inline void kpkeys_hardened_pgtables_init(void) {}
 
+static inline phys_addr_t kpkeys_physmem_pgtable_alloc(void)
+{
+	return 0;
+}
+
 #endif /* CONFIG_KPKEYS_HARDENED_PGTABLES */
 
 #endif /* _LINUX_KPKEYS_H */
diff --git a/mm/kpkeys_hardened_pgtables.c b/mm/kpkeys_hardened_pgtables.c
index fff7e2a64b64..c7a8935571ac 100644
--- a/mm/kpkeys_hardened_pgtables.c
+++ b/mm/kpkeys_hardened_pgtables.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 #include <linux/kpkeys.h>
+#include <linux/memblock.h>
 #include <linux/mm.h>
 #include <linux/set_memory.h>
 
@@ -30,6 +31,9 @@ static int set_pkey_default(struct page *page, unsigned int nr_pages)
 	return ret;
 }
 
+/* pkeys physmem allocator (PPA) - implemented below */
+static void ppa_finalize(void);
+
 struct page *kpkeys_pgtable_alloc(gfp_t gfp, unsigned int order)
 {
 	struct page *page;
@@ -60,4 +64,116 @@ void __init kpkeys_hardened_pgtables_init(void)
 		return;
 
 	static_branch_enable(&kpkeys_hardened_pgtables_key);
+
+	ppa_finalize();
+}
+
+/*
+ * pkeys physmem allocator (PPA): allocator for very early page tables
+ * (especially for creating the linear map), based on memblock. Allocated
+ * ranges are tracked so that their pkey can be set once it is safe to do so.
+ */
+
+/*
+ * We may have to track many ranges when allocating page tables for the linear
+ * map, as their number grows with the amount of available memory. Assuming that
+ * memblock returns contiguous blocks whenever possible, the number of ranges
+ * to track cannot however exceed the number of regions that memblock itself
+ * tracks. memblock_allow_resize() hasn't been called yet at that point, so
+ * that limit is the size of the statically allocated array.
+ */
+#define PHYSMEM_MAX_RANGES	INIT_MEMBLOCK_MEMORY_REGIONS
+
+struct physmem_range {
+	phys_addr_t addr;
+	phys_addr_t size;
+};
+
+struct pkeys_physmem_allocator {
+	struct physmem_range allocated_ranges[PHYSMEM_MAX_RANGES];
+	unsigned int nr_allocated_ranges;
+};
+
+static struct pkeys_physmem_allocator pkeys_physmem_allocator __initdata;
+
+static int __init set_pkey_pgtable_phys(phys_addr_t pa, phys_addr_t size)
+{
+	unsigned long addr = (unsigned long)__va(pa);
+	int ret;
+
+	ret = set_memory_pkey(addr, size / PAGE_SIZE, KPKEYS_PKEY_PGTABLES);
+	pr_debug("%s: addr=%pa, size=%pa\n", __func__, &addr, &size);
+
+	WARN_ON(ret);
+	return ret;
+}
+
+static bool __init ppa_try_extend_last_range(phys_addr_t addr, phys_addr_t size)
+{
+	struct pkeys_physmem_allocator *ppa = &pkeys_physmem_allocator;
+	struct physmem_range *range;
+
+	if (!ppa->nr_allocated_ranges)
+		return false;
+
+	range = &ppa->allocated_ranges[ppa->nr_allocated_ranges - 1];
+
+	/* Merge the new range into the last range if they are contiguous */
+	if (addr == range->addr + range->size) {
+		range->size += size;
+		return true;
+	} else if (addr + size == range->addr) {
+		range->addr -= size;
+		range->size += size;
+		return true;
+	}
+
+	return false;
+}
+
+static void __init ppa_register_allocated_range(phys_addr_t addr,
+						phys_addr_t size)
+{
+	struct pkeys_physmem_allocator *ppa = &pkeys_physmem_allocator;
+	struct physmem_range *range;
+
+	if (!addr)
+		return;
+
+	if (ppa_try_extend_last_range(addr, size))
+		return;
+
+	/* Could not extend the last range, create a new one */
+	if (WARN_ON(ppa->nr_allocated_ranges >= PHYSMEM_MAX_RANGES))
+		return;
+
+	range = &ppa->allocated_ranges[ppa->nr_allocated_ranges++];
+	range->addr = addr;
+	range->size = size;
+}
+
+static void __init ppa_finalize(void)
+{
+	struct pkeys_physmem_allocator *ppa = &pkeys_physmem_allocator;
+
+	for (unsigned int i = 0; i < ppa->nr_allocated_ranges; i++) {
+		struct physmem_range *range = &ppa->allocated_ranges[i];
+
+		set_pkey_pgtable_phys(range->addr, range->size);
+	}
+}
+
+phys_addr_t __ref kpkeys_physmem_pgtable_alloc(void)
+{
+	size_t size = PAGE_SIZE;
+	phys_addr_t addr;
+
+	addr = memblock_phys_alloc_range(size, size, 0,
+					 MEMBLOCK_ALLOC_NOLEAKTRACE);
+	if (!addr)
+		return addr;
+
+	ppa_register_allocated_range(addr, size);
+
+	return addr;
 }

-- 
2.51.2



  parent reply	other threads:[~2026-05-05 16:08 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-05 16:05 [PATCH RFC v7 00/24] pkeys-based page table hardening Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 01/24] mm: Introduce kpkeys Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 02/24] set_memory: Introduce set_memory_pkey() stub Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 03/24] arm64: mm: Enable overlays for all EL1 indirect permissions Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 04/24] arm64: Introduce por_elx_set_pkey_perms() helper Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 05/24] arm64: Implement asm/kpkeys.h using POE Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 06/24] arm64: set_memory: Implement set_memory_pkey() Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 07/24] arm64: Context-switch POR_EL1 Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 08/24] arm64: Initialize POR_EL1 register on cpu_resume() Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 09/24] arm64: Enable kpkeys Kevin Brodsky
2026-05-05 16:05 ` [PATCH RFC v7 10/24] memblock: Move INIT_MEMBLOCK_* macros to header Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 11/24] mm: kpkeys: Introduce kpkeys_hardened_pgtables feature Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 12/24] mm: kpkeys: Protect regular page tables Kevin Brodsky
2026-05-05 16:06 ` Kevin Brodsky [this message]
2026-05-05 16:06 ` [PATCH RFC v7 14/24] mm: kpkeys: Protect vmemmap " Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 15/24] mm: kpkeys: Introduce hook for protecting static " Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 16/24] arm64: kpkeys: Implement arch_supports_kpkeys_early() Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 17/24] arm64: kpkeys: Support KPKEYS_CTX_PGTABLES Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 18/24] arm64: kpkeys: Ensure the linear map can be modified Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 19/24] arm64: kpkeys: Protect early page tables Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 20/24] arm64: kpkeys: Protect init_pg_dir Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 21/24] arm64: kpkeys: Guard page table writes Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 22/24] arm64: kpkeys: Batch KPKEYS_CTX_PGTABLES switches Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 23/24] arm64: kpkeys: Enable kpkeys_hardened_pgtables support Kevin Brodsky
2026-05-05 16:06 ` [PATCH RFC v7 24/24] mm: Add basic tests for kpkeys_hardened_pgtables Kevin Brodsky

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260505-kpkeys-v7-13-20c0bdd97197@arm.com \
    --to=kevin.brodsky@arm.com \
    --cc=akpm@linux-foundation.org \
    --cc=broonie@kernel.org \
    --cc=catalin.marinas@arm.com \
    --cc=dave.hansen@linux.intel.com \
    --cc=david@kernel.org \
    --cc=ira.weiny@intel.com \
    --cc=jannh@google.com \
    --cc=jeffxu@chromium.org \
    --cc=joey.gouly@arm.com \
    --cc=kees@kernel.org \
    --cc=linusw@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=luto@kernel.org \
    --cc=maz@kernel.org \
    --cc=mbland@motorola.com \
    --cc=peterz@infradead.org \
    --cc=pierre.langlois@arm.com \
    --cc=qperret@google.com \
    --cc=rick.p.edgecombe@intel.com \
    --cc=rppt@kernel.org \
    --cc=ryan.roberts@arm.com \
    --cc=tglx@kernel.org \
    --cc=vbabka@kernel.org \
    --cc=will@kernel.org \
    --cc=willy@infradead.org \
    --cc=x86@kernel.org \
    --cc=yang@os.amperecomputing.com \
    --cc=yeoreum.yun@arm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox