diff options
author | Mitchel Humpherys <mitchelh@codeaurora.org> | 2015-10-12 17:52:23 -0700 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-23 20:57:44 -0700 |
commit | af7618f309ad963edc3e87e4ca308e585ed79e18 (patch) | |
tree | fb47a5d053f3157939d9e85722cd1c94da376010 /drivers/soc | |
parent | 6bc3fcc1f0f2ce988e589f1b912a8cdb686d54f8 (diff) |
soc: qcom: Add kernel_protect library and initcall
It's good security practice to make your executable code read-only. On
hypervisor-enabled targets, this can be trivially accomplished by
removing the writable attribute from all stage-2 mappings of the kernel
text. Add a small library and initcall to do this.
Due to constraints on the hypervisor, this needs to happen before all of
the cores are brought out of reset, so make it an early_initcall.
Change-Id: I2d3ee4ad69402d98f0f6a9078c58e66cd227d222
Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
Diffstat (limited to 'drivers/soc')
-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); |