]>
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) { | |
35 | kasan_unpoison_vmalloc(s, SCS_SIZE); | |
36 | memset(s, 0, SCS_SIZE); | |
37 | return s; | |
38 | } | |
39 | } | |
40 | ||
41 | return __vmalloc_node_range(SCS_SIZE, 1, VMALLOC_START, VMALLOC_END, | |
42 | GFP_SCS, PAGE_KERNEL, 0, node, | |
43 | __builtin_return_address(0)); | |
44 | } | |
bee348fa | 45 | |
a2abe7cb ST |
46 | void *scs_alloc(int node) |
47 | { | |
48 | void *s; | |
49 | ||
50 | s = __scs_alloc(node); | |
bee348fa WD |
51 | if (!s) |
52 | return NULL; | |
d08b9f0c | 53 | |
bee348fa WD |
54 | *__scs_magic(s) = SCS_END_MAGIC; |
55 | ||
56 | /* | |
57 | * Poison the allocation to catch unintentional accesses to | |
58 | * the shadow stack when KASAN is enabled. | |
59 | */ | |
a2abe7cb | 60 | kasan_poison_vmalloc(s, SCS_SIZE); |
bee348fa | 61 | __scs_account(s, 1); |
d08b9f0c ST |
62 | return s; |
63 | } | |
64 | ||
a2abe7cb | 65 | void scs_free(void *s) |
d08b9f0c | 66 | { |
a2abe7cb ST |
67 | int i; |
68 | ||
bee348fa | 69 | __scs_account(s, -1); |
a2abe7cb ST |
70 | |
71 | /* | |
72 | * We cannot sleep as this can be called in interrupt context, | |
73 | * so use this_cpu_cmpxchg to update the cache, and vfree_atomic | |
74 | * to free the stack. | |
75 | */ | |
76 | ||
77 | for (i = 0; i < NR_CACHED_SCS; i++) | |
78 | if (this_cpu_cmpxchg(scs_cache[i], 0, s) == NULL) | |
79 | return; | |
80 | ||
528a4ab4 | 81 | kasan_unpoison_vmalloc(s, SCS_SIZE); |
a2abe7cb ST |
82 | vfree_atomic(s); |
83 | } | |
84 | ||
85 | static int scs_cleanup(unsigned int cpu) | |
86 | { | |
87 | int i; | |
88 | void **cache = per_cpu_ptr(scs_cache, cpu); | |
89 | ||
90 | for (i = 0; i < NR_CACHED_SCS; i++) { | |
91 | vfree(cache[i]); | |
92 | cache[i] = NULL; | |
93 | } | |
94 | ||
95 | return 0; | |
d08b9f0c ST |
96 | } |
97 | ||
98 | void __init scs_init(void) | |
99 | { | |
a2abe7cb ST |
100 | cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "scs:scs_cache", NULL, |
101 | scs_cleanup); | |
d08b9f0c ST |
102 | } |
103 | ||
104 | int scs_prepare(struct task_struct *tsk, int node) | |
105 | { | |
106 | void *s = scs_alloc(node); | |
107 | ||
108 | if (!s) | |
109 | return -ENOMEM; | |
110 | ||
51189c7a | 111 | task_scs(tsk) = task_scs_sp(tsk) = s; |
d08b9f0c ST |
112 | return 0; |
113 | } | |
114 | ||
5bbaf9d1 ST |
115 | static void scs_check_usage(struct task_struct *tsk) |
116 | { | |
117 | static unsigned long highest; | |
118 | ||
119 | unsigned long *p, prev, curr = highest, used = 0; | |
120 | ||
121 | if (!IS_ENABLED(CONFIG_DEBUG_STACK_USAGE)) | |
122 | return; | |
123 | ||
124 | for (p = task_scs(tsk); p < __scs_magic(tsk); ++p) { | |
125 | if (!READ_ONCE_NOCHECK(*p)) | |
126 | break; | |
333ed746 | 127 | used += sizeof(*p); |
5bbaf9d1 ST |
128 | } |
129 | ||
130 | while (used > curr) { | |
131 | prev = cmpxchg_relaxed(&highest, curr, used); | |
132 | ||
133 | if (prev == curr) { | |
134 | pr_info("%s (%d): highest shadow stack usage: %lu bytes\n", | |
135 | tsk->comm, task_pid_nr(tsk), used); | |
136 | break; | |
137 | } | |
138 | ||
139 | curr = prev; | |
140 | } | |
141 | } | |
142 | ||
d08b9f0c ST |
143 | void scs_release(struct task_struct *tsk) |
144 | { | |
145 | void *s = task_scs(tsk); | |
146 | ||
147 | if (!s) | |
148 | return; | |
149 | ||
88485be5 WD |
150 | WARN(task_scs_end_corrupted(tsk), |
151 | "corrupted shadow stack detected when freeing task\n"); | |
5bbaf9d1 | 152 | scs_check_usage(tsk); |
d08b9f0c ST |
153 | scs_free(s); |
154 | } |