summaryrefslogtreecommitdiff
path: root/drivers/soc/qcom/kernel_protect.c
blob: 7319297185c50c7c1e36e2f5a50b3810e03ecddb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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);