diff options
-rw-r--r-- | drivers/soc/qcom/Kconfig | 22 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/kernel_protect.c | 107 |
3 files changed, 130 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index eb1ced78d8a8..1ce67325188b 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -521,4 +521,26 @@ config MSM_AVTIMER This driver gets the Q6 out of power collapsed state and exposes ioctl control to read avtimer tick. +config MSM_KERNEL_PROTECT + bool "Protect kernel text by removing write permissions in stage-2" + depends on !FUNCTION_TRACER + help + On hypervisor-enabled targets, this option will make a call into + the hypervisor to request that the kernel text be remapped + without write permissions. This protects against malicious + devices rewriting kernel code. + + Note that this will BREAK any runtime patching of the kernel text + (i.e. anything that uses apply_alternatives, + aarch64_insn_patch_text_nosync, etc. including the various CPU + errata workarounds in arch/arm64/kernel/cpu_errata.c). + +config MSM_KERNEL_PROTECT_TEST + bool "Bootup test of kernel protection (INTENTIONAL CRASH)" + depends on MSM_KERNEL_PROTECT + help + Attempts to write to the kernel text after making the kernel text + read-only. This test is FATAL whether it passes or fails! + Success is signaled by a stage-2 fault. + source "drivers/soc/qcom/memshare/Kconfig" diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index ebed5302bdd3..97e823d53244 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -68,3 +68,4 @@ obj-$(CONFIG_MSM_RUN_QUEUE_STATS) += msm_rq_stats.o obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o obj-$(CONFIG_MSM_AVTIMER) += avtimer.o obj-$(CONFIG_HW_PERF_EVENTS) += perf_event_kryo.o +obj-$(CONFIG_MSM_KERNEL_PROTECT) += kernel_protect.o diff --git a/drivers/soc/qcom/kernel_protect.c b/drivers/soc/qcom/kernel_protect.c new file mode 100644 index 000000000000..7319297185c5 --- /dev/null +++ b/drivers/soc/qcom/kernel_protect.c @@ -0,0 +1,107 @@ +/* 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 <linux/printk.h> +#include <linux/init.h> +#include <linux/gfp.h> +#include <soc/qcom/secure_buffer.h> +#include <asm/sections.h> +#include <asm/cacheflush.h> + + +#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); |