blob: f497adfd774d08a677e1362cf16f1e6f43891745 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 - Google Inc
* Author: Ard Biesheuvel <ardb@google.com>
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/memory.h>
#include <linux/mm.h>
#include <linux/sizes.h>
#include <asm/fixmap.h>
#include <asm/kernel-pgtable.h>
#include <asm/mmu_context.h>
#include <asm/pgalloc.h>
#include <asm/tlbflush.h>
#include <asm/sections.h>
static DEFINE_RAW_SPINLOCK(patch_pte_lock);
DEFINE_STATIC_KEY_FALSE(ro_page_tables);
static bool __initdata ro_page_tables_enabled = true;
static int __init parse_ro_page_tables(char *arg)
{
return strtobool(arg, &ro_page_tables_enabled);
}
early_param("ro_page_tables", parse_ro_page_tables);
static bool in_kernel_text_or_rodata(phys_addr_t pa)
{
/*
* This is a minimal check to ensure that the r/o page table patching
* API is not being abused to make changes to the kernel text. This
* should ideally cover module and BPF text/rodata as well, but that
* is less straight-forward and hence more costly.
*/
return pa >= __pa_symbol(_stext) && pa < __pa_symbol(__init_begin);
}
pte_t xchg_ro_pte(struct mm_struct *mm, pte_t *ptep, pte_t pte)
{
unsigned long flags;
u64 pte_pa;
pte_t ret;
pte_t *p;
/* can we use __pa() on ptep? */
if (!virt_addr_valid(ptep)) {
/* only linear aliases are remapped r/o anyway */
pte_val(ret) = xchg_relaxed(&pte_val(*ptep), pte_val(pte));
return ret;
}
pte_pa = __pa(ptep);
BUG_ON(in_kernel_text_or_rodata(pte_pa));
raw_spin_lock_irqsave(&patch_pte_lock, flags);
p = (pte_t *)set_fixmap_offset(FIX_TEXT_POKE_PTE, pte_pa);
pte_val(ret) = xchg_relaxed(&pte_val(*p), pte_val(pte));
clear_fixmap(FIX_TEXT_POKE_PTE);
raw_spin_unlock_irqrestore(&patch_pte_lock, flags);
return ret;
}
pte_t cmpxchg_ro_pte(struct mm_struct *mm, pte_t *ptep, pte_t old, pte_t new)
{
unsigned long flags;
u64 pte_pa;
pte_t ret;
pte_t *p;
BUG_ON(!virt_addr_valid(ptep));
pte_pa = __pa(ptep);
BUG_ON(in_kernel_text_or_rodata(pte_pa));
raw_spin_lock_irqsave(&patch_pte_lock, flags);
p = (pte_t *)set_fixmap_offset(FIX_TEXT_POKE_PTE, pte_pa);
pte_val(ret) = cmpxchg_relaxed(&pte_val(*p), pte_val(old), pte_val(new));
clear_fixmap(FIX_TEXT_POKE_PTE);
raw_spin_unlock_irqrestore(&patch_pte_lock, flags);
return ret;
}
static int __init ro_page_tables_init(void)
{
if (ro_page_tables_enabled) {
if (!rodata_full) {
pr_err("Failed to enable R/O page table protection, rodata=full is not enabled\n");
} else {
pr_err("Enabling R/O page table protection\n");
static_branch_enable(&ro_page_tables);
}
}
return 0;
}
early_initcall(ro_page_tables_init);