/* Copyright (c) 2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #ifdef CONFIG_MSM_KERNEL_PROTECT_TEST /* * We're going to crash the system so we need to make sure debug messages * in the main msm_protect_kernel initcall make it to the serial console. */ #undef pr_debug #define pr_debug pr_err /* tests protection by trying to hijack __alloc_pages_nodemask */ static void msm_protect_kernel_test(void) { /* * There's nothing special about __alloc_pages_nodemask, we just * need something that lives in the regulator (non-init) kernel * text section that we know will never be compiled out. */ char *addr = (char *)__alloc_pages_nodemask; pr_err("Checking whether the kernel text is writable...\n"); pr_err("A BUG means it is writable (this is bad)\n"); pr_err("A stage-2 fault means it's not writable (this is good, but we'll still crash)\n"); /* * We can't simply do a `*addr = 0' since the kernel text might be * read-only in stage-1. We have to ensure the address is writable * in stage-1 first, otherwise we'll just get a stage-1 fault and * we'll never know if our stage-2 protection is actually working. */ if (set_memory_rw(round_down((u64)addr, PAGE_SIZE), 1)) { pr_err("Couldn't set memory as RW. Can't perform check!\n"); return; } pr_err("Writing now...\n"); *addr = 0; pr_err("If we're still alive right now then kernel protection did NOT work.\n"); BUG(); } #else static void msm_protect_kernel_test(void) { } #endif static int __init msm_protect_kernel(void) { int ret; u32 vmid_hlos = VMID_HLOS; int dest_perms = PERM_READ | PERM_EXEC; /* * Although the kernel image is mapped with section mappings, the * start and end of the .text segment are on a PAGE_SIZE * boundaries. */ phys_addr_t kernel_x_start_rounded = round_down(__pa(_stext), PAGE_SIZE); phys_addr_t kernel_x_end = round_up(__pa(_etext), PAGE_SIZE); void *virt_start = phys_to_virt(kernel_x_start_rounded); void *virt_end = phys_to_virt(kernel_x_end); pr_debug("assigning from phys: %pa to %pa\n", &kernel_x_start_rounded, &kernel_x_end); pr_debug("virtual: %p to %p\n", virt_start, virt_end); ret = hyp_assign_phys(kernel_x_start_rounded, kernel_x_end - kernel_x_start_rounded, &vmid_hlos, 1, &vmid_hlos, &dest_perms, 1); if (ret) /* * We want to fail relatively silently since not all * platforms support the hyp_assign_phys call. */ pr_debug("Couldn't protect the kernel region: %d\n", ret); msm_protect_kernel_test(); return ret; } /* * The assign call only works if it happens before we go into SMP mode. It * needs to be an early_initcall so that it happens before we bring the * other cores out of reset. */ early_initcall(msm_protect_kernel);