blob: bbb7551c30f9d295e22225d87416012a74a66494 [file] [log] [blame]
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <asm/cache.h>
#include <asm/pgtable.h>
#include <asm/rodata.h>
#include <asm/sections.h>
#include <asm/tlbflush.h>
#include "mm.h"
static int kernel_set_to_readonly __read_mostly;
static int set_page_attributes(unsigned long virt, int numpages,
pte_t (*f)(pte_t))
{
pmd_t *pmd;
pte_t *pte;
unsigned long start = virt;
unsigned long end = virt + (numpages << PAGE_SHIFT);
unsigned long pmd_end;
while (virt < end) {
pmd = pmd_off_k(virt);
pmd_end = min(ALIGN(virt + 1, PMD_SIZE), end);
if ((pmd_val(*pmd) & PMD_TYPE_MASK) != PMD_TYPE_TABLE) {
pr_err("%s: pmd %p=%08lx for %08lx not page table\n",
__func__, pmd, pmd_val(*pmd), virt);
virt = pmd_end;
continue;
}
while (virt < pmd_end) {
pte = pte_offset_kernel(pmd, virt);
set_pte_ext(pte, f(*pte), 0);
virt += PAGE_SIZE;
}
}
flush_tlb_kernel_range(start, end);
return 0;
}
int set_memory_rw(unsigned long virt, int numpages)
{
return set_page_attributes(virt, numpages, pte_mkwrite);
}
EXPORT_SYMBOL(set_memory_rw);
int set_memory_ro(unsigned long virt, int numpages)
{
return set_page_attributes(virt, numpages, pte_wrprotect);
}
EXPORT_SYMBOL(set_memory_ro);
#ifdef CONFIG_DEBUG_RODATA_TEST
static const int rodata_test_data = 0xC3;
static noinline void rodata_test(void)
{
int re;
pr_info("%s: attempting to write to read-only section:\n", __func__);
if (*(volatile int *)&rodata_test_data != 0xC3) {
pr_err("read only data changed before test\n");
return;
}
asm volatile(
"0: str %[zero], [%[rodata_test_data]]\n"
" mov %[result], #0xFF\n"
" b 2f\n"
"1: mov %[result], #1\n"
"2:\n"
".pushsection __ex_table, \"a\"\n"
" .long 0b, 1b\n"
".popsection\n"
: [re] "=r" (re)
: [rodata_test_data] "r" (&rodata_test_data), [zero] "r" (0)
: "memory"
);
if (re == 1)
pr_info("write to read-only section trapped, success\n");
else
pr_err("write to read-only section NOT trapped, test failed\n");
if (*(volatile int *)&rodata_test_data != 0xC3)
pr_err("read only data changed during write\n");
}
#else
static inline void rodata_test(void) { }
#endif
void set_kernel_text_ro(void)
{
unsigned long start_addr = PAGE_ALIGN((unsigned long)_text);
unsigned long size = PAGE_ALIGN((unsigned long)__end_rodata) - start_addr;
if (!kernel_set_to_readonly)
return;
pr_info_once("Write protecting the kernel text section %lx - %lx\n",
start_addr, start_addr + size);
pr_debug("Set kernel text: %lx - %lx to read only\n",
start_addr, start_addr + size);
set_memory_ro(start_addr, size >> PAGE_SHIFT);
}
void set_kernel_text_rw(void)
{
unsigned long start_addr = PAGE_ALIGN((unsigned long)_text);
unsigned long size = PAGE_ALIGN((unsigned long)__end_rodata) - start_addr;
if (!kernel_set_to_readonly)
return;
pr_debug("Set kernel text: %lx - %lx to read-write\n",
start_addr, start_addr + size);
set_memory_rw(start_addr, size >> PAGE_SHIFT);
}
void mark_rodata_ro(void)
{
kernel_set_to_readonly = 1;
set_kernel_text_ro();
rodata_test();
}