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>,
"Ard Biesheuvel" <ardb+git@google.com>,
"Andy Lutomirski" <luto@kernel.org>,
"Catalin Marinas" <catalin.marinas@arm.com>,
"Dave Hansen" <dave.hansen@linux.intel.com>,
"David Hildenbrand (Arm)" <david@kernel.org>,
"Jann Horn" <jannh@google.com>, "Jeff Xu" <jeffxu@chromium.org>,
"Joey Gouly" <joey.gouly@arm.com>, "Kees Cook" <kees@kernel.org>,
"Linu Cherian" <linu.cherian@arm.com>,
"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>,
"Pierre-Clément Tosi" <ptosi@google.com>,
"Quentin Perret" <qperret@google.com>,
"Rick Edgecombe" <rick.p.edgecombe@intel.com>,
"Ryan Roberts" <ryan.roberts@arm.com>,
"Vlastimil Babka" <vbabka@kernel.org>,
"Will Deacon" <will@kernel.org>,
"Yang Shi" <yang@os.amperecomputing.com>,
"Yeoreum Yun" <yeoreum.yun@arm.com>,
linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, linux-mm@kvack.org,
x86@kernel.org, "Ira Weiny" <iweiny@kernel.org>,
"Lorenzo Stoakes" <ljs@kernel.org>,
"Thomas Gleixner" <tglx@kernel.org>
Subject: [PATCH RFC v10 13/22] mm: kpkeys: Protect early kernel page tables
Date: Mon, 09 Nov 2026 15:58:38 +0000 [thread overview]
Message-ID: <20261109-kpkeys-v10-13-aa6c4d9603bc@arm.com> (raw)
In-Reply-To: <20261109-kpkeys-v10-0-aa6c4d9603bc@arm.com>
Initial kernel page tables (kernel image, direct map, vmemmap, etc.)
are not allocated using pagetable_alloc() - the buddy allocator
isn't ready at that point. We need to use a different approach to
protect them.
This patch takes the "brute force" route: when
kpkeys_hardened_pgtables is enabled, walk all installed kernel
page table, setting the pkey of their backing pages in the direct
map along the way.
The standard pagewalk API is used for that purpose, albeit in a
slightly unusual way:
* Visiting an entry is used to set the pkey of the table it points
to, hence: .pmd_entry -> protect_pte_table
* PTE entries are always leaves, so no need to visit them (no
.pte_entry callback)
* The range is meant to capture all installed kernel page tables.
There doesn't seem to be a generic way to express this;
[PAGE_OFFSET, ULONG_MAX) seems to be good enough on arm64/x86.
Locking probably isn't necessary so early during boot, but taking
the mmap_write_lock shouldn't hurt either. We also assume that all
page tables installed after that point are allocated using
pagetable_alloc(), which seems to be the case, at least on arm64.
There are some more assumptions - see comment above
protect_early_kernel_pgtables().
This simple approach should reliably protect all early kernel page
tables without invasive changes. However, adding support for large
blocks (PMD/PUD) is likely to be difficult and/or inefficient as
splitting will ocur when protecting individual page table pages.
Modifying the early page table allocators directly may be
preferable.
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
---
mm/kpkeys_hardened_pgtables.c | 122 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 122 insertions(+)
diff --git a/mm/kpkeys_hardened_pgtables.c b/mm/kpkeys_hardened_pgtables.c
index 2311fd8da6bb..f6825a0e0689 100644
--- a/mm/kpkeys_hardened_pgtables.c
+++ b/mm/kpkeys_hardened_pgtables.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/kpkeys.h>
#include <linux/mm.h>
+#include <linux/pagewalk.h>
+#include <linux/pgtable.h>
#include <linux/set_memory.h>
#include <kunit/visibility.h>
@@ -54,10 +56,130 @@ void kpkeys_pgtable_free(struct page *page, unsigned int order)
__free_pages(page, order);
}
+static void __init protect_pgtable_page(void *page)
+{
+ int ret;
+
+ ret = set_direct_map_pkey((unsigned long)page, 1, KPKEYS_PKEY_PGTABLES);
+ WARN_ON_ONCE(ret);
+}
+
+static int __init protect_pte_table(pmd_t *pmd, unsigned long addr,
+ unsigned long next, struct mm_walk *walk)
+{
+ pmd_t entry = pmdp_get(pmd);
+
+ if (!pmd_present(entry) || pmd_leaf(entry))
+ return 0;
+
+ protect_pgtable_page(pte_offset_kernel(pmd, 0));
+
+ return 0;
+}
+
+static int __init protect_pmd_table(pud_t *pud, unsigned long addr,
+ unsigned long next, struct mm_walk *walk)
+{
+ pud_t entry;
+
+ if (mm_pmd_folded(walk->mm))
+ return 0;
+
+ entry = pudp_get(pud);
+ if (!pud_present(entry) || pud_leaf(entry))
+ return 0;
+
+ protect_pgtable_page(pmd_offset(pud, 0));
+
+ return 0;
+}
+
+static int __init protect_pud_table(p4d_t *p4d, unsigned long addr,
+ unsigned long next, struct mm_walk *walk)
+{
+ p4d_t entry;
+
+ if (mm_pud_folded(walk->mm))
+ return 0;
+
+ entry = p4dp_get(p4d);
+ if (!p4d_present(entry) || p4d_leaf(entry))
+ return 0;
+
+ protect_pgtable_page(pud_offset(p4d, 0));
+
+ return 0;
+}
+
+static int __init protect_p4d_table(pgd_t *pgd, unsigned long addr,
+ unsigned long next, struct mm_walk *walk)
+{
+ pgd_t entry;
+
+ if (mm_p4d_folded(walk->mm))
+ return 0;
+
+ entry = pgdp_get(pgd);
+ if (!pgd_present(entry) || pgd_leaf(entry))
+ return 0;
+
+ protect_pgtable_page(p4d_offset(pgd, 0));
+
+ return 0;
+}
+
+static const struct mm_walk_ops protect_pgtable_ops __initconst = {
+ .pmd_entry = protect_pte_table,
+ .pud_entry = protect_pmd_table,
+ .p4d_entry = protect_pud_table,
+ .pgd_entry = protect_p4d_table,
+};
+
+/*
+ * Protect all installed kernel page tables by walking them and setting the
+ * direct mapping of all backing pages to pkey KPKEYS_PKEY_PGTABLES. We assume
+ * all non-root levels (PTE to P4D) are page-sized (asserts below).
+ *
+ * The root PGD page is left untouched; it is normally accessed via the kimg
+ * mapping only. On arm64 it cannot be written directly via any mapping.
+ *
+ * This approach is unsafe if set_direct_map_pkey() may split blocks in the
+ * direct map; we rely on the direct map being fully PTE-mapped.
+ */
+static void __init protect_early_kernel_pgtables(void)
+{
+ int ret;
+
+ mmap_write_lock(&init_mm);
+ /*
+ * [PAGE_OFFSET, ULONG_MAX) covers all early kernel mappings on arm64
+ * and x86 (at least).
+ */
+ ret = walk_kernel_page_table_range(PAGE_OFFSET, ULONG_MAX,
+ &protect_pgtable_ops, NULL, NULL);
+ mmap_write_unlock(&init_mm);
+ WARN_ON_ONCE(ret);
+}
+
+static_assert(MAX_PTRS_PER_PTE * sizeof(pte_t) == PAGE_SIZE);
+
+#ifndef __PAGETABLE_PMD_FOLDED
+static_assert(MAX_PTRS_PER_PMD * sizeof(pmd_t) == PAGE_SIZE);
+#endif
+
+#ifndef __PAGETABLE_PUD_FOLDED
+static_assert(MAX_PTRS_PER_PUD * sizeof(pud_t) == PAGE_SIZE);
+#endif
+
+#ifndef __PAGETABLE_P4D_FOLDED
+static_assert(MAX_PTRS_PER_P4D * sizeof(p4d_t) == PAGE_SIZE);
+#endif
+
void __init kpkeys_hardened_pgtables_init(void)
{
if (!kpkeys_supported())
return;
static_branch_enable(&kpkeys_hardened_pgtables_key);
+ protect_early_kernel_pgtables();
}
--
2.56.0
next prev parent reply other threads:[~2026-11-09 16:01 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 15:58 [PATCH RFC v10 00/22] pkeys-based page table hardening Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 01/22] mm: Introduce kpkeys Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 02/22] set_memory: Introduce set_direct_map_pkey() stub Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 03/22] arm64: Introduce por_elx_set_pkey_perms() helper Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 04/22] arm64: Implement asm/kpkeys.h using POE Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 05/22] arm64: set_memory: Implement set_direct_map_pkey() Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 06/22] arm64: Disable EL2 trapping of POR_EL1 Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 07/22] arm64: Initialize POR_EL1 register on cpu_resume() Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 08/22] arm64: Context-switch POR_EL1 Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 09/22] arm64: mm: Enable overlays for all EL1 indirect permissions Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 10/22] arm64: Enable kpkeys Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 11/22] mm: kpkeys: Introduce kpkeys_hardened_pgtables feature Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 12/22] mm: kpkeys: Protect regular page tables Kevin Brodsky
2026-11-09 15:58 ` Kevin Brodsky [this message]
2026-11-09 15:58 ` [PATCH RFC v10 14/22] mm: kpkeys: Protect vmemmap " Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 15/22] arm64: kpkeys: Implement arch_supports_kpkeys_early() Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 16/22] arm64: kpkeys: Support KPKEYS_CTX_PGTABLES Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 17/22] arm64: kpkeys: Ensure the linear map can be modified Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 18/22] arm64: kpkeys: Guard page table writes Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 19/22] arm64: kpkeys: Batch KPKEYS_CTX_PGTABLES switches Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 20/22] arm64: kpkeys: Enable kpkeys_hardened_pgtables support Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 21/22] arm64: mm: Map kernel image alias of init_pg_dir read-only Kevin Brodsky
2026-11-09 15:58 ` [PATCH RFC v10 22/22] mm: Add basic tests for kpkeys_hardened_pgtables Kevin Brodsky
2026-11-09 19:28 ` Mark Brown
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=20261109-kpkeys-v10-13-aa6c4d9603bc@arm.com \
--to=kevin.brodsky@arm.com \
--cc=akpm@linux-foundation.org \
--cc=ardb+git@google.com \
--cc=broonie@kernel.org \
--cc=catalin.marinas@arm.com \
--cc=dave.hansen@linux.intel.com \
--cc=david@kernel.org \
--cc=iweiny@kernel.org \
--cc=jannh@google.com \
--cc=jeffxu@chromium.org \
--cc=joey.gouly@arm.com \
--cc=kees@kernel.org \
--cc=linu.cherian@arm.com \
--cc=linusw@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@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=ptosi@google.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
all inboxes | Powered by JetHome®