[RFC PATCH v2 1/4] arm64: mm: Map fixmap PTE tables r/o in the linear map
From: Ard Biesheuvel
Date: Thu Aug 27 2026 - 12:49:34 EST
From: Ard Biesheuvel <ardb@xxxxxxxxxx>
Without physical KASLR, the fixmap page tables will appear at an a
priori known offset in the physical address space, and due to the lack
of randomization, the linear map carries a writeable alias of the fixmap
PTE pages, which appears at an offset in the kernel VA space that is
also predictable.
Given that the placement of the fixmap area is never randomized either,
a single store to this linear alias region is sufficient to map any
physical page with any permissions at a known offset in the kernel VA
space, including on top of the PTI trampoline.
Avoid this, by remapping the fixmap PTE pages read-only in the linear
map. This is possible because all updates to bm_pte[] occur via the
mapping of the kernel image in the vmap area. A read-only mapping is
still needed for things like ptdump that walk the page tables.
Signed-off-by: Ard Biesheuvel <ardb@xxxxxxxxxx>
---
arch/arm64/include/asm/fixmap.h | 3 +++
arch/arm64/mm/fixmap.c | 8 +++++---
arch/arm64/mm/mmu.c | 8 ++++++++
3 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h
index 170c3502d723..9191125738e9 100644
--- a/arch/arm64/include/asm/fixmap.h
+++ b/arch/arm64/include/asm/fixmap.h
@@ -112,6 +112,9 @@ enum fixed_addresses {
void __init early_fixmap_init(void);
+extern pte_t fixmap_bm_pte[][PTRS_PER_PTE];
+extern const size_t fixmap_bm_pte_size;
+
#define __early_set_fixmap __set_fixmap
extern void __set_fixmap(enum fixed_addresses idx, phys_addr_t phys, pgprot_t prot);
diff --git a/arch/arm64/mm/fixmap.c b/arch/arm64/mm/fixmap.c
index f66a0016dd02..3a8cf6de6a7d 100644
--- a/arch/arm64/mm/fixmap.c
+++ b/arch/arm64/mm/fixmap.c
@@ -31,13 +31,15 @@ static_assert(NR_BM_PMD_TABLES == 1);
#define BM_PTE_TABLE_IDX(addr) __BM_TABLE_IDX(addr, PMD_SHIFT)
-static pte_t bm_pte[NR_BM_PTE_TABLES][PTRS_PER_PTE] __bss_pgtbl;
+pte_t fixmap_bm_pte[NR_BM_PTE_TABLES][PTRS_PER_PTE] __bss_pgtbl;
static pmd_t bm_pmd[PTRS_PER_PMD] __bss_pgtbl __maybe_unused;
static pud_t bm_pud[PTRS_PER_PUD] __bss_pgtbl __maybe_unused;
+const size_t fixmap_bm_pte_size = sizeof(fixmap_bm_pte);
+
static inline pte_t *fixmap_pte(unsigned long addr)
{
- return &bm_pte[BM_PTE_TABLE_IDX(addr)][pte_index(addr)];
+ return &fixmap_bm_pte[BM_PTE_TABLE_IDX(addr)][pte_index(addr)];
}
static void __init early_fixmap_init_pte(pmd_t *pmdp, unsigned long addr)
@@ -46,7 +48,7 @@ static void __init early_fixmap_init_pte(pmd_t *pmdp, unsigned long addr)
pte_t *ptep;
if (pmd_none(pmd)) {
- ptep = bm_pte[BM_PTE_TABLE_IDX(addr)];
+ ptep = fixmap_bm_pte[BM_PTE_TABLE_IDX(addr)];
__pmd_populate(pmdp, __pa_symbol(ptep),
PMD_TYPE_TABLE | PMD_TABLE_AF);
}
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 79d90226fd5d..9c1aa838e9d5 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -1184,6 +1184,7 @@ static void __init map_mem(void)
phys_addr_t init_begin = __pa_symbol(__init_begin);
phys_addr_t init_end = __pa_symbol(__init_end);
phys_addr_t kernel_end = __pa_symbol(__bss_stop);
+ phys_addr_t fixmap_pte_base = __pa_symbol(fixmap_bm_pte);
phys_addr_t start, end;
int flags = NO_EXEC_MAPPINGS;
u64 i;
@@ -1225,6 +1226,9 @@ static void __init map_mem(void)
__map_memblock(init_end, kernel_end, pgprot_tagged(PAGE_KERNEL),
flags);
+ __map_memblock(fixmap_pte_base, fixmap_pte_base + fixmap_bm_pte_size,
+ pgprot_tagged(PAGE_KERNEL), flags);
+
/* map all the memory banks */
for_each_mem_range(i, &start, &end) {
/*
@@ -1268,6 +1272,10 @@ void mark_rodata_ro(void)
(unsigned long)_stext - (unsigned long)_text,
PAGE_KERNEL_RO);
+ update_mapping_prot(__pa_symbol(fixmap_bm_pte),
+ (unsigned long)lm_alias(fixmap_bm_pte),
+ fixmap_bm_pte_size, PAGE_KERNEL_RO);
+
/* Map the kernel data/bss as invalid in the linear map */
mark_linear_data_alias_valid(false);
}
--
2.55.0.887.g758fc8c411-goog