summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/soc/qcom/Kconfig22
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/kernel_protect.c107
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);