/* * linux/arch/arm/mm/rodata.c * * Copyright (C) 2011 Google, Inc. * * Author: Colin Cross <ccross@android.com> * * Based on x86 implementation in arch/x86/mm/init_32.c * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #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; #ifdef CONFIG_DEBUG_RODATA_TEST static const int rodata_test_data = 0xC3; static noinline void rodata_test(void) { int result; 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; } /* * Attempt to to write to rodata_test_data, trapping the expected * data abort. If the trap executed, result will be 1. If it didn't, * result will be 0xFF. */ asm volatile( "0: str %[zero], [%[rodata_test_data]]\n" " mov %[result], #0xFF\n" " b 2f\n" "1: mov %[result], #1\n" "2:\n" /* Exception fixup - if store at label 0 faults, jumps to 1 */ ".pushsection __ex_table, \"a\"\n" " .long 0b, 1b\n" ".popsection\n" : [result] "=r" (result) : [rodata_test_data] "r" (&rodata_test_data), [zero] "r" (0) : "memory" ); if (result == 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 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_ro(unsigned long virt, int numpages) { return set_page_attributes(virt, numpages, pte_wrprotect); } EXPORT_SYMBOL(set_memory_ro); int set_memory_rw(unsigned long virt, int numpages) { return set_page_attributes(virt, numpages, pte_mkwrite); } EXPORT_SYMBOL(set_memory_rw); void set_kernel_text_rw(void) { unsigned long start = PAGE_ALIGN((unsigned long)_text); unsigned long size = PAGE_ALIGN((unsigned long)__end_rodata) - start; if (!kernel_set_to_readonly) return; pr_debug("Set kernel text: %lx - %lx to read-write\n", start, start + size); set_memory_rw(start, size >> PAGE_SHIFT); } void set_kernel_text_ro(void) { unsigned long start = PAGE_ALIGN((unsigned long)_text); unsigned long size = PAGE_ALIGN((unsigned long)__end_rodata) - start; if (!kernel_set_to_readonly) return; pr_info_once("Write protecting the kernel text section %lx - %lx\n", start, start + size); pr_debug("Set kernel text: %lx - %lx to read only\n", start, start + size); set_memory_ro(start, size >> PAGE_SHIFT); } void mark_rodata_ro(void) { kernel_set_to_readonly = 1; set_kernel_text_ro(); rodata_test(); }