arm64: mm: Map fixmap page tables read-only
v2 cover arm64: mm: Map fixmap page tables read-only
From: Ard Biesheuvel <[email protected]>
This v2 now covers intermediate level page tables as well as the PTE level page table for the fixmap. The latter is a special case, as it
a) is only accessed via the kernel image's mapping, and never via the linear map (except for ptdump etc)
b) must be accessible via a read-write mapping, as all manipulation of read-only page table descriptors relies on the fixmap itself
and so it is treated separately. The intermediate page tables may be shared with other mappings in the upper kernel/vmalloc region, so they must be updatable using the ordinary APIs.
Build tested and boot tested on a Lenovo Yoga C630 using 16k pages.
v1: https://lore.kernel.org/all/[email protected]/
Ard Biesheuvel (4):
arm64: mm: Map fixmap PTE tables r/o in the linear map
arm64: mm: Use fault handler to permit swapper_pg_dir updates
arm64: mm: Create r/o page table region that permits updates
arm64: mm: Move fixmap intermediate page tables into .rodata
arch/arm64/include/asm/fixmap.h | 4 ++ arch/arm64/include/asm/linkage.h | 1 + arch/arm64/include/asm/pgtable.h | 55 ++++++++------------ arch/arm64/kernel/vmlinux.lds.S | 6 +++ arch/arm64/mm/fixmap.c | 12 +++-- arch/arm64/mm/mmu.c | 48 +++++++++++++---- 6 files changed, 78 insertions(+), 48 deletions(-)
Signature · 1 line
2.55.0.887.g758fc8c411-goog
RFC PATCH v2 1/4 arm64: mm: Map fixmap PTE tables r/o in the linear map
From: Ard Biesheuvel <[email protected]>
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.
--- 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(-)
arch/arm64/include/asm/fixmap.h +3 -0
@@ -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_fixmapextern void __set_fixmap (enum fixed_addresses idx,phys_addr_t phys,pgprot_t prot);
arch/arm64/mm/fixmap.c +5 -3
@@ -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 ); }
arch/arm64/mm/mmu.c +8 -0
@@ -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); }
Signature · 1 line
2.55.0.887.g758fc8c411-goog
Re: [RFC PATCH v2 1/4] arm64: mm: Map fixmap PTE tables r/o in the linear map
Quoted 30 lines · Ard Biesheuvel
From: Ard Biesheuvel <[email protected]>
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 <[email protected]>--- 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(-)arch/arm64/include/asm/fixmap.h +0
-0
arch/arm64/include/asm/fixmap.h enum fixed_addresses { · line 115
Quoted 4 lines
arch/arm64/include/asm/fixmap.h +1
-0@@ -112,6 +112,9 @@ enum fixed_addresses {void __init early_fixmap_init (void ); +extern pte_t fixmap_bm_pte[][PTRS_PER_PTE ];
Instead of fixmap_bm_pte_size, why not use the same inner size as the definition, and then simply use sizeof() where required (or make a macro out of it)? Both the inner and outer sizes have to match the definition so there's no risk of things going out of sync I think?
- Kevin
Quoted 2 lines
arch/arm64/include/asm/fixmap.h +1
-0+extern const size_t fixmap_bm_pte_size;
RFC PATCH v2 2/4 arm64: mm: Use fault handler to permit swapper_pg_dir updates
In reply to Ard BiesheuvelFrom: Ard Biesheuvel <[email protected]>
Instead of checking the destination of a page table descriptor update against the virtual address of swapper_pg_dir in the kernel image, and taking a dedicated path to perform such updates via a fixmap alias, rely on a fault handler to do so if the plain store faulted.
This is more efficient for the general case, and makes it more straight-forward to extend this logic to other page tables that may have been allocated statically in the .rodata region of the kernel image.
--- arch/arm64/include/asm/fixmap.h | 1 + arch/arm64/include/asm/pgtable.h | 49 +++++++++----------- arch/arm64/mm/mmu.c | 18 +++---- 3 files changed, 32 insertions(+), 36 deletions(-)
arch/arm64/include/asm/fixmap.h +1 -0
@@ -99,6 +99,7 @@ enum fixed_addresses {FIX_PUD ,FIX_P4D ,FIX_PGD , +FIX_PTVAL , __end_of_fixed_addresses };
arch/arm64/include/asm/pgtable.h +22 -27
93 lines not on this page · raw message
arch/arm64/mm/mmu.c +9 -9
@@ -63,34 +63,34 @@ static bool rodata_is_rw __ro_after_init = true;*/ long __section(".mmuoff.data.write") __early_cpu_boot_status; -static DEFINE_SPINLOCK (swapper_pgdir_lock);static DEFINE_MUTEX (fixmap_lock); -void noinstr set_swapper_pgd(pgd_t *pgdp, pgd_t pgd) +void noinstr try_set_readonly_ptval(ptval_t *ptvalp, ptval_t ptval) { -pgd_t *fixmap_pgdp; +static DEFINE_SPINLOCK (lock); + +BUG_ON (!in_swapper_pgdir (ptvalp));/* * Don't bother with the fixmap if swapper_pg_dir is still mapped * writable in the kernel mapping. */ if (rodata_is_rw) { -WRITE_ONCE (*pgdp, pgd); +WRITE_ONCE (*ptvalp, ptval);dsb (ishst);isb ();return ; } -spin_lock (&swapper_pgdir_lock); - fixmap_pgdp =pgd_set_fixmap (__pa_symbol (pgdp)); -WRITE_ONCE (*fixmap_pgdp, pgd); +guard (spinlock)(&lock); + ptvalp = (ptval_t *)set_fixmap_offset (FIX_PTVAL ,__pa_symbol (ptvalp)); +WRITE_ONCE (*ptvalp, ptval);/* * We need dsb(ishst) here to ensure the page-table-walker sees * our new entry before set_p?d() returns. The fixmap's * flush_tlb_kernel_range() via clear_fixmap() does this for us. */ -pgd_clear_fixmap (); -spin_unlock (&swapper_pgdir_lock); +clear_fixmap (FIX_PTVAL ); } pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn,
Signature · 1 line
2.55.0.887.g758fc8c411-goog
Re: [RFC PATCH v2 2/4] arm64: mm: Use fault handler to permit swapper_pg_dir updates
Quoted 23 lines · Ard Biesheuvel
From: Ard Biesheuvel <[email protected]>
Instead of checking the destination of a page table descriptor update against the virtual address of
swapper_pg_dirin the kernel image, and taking a dedicated path to perform such updates via a fixmap alias, rely on a fault handler to do so if the plain store faulted.This is more efficient for the general case, and makes it more straight-forward to extend this logic to other page tables that may have been allocated statically in the .rodata region of the kernel image.
Signed-off-by Ard Biesheuvel <[email protected]>--- arch/arm64/include/asm/fixmap.h | 1 + arch/arm64/include/asm/pgtable.h | 49 +++++++++----------- arch/arm64/mm/mmu.c | 18 +++---- 3 files changed, 32 insertions(+), 36 deletions(-)arch/arm64/include/asm/fixmap.h +0
-0
arch/arm64/include/asm/fixmap.h enum fixed_addresses { · line 101
Quoted 3 lines
arch/arm64/include/asm/fixmap.h +0
-0@@ -99,6 +99,7 @@ enum fixed_addresses {FIX_PUD ,FIX_P4D , FIX_PGD,
Doesn't FIX_PTVAL replace FIX_PGD (i.e. we can remove it)?
Quoted 112 lines
arch/arm64/include/asm/fixmap.h +1
-0+FIX_PTVAL , __end_of_fixed_addresses };arch/arm64/include/asm/pgtable.h +22
-27@@ -35,6 +35,7 @@#include <asm/cmpxchg.h> #include <asm/fixmap.h> #include <asm/por.h> +#include <asm/uaccess.h> #include <linux/mmdebug.h> #include <linux/mm_types.h> #include <linux/sched.h> @@ -814,7 +815,7 @@ extern pgd_t idmap_pg_dir[];extern pgd_t tramp_pg_dir[];extern pgd_t reserved_pg_dir[]; -extern void set_swapper_pgd (pgd_t *pgdp,pgd_t pgd); +void try_set_readonly_ptval (ptval_t *ptvalp,ptval_t ptval);static inline bool in_swapper_pgdir (void *addr) {@@ -824,17 +825,14 @@ static inline bool in_swapper_pgdir(void *addr)static inline void set_pmd (pmd_t *pmdp,pmd_t pmd) { -#ifdef __PAGETABLE_PMD_FOLDED -if (in_swapper_pgdir (pmdp)) { -set_swapper_pgd ((pgd_t *)pmdp,__pgd (pmd_val (pmd))); -return ; - } -#endif /* __PAGETABLE_PMD_FOLDED */ - -WRITE_ONCE (*pmdp, pmd); +__put_kernel_nofault (pmdp, &pmd, pmd_t, fault);if (pmd_valid (pmd))queue_pte_barriers (); +return ; + +fault: +try_set_readonly_ptval (&pmd_val (*pmdp),pmd_val (pmd)); }static inline void pmd_clear (pmd_t *pmdp)@@ -890,15 +888,14 @@ static inline bool pgtable_l4_enabled(void);static inline void set_pud (pud_t *pudp,pud_t pud) { -if (!pgtable_l4_enabled () &&in_swapper_pgdir (pudp)) { -set_swapper_pgd ((pgd_t *)pudp,__pgd (pud_val (pud))); -return ; - } - -WRITE_ONCE (*pudp, pud); +__put_kernel_nofault (pudp, &pud, pud_t, fault);if (pud_valid (pud))queue_pte_barriers (); +return ; + +fault: +try_set_readonly_ptval (&pud_val (*pudp),pud_val (pud)); }static inline void pud_clear (pud_t *pudp)@@ -971,13 +968,12 @@ static inline bool mm_pud_folded(const struct mm_struct *mm)static inline void set_p4d (p4d_t *p4dp,p4d_t p4d) { -if (in_swapper_pgdir (p4dp)) { -set_swapper_pgd ((pgd_t *)p4dp,__pgd (p4d_val (p4d))); -return ; - } - -WRITE_ONCE (*p4dp, p4d); +__put_kernel_nofault (p4dp, &p4d, p4d_t, fault);queue_pte_barriers (); +return ; + +fault: +try_set_readonly_ptval (&p4d_val (*p4dp),p4d_val (p4d)); }static inline void p4d_clear (p4d_t *p4dp)@@ -1099,13 +1095,12 @@ static inline bool mm_p4d_folded(const struct mm_struct *mm)static inline void set_pgd (pgd_t *pgdp,pgd_t pgd) { -if (in_swapper_pgdir (pgdp)) { -set_swapper_pgd (pgdp,__pgd (pgd_val (pgd))); -return ; - } - -WRITE_ONCE (*pgdp, pgd); +__put_kernel_nofault (pgdp, &pgd, pgd_t, fault);queue_pte_barriers (); +return ; + +fault: +try_set_readonly_ptval (&pgd_val (*pgdp),pgd_val (pgd)); }static inline void pgd_clear (pgd_t *pgdp)arch/arm64/mm/mmu.c +0
-0
arch/arm64/mm/mmu.c static bool rodata_is_rw __ro_after_init = true; · line 66
Quoted 4 lines
arch/arm64/mm/mmu.c +0
-1@@ -63,34 +63,34 @@ static bool rodata_is_rw __ro_after_init = true;*/ long __section(".mmuoff.data.write") __early_cpu_boot_status; -static DEFINE_SPINLOCK (swapper_pgdir_lock);
Might be nicer to change this in a separate patch as AFAICT it's not related/required for this patch.
Quoted 5 lines
arch/arm64/mm/mmu.c +1
-1static DEFINE_MUTEX (fixmap_lock); -void noinstr set_swapper_pgd(pgd_t *pgdp, pgd_t pgd) +void noinstr try_set_readonly_ptval(ptval_t *ptvalp, ptval_t ptval) {
arch/arm64/mm/mmu.c static bool rodata_is_rw __ro_after_init = true; · line 78
Quoted 10 lines
arch/arm64/mm/mmu.c +3
-1-pgd_t *fixmap_pgdp; +static DEFINE_SPINLOCK (lock); + +BUG_ON (!in_swapper_pgdir (ptvalp));/* * Don't bother with the fixmap if swapper_pg_dir is still mapped * writable in the kernel mapping. */ if (rodata_is_rw) {
Is that ever a valid situation? If rodata_is_rw, then we shouldn't be here in the first place and that deserves a BUG_ON(), no?
- Kevin
Quoted 24 lines
arch/arm64/mm/mmu.c +5
-6-WRITE_ONCE (*pgdp, pgd); +WRITE_ONCE (*ptvalp, ptval);dsb (ishst);isb ();return ; } -spin_lock (&swapper_pgdir_lock); - fixmap_pgdp =pgd_set_fixmap (__pa_symbol (pgdp)); -WRITE_ONCE (*fixmap_pgdp, pgd); +guard (spinlock)(&lock); + ptvalp = (ptval_t *)set_fixmap_offset (FIX_PTVAL ,__pa_symbol (ptvalp)); +WRITE_ONCE (*ptvalp, ptval);/* * We need dsb(ishst) here to ensure the page-table-walker sees * our new entry before set_p?d() returns. The fixmap's * flush_tlb_kernel_range() via clear_fixmap() does this for us. */ -pgd_clear_fixmap (); -spin_unlock (&swapper_pgdir_lock); +clear_fixmap (FIX_PTVAL ); } pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn,
RFC PATCH v2 3/4 arm64: mm: Create r/o page table region that permits updates
In reply to Ard BiesheuvelFrom: Ard Biesheuvel <[email protected]>
Generalize the handling of swapper_pg_dir, and apply the fixmap fallback for page table updates to all page tables in the .rodata..pgtbl section.
Currently, this holds only swapper_pg_dir, but this will be expanded in subsequent patches.
--- arch/arm64/include/asm/pgtable.h | 6 ----- arch/arm64/kernel/vmlinux.lds.S | 6 +++++ arch/arm64/mm/mmu.c | 26 +++++++++++++++++--- 3 files changed, 29 insertions(+), 9 deletions(-)
arch/arm64/include/asm/pgtable.h +0 -6
@@ -817,12 +817,6 @@ extern pgd_t reserved_pg_dir[];void try_set_readonly_ptval (ptval_t *ptvalp,ptval_t ptval); -static inline bool in_swapper_pgdir (void *addr) -{ -return ((unsigned long )addr &PAGE_MASK ) == - ((unsigned long )swapper_pg_dir &PAGE_MASK ); -} -static inline void set_pmd (pmd_t *pmdp,pmd_t pmd) {__put_kernel_nofault (pmdp, &pmd, pmd_t, fault);
arch/arm64/kernel/vmlinux.lds.S +6 -0
@@ -243,9 +243,15 @@ SECTIONS reserved_pg_dir = .; . += PAGE_SIZE; + __rodata_pgtbl_start = .; swapper_pg_dir = .; . += PAGE_SIZE; + .pgtbl.ro_after_init : ALIGN(PAGE_SIZE) { + *(.pgtbl.ro_after_init) + } + __rodata_pgtbl_end = .; + . = ALIGN(SEGMENT_ALIGN); __init_begin = .; __inittext_begin = .;
arch/arm64/mm/mmu.c +23 -3
@@ -65,17 +65,27 @@ long __section(".mmuoff.data.write") __early_cpu_boot_status;static DEFINE_MUTEX (fixmap_lock); +static struct range kimg_ropgtbl_range__ro_after_init ; + void noinstr try_set_readonly_ptval(ptval_t *ptvalp, ptval_t ptval) {static DEFINE_SPINLOCK (lock); +bool is_lm =__is_lm_address (ptvalp); +u64 pa = is_lm ?__pa (ptvalp) :__pa_symbol (ptvalp); -BUG_ON (!in_swapper_pgdir (ptvalp)); +if (!range_contains (&kimg_ropgtbl_range, + &DEFINE_RANGE (pa, pa +sizeof (ptval_t)))) +BUG ();/* -* Don't bother with the fixmap if swapper_pg_dir is still mapped +* Don't bother with the fixmap if .rodata is still mapped * writable in the kernel mapping. */ if (rodata_is_rw) { +/* no fault should have occurred for a kimg address */ +BUG_ON (!is_lm); + + ptvalp = (ptval_t *)__phys_to_kimg (pa);WRITE_ONCE (*ptvalp, ptval);dsb (ishst);isb ();
@@ -83,7 +93,7 @@ void noinstr try_set_readonly_ptval(ptval_t *ptvalp, ptval_t ptval) }guard (spinlock)(&lock); - ptvalp = (ptval_t *)set_fixmap_offset (FIX_PTVAL ,__pa_symbol (ptvalp)); + ptvalp = (ptval_t *)set_fixmap_offset (FIX_PTVAL , pa);WRITE_ONCE (*ptvalp, ptval);/* * We need dsb(ishst) here to ensure the page-table-walker sees
@@ -1177,6 +1187,14 @@ static inline void arm64_kfence_map_pool(void) { }#endif /* CONFIG_KFENCE */ +static void __init record_ropgtbl_phys_range (void ) +{ +extern const char __rodata_pgtbl_start[], __rodata_pgtbl_end[]; + + kimg_ropgtbl_range =DEFINE_RANGE (__pa_symbol (__rodata_pgtbl_start), +__pa_symbol (__rodata_pgtbl_end)); +} +static void __init map_mem (void ) {static const u64 direct_map_end =_PAGE_END (VA_BITS_MIN );
@@ -1189,6 +1207,8 @@ static void __init map_mem(void)int flags =NO_EXEC_MAPPINGS ;u64 i; +record_ropgtbl_phys_range (); +/* * Setting hierarchical PXNTable attributes on table entries covering * the linear region is only possible if it is guaranteed that no table
Signature · 1 line
2.55.0.887.g758fc8c411-goog
RFC PATCH v2 4/4 arm64: mm: Move fixmap intermediate page tables into .rodata
In reply to Ard BiesheuvelFrom: Ard Biesheuvel <[email protected]>
The fixmap intermediate page tables are allocated statically, are installed into the kernel's page table hierarchy early during boot, and control a slice of the kernel's virtual address space that is not subject to KASLR randomization.
Combined with the lack of randomization of the linear map, and the tendency of some Android bootloaders to place the kernel image at the base of DRAM in the physical space, the placement of these page tables produces a vulnerability that is comparatively easy to exploit.
Avoid this, by moving these intermediate page tables into .rodata, so that they cannot be manipulated directly via the linear map.
--- arch/arm64/include/asm/linkage.h | 1 + arch/arm64/mm/fixmap.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-)
arch/arm64/include/asm/linkage.h +1 -0
@@ -45,6 +45,7 @@#define _THIS_IP_ ({ unsigned long __ip; asm volatile("adr %0, ." : "=r" (__ip)); __ip; }) +#define __rodata_pgtbl __section(".pgtbl.ro_after_init") __aligned(PAGE_SIZE)#define __bss_pgtbl __section(".bss..pgtbl") __aligned(PAGE_SIZE)#endif
arch/arm64/mm/fixmap.c +2 -2
@@ -32,8 +32,8 @@ static_assert(NR_BM_PMD_TABLES == 1);#define BM_PTE_TABLE_IDX (addr) __BM_TABLE_IDX(addr, PMD_SHIFT) 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; +static pmd_t bm_pmd[PTRS_PER_PMD] __rodata_pgtbl; +static pud_t bm_pud[PTRS_PER_PUD] __rodata_pgtbl;const size_t fixmap_bm_pte_size =sizeof (fixmap_bm_pte);
Signature · 1 line
2.55.0.887.g758fc8c411-goog
Re: [RFC PATCH v2 4/4] arm64: mm: Move fixmap intermediate page tables into .rodata
Quoted 7 lines · Ard Biesheuvel
From: Ard Biesheuvel <[email protected]>
The fixmap intermediate page tables are allocated statically, are installed into the kernel's page table hierarchy early during boot, and control a slice of the kernel's virtual address space that is not subject to KASLR randomization.
Quoted 7 lines
Combined with the lack of randomization of the linear map, and the tendency of some Android bootloaders to place the kernel image at the base of DRAM in the physical space, the placement of these page tables produces a vulnerability that is comparatively easy to exploit.
Avoid this, by moving these intermediate page tables into .rodata, so that they cannot be manipulated directly via the linear map.
Would be good to mention that this works thanks to the previous patches, otherwise one would wonder why we didn't do this earlier :)
- Kevin
Quoted 33 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- arch/arm64/include/asm/linkage.h | 1 + arch/arm64/mm/fixmap.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-)arch/arm64/include/asm/linkage.h +1
-0@@ -45,6 +45,7 @@#define _THIS_IP_ ({ unsigned long __ip; asm volatile("adr %0, ." : "=r" (__ip)); __ip; }) +#define __rodata_pgtbl __section(".pgtbl.ro_after_init") __aligned(PAGE_SIZE)#define __bss_pgtbl __section(".bss..pgtbl") __aligned(PAGE_SIZE)#endif arch/arm64/mm/fixmap.c +2
-2@@ -32,8 +32,8 @@ static_assert(NR_BM_PMD_TABLES == 1);#define BM_PTE_TABLE_IDX (addr) __BM_TABLE_IDX(addr, PMD_SHIFT) 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; +static pmd_t bm_pmd[PTRS_PER_PMD] __rodata_pgtbl; +static pud_t bm_pud[PTRS_PER_PUD] __rodata_pgtbl;const size_t fixmap_bm_pte_size =sizeof (fixmap_bm_pte);
Quoted 19 lines · Ard Biesheuvel
From: Ard Biesheuvel <[email protected]>
This v2 now covers intermediate level page tables as well as the PTE level page table for the fixmap. The latter is a special case, as it
a) is only accessed via the kernel image's mapping, and never via the linear map (except for ptdump etc)
b) must be accessible via a read-write mapping, as all manipulation of read-only page table descriptors relies on the fixmap itself
and so it is treated separately. The intermediate page tables may be shared with other mappings in the upper kernel/vmalloc region, so they must be updatable using the ordinary APIs.
Build tested and boot tested on a Lenovo Yoga C630 using 16k pages.
Quoted 8 lines
Cc Ryan Roberts <[email protected]>Cc Anshuman Khandual <[email protected]>Cc Kevin Brodsky <[email protected]>Cc Liz Prucka <[email protected]>Cc Seth Jenkins <[email protected]>Cc Kees Cook <[email protected]>Cc Jann Horn <[email protected]>
Looks like the Cc's didn't propagate to the actual patches, fortunately my lei filters did catch this series ;)
Either way I quite like this series, it's an elegant approach and it should increase security without overhead, what's not to like!
I also considered it from the perspective of kpkeys protection [1] and I think they should work together fine. The kpkeys series still allows page table setters to write to all page tables, so if we get a fault there it must be because the target is read only, and not because of a pkey fault.
- Kevin
Quoted 29 lines · Kevin Brodsky
Quoted 19 lines · Ard Biesheuvel
From: Ard Biesheuvel <[email protected]>
This v2 now covers intermediate level page tables as well as the PTE level page table for the fixmap. The latter is a special case, as it
a) is only accessed via the kernel image's mapping, and never via the linear map (except for ptdump etc)
b) must be accessible via a read-write mapping, as all manipulation of read-only page table descriptors relies on the fixmap itself
and so it is treated separately. The intermediate page tables may be shared with other mappings in the upper kernel/vmalloc region, so they must be updatable using the ordinary APIs.
Build tested and boot tested on a Lenovo Yoga C630 using 16k pages.
Quoted 8 lines
Cc Ryan Roberts <[email protected]>Cc Anshuman Khandual <[email protected]>Cc Kevin Brodsky <[email protected]>Cc Liz Prucka <[email protected]>Cc Seth Jenkins <[email protected]>Cc Kees Cook <[email protected]>Cc Jann Horn <[email protected]>
Quoted 3 lines
Looks like the Cc's didn't propagate to the actual patches, fortunately my lei filters did catch this series ;)
Ugh I must have forgotten to put --cc-cover
Quoted 9 lines
Either way I quite like this series, it's an elegant approach and it should increase security without overhead, what's not to like!
I also considered it from the perspective of kpkeys protection [1] and I think they should work together fine. The kpkeys series still allows page table setters to write to all page tables, so if we get a fault there it must be because the target is read only, and not because of a pkey fault.
Excellent, thanks for confirming.