]>
Commit | Line | Data |
---|---|---|
d08b9f0c ST |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Shadow Call Stack support. | |
4 | * | |
5 | * Copyright (C) 2019 Google LLC | |
6 | */ | |
7 | ||
a2abe7cb | 8 | #include <linux/cpuhotplug.h> |
d08b9f0c | 9 | #include <linux/kasan.h> |
628d06a4 | 10 | #include <linux/mm.h> |
d08b9f0c | 11 | #include <linux/scs.h> |
a2abe7cb | 12 | #include <linux/vmalloc.h> |
628d06a4 | 13 | #include <linux/vmstat.h> |
d08b9f0c | 14 | |
bee348fa WD |
15 | static void __scs_account(void *s, int account) |
16 | { | |
a2abe7cb | 17 | struct page *scs_page = vmalloc_to_page(s); |
bee348fa | 18 | |
991e7673 | 19 | mod_node_page_state(page_pgdat(scs_page), NR_KERNEL_SCS_KB, |
bee348fa WD |
20 | account * (SCS_SIZE / SZ_1K)); |
21 | } | |
22 | ||
a2abe7cb ST |
23 | /* Matches NR_CACHED_STACKS for VMAP_STACK */ |
24 | #define NR_CACHED_SCS 2 | |
25 | static DEFINE_PER_CPU(void *, scs_cache[NR_CACHED_SCS]); | |
26 | ||
27 | static void *__scs_alloc(int node) | |
d08b9f0c | 28 | { |
a2abe7cb ST |
29 | int i; |
30 | void *s; | |
31 | ||
32 | for (i = 0; i < NR_CACHED_SCS; i++) { | |
33 | s = this_cpu_xchg(scs_cache[i], NULL); | |
34 | if (s) { | |
f6e39794 AK |
35 | s = kasan_unpoison_vmalloc(s, SCS_SIZE, |
36 | KASAN_VMALLOC_PROT_NORMAL); | |
a2abe7cb | 37 | memset(s, 0, SCS_SIZE); |
f6e39794 | 38 | goto out; |
a2abe7cb ST |
39 | } |
40 | } | |
41 | ||
f6e39794 | 42 | s = __vmalloc_node_range(SCS_SIZE, 1, VMALLOC_START, VMALLOC_END, |
a2abe7cb ST |
43 | GFP_SCS, PAGE_KERNEL, 0, node, |
44 | __builtin_return_address(0)); | |
f6e39794 AK |
45 | |
46 | out: | |
47 | return kasan_reset_tag(s); | |
a2abe7cb | 48 | } |
bee348fa | 49 | |
a2abe7cb ST |
50 | void *scs_alloc(int node) |
51 | { | |
52 | void *s; | |
53 | ||
54 | s = __scs_alloc(node); | |
bee348fa WD |
55 | if (!s) |
56 | return NULL; | |
d08b9f0c | 57 | |
bee348fa WD |
58 | *__scs_magic(s) = SCS_END_MAGIC; |
59 | ||
60 | /* | |
61 | * Poison the allocation to catch unintentional accesses to | |
62 | * the shadow stack when KASAN is enabled. | |
63 | */ | |
a2abe7cb | 64 | kasan_poison_vmalloc(s, SCS_SIZE); |
bee348fa | 65 | __scs_account(s, 1); |
d08b9f0c ST |
66 | return s; |
67 | } | |
68 | ||
a2abe7cb | 69 | void scs_free(void *s) |
d08b9f0c | 70 | { |
a2abe7cb ST |
71 | int i; |
72 | ||
bee348fa | 73 | __scs_account(s, -1); |
a2abe7cb ST |
74 | |
75 | /* | |
76 | * We cannot sleep as this can be called in interrupt context, | |
77 | * so use this_cpu_cmpxchg to update the cache, and vfree_atomic | |
78 | * to free the stack. | |
79 | */ | |
80 | ||
81 | for (i = 0; i < NR_CACHED_SCS; i++) | |
82 | if (this_cpu_cmpxchg(scs_cache[i], 0, s) == NULL) | |
83 | return; | |
84 | ||
f6e39794 | 85 | kasan_unpoison_vmalloc(s, SCS_SIZE, KASAN_VMALLOC_PROT_NORMAL); |
a2abe7cb ST |
86 | vfree_atomic(s); |
87 | } | |
88 | ||
89 | static int scs_cleanup(unsigned int cpu) | |
90 | { | |
91 | int i; | |
92 | void **cache = per_cpu_ptr(scs_cache, cpu); | |
93 | ||
94 | for (i = 0; i < NR_CACHED_SCS; i++) { | |
95 | vfree(cache[i]); | |
96 | cache[i] = NULL; | |
97 | } | |
98 | ||
99 | return 0; | |
d08b9f0c ST |
100 | } |
101 | ||
102 | void __init scs_init(void) | |
103 | { | |
a2abe7cb ST |
104 | cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "scs:scs_cache", NULL, |
105 | scs_cleanup); | |
d08b9f0c ST |
106 | } |
107 | ||
108 | int scs_prepare(struct task_struct *tsk, int node) | |
109 | { | |
110 | void *s = scs_alloc(node); | |
111 | ||
112 | if (!s) | |
113 | return -ENOMEM; | |
114 | ||
51189c7a | 115 | task_scs(tsk) = task_scs_sp(tsk) = s; |
d08b9f0c ST |
116 | return 0; |
117 | } | |
118 | ||
5bbaf9d1 ST |
119 | static void scs_check_usage(struct task_struct *tsk) |
120 | { | |
121 | static unsigned long highest; | |
122 | ||
123 | unsigned long *p, prev, curr = highest, used = 0; | |
124 | ||
125 | if (!IS_ENABLED(CONFIG_DEBUG_STACK_USAGE)) | |
126 | return; | |
127 | ||
128 | for (p = task_scs(tsk); p < __scs_magic(tsk); ++p) { | |
129 | if (!READ_ONCE_NOCHECK(*p)) | |
130 | break; | |
333ed746 | 131 | used += sizeof(*p); |
5bbaf9d1 ST |
132 | } |
133 | ||
134 | while (used > curr) { | |
135 | prev = cmpxchg_relaxed(&highest, curr, used); | |
136 | ||
137 | if (prev == curr) { | |
138 | pr_info("%s (%d): highest shadow stack usage: %lu bytes\n", | |
139 | tsk->comm, task_pid_nr(tsk), used); | |
140 | break; | |
141 | } | |
142 | ||
143 | curr = prev; | |
144 | } | |
145 | } | |
146 | ||
d08b9f0c ST |
147 | void scs_release(struct task_struct *tsk) |
148 | { | |
149 | void *s = task_scs(tsk); | |
150 | ||
151 | if (!s) | |
152 | return; | |
153 | ||
88485be5 WD |
154 | WARN(task_scs_end_corrupted(tsk), |
155 | "corrupted shadow stack detected when freeing task\n"); | |
5bbaf9d1 | 156 | scs_check_usage(tsk); |
d08b9f0c ST |
157 | scs_free(s); |
158 | } |