xref: /openbmc/linux/kernel/scs.c (revision 5bbaf9d1fcb9be696ee9a61636ab6803556c70f2)
1d08b9f0cSSami Tolvanen // SPDX-License-Identifier: GPL-2.0
2d08b9f0cSSami Tolvanen /*
3d08b9f0cSSami Tolvanen  * Shadow Call Stack support.
4d08b9f0cSSami Tolvanen  *
5d08b9f0cSSami Tolvanen  * Copyright (C) 2019 Google LLC
6d08b9f0cSSami Tolvanen  */
7d08b9f0cSSami Tolvanen 
8d08b9f0cSSami Tolvanen #include <linux/kasan.h>
9628d06a4SSami Tolvanen #include <linux/mm.h>
10d08b9f0cSSami Tolvanen #include <linux/scs.h>
11d08b9f0cSSami Tolvanen #include <linux/slab.h>
12628d06a4SSami Tolvanen #include <linux/vmstat.h>
13d08b9f0cSSami Tolvanen #include <asm/scs.h>
14d08b9f0cSSami Tolvanen 
15d08b9f0cSSami Tolvanen static struct kmem_cache *scs_cache;
16d08b9f0cSSami Tolvanen 
17d08b9f0cSSami Tolvanen static void *scs_alloc(int node)
18d08b9f0cSSami Tolvanen {
19d08b9f0cSSami Tolvanen 	void *s;
20d08b9f0cSSami Tolvanen 
21d08b9f0cSSami Tolvanen 	s = kmem_cache_alloc_node(scs_cache, GFP_SCS, node);
22d08b9f0cSSami Tolvanen 	if (s) {
23d08b9f0cSSami Tolvanen 		*__scs_magic(s) = SCS_END_MAGIC;
24d08b9f0cSSami Tolvanen 		/*
25d08b9f0cSSami Tolvanen 		 * Poison the allocation to catch unintentional accesses to
26d08b9f0cSSami Tolvanen 		 * the shadow stack when KASAN is enabled.
27d08b9f0cSSami Tolvanen 		 */
28d08b9f0cSSami Tolvanen 		kasan_poison_object_data(scs_cache, s);
29d08b9f0cSSami Tolvanen 	}
30d08b9f0cSSami Tolvanen 
31d08b9f0cSSami Tolvanen 	return s;
32d08b9f0cSSami Tolvanen }
33d08b9f0cSSami Tolvanen 
34d08b9f0cSSami Tolvanen static void scs_free(void *s)
35d08b9f0cSSami Tolvanen {
36d08b9f0cSSami Tolvanen 	kasan_unpoison_object_data(scs_cache, s);
37d08b9f0cSSami Tolvanen 	kmem_cache_free(scs_cache, s);
38d08b9f0cSSami Tolvanen }
39d08b9f0cSSami Tolvanen 
40d08b9f0cSSami Tolvanen void __init scs_init(void)
41d08b9f0cSSami Tolvanen {
42d08b9f0cSSami Tolvanen 	scs_cache = kmem_cache_create("scs_cache", SCS_SIZE, 0, 0, NULL);
43d08b9f0cSSami Tolvanen }
44d08b9f0cSSami Tolvanen 
45628d06a4SSami Tolvanen static struct page *__scs_page(struct task_struct *tsk)
46628d06a4SSami Tolvanen {
47628d06a4SSami Tolvanen 	return virt_to_page(task_scs(tsk));
48628d06a4SSami Tolvanen }
49628d06a4SSami Tolvanen 
50628d06a4SSami Tolvanen static void scs_account(struct task_struct *tsk, int account)
51628d06a4SSami Tolvanen {
52628d06a4SSami Tolvanen 	mod_zone_page_state(page_zone(__scs_page(tsk)), NR_KERNEL_SCS_KB,
53628d06a4SSami Tolvanen 		account * (SCS_SIZE / 1024));
54628d06a4SSami Tolvanen }
55628d06a4SSami Tolvanen 
56d08b9f0cSSami Tolvanen int scs_prepare(struct task_struct *tsk, int node)
57d08b9f0cSSami Tolvanen {
58d08b9f0cSSami Tolvanen 	void *s = scs_alloc(node);
59d08b9f0cSSami Tolvanen 
60d08b9f0cSSami Tolvanen 	if (!s)
61d08b9f0cSSami Tolvanen 		return -ENOMEM;
62d08b9f0cSSami Tolvanen 
63d08b9f0cSSami Tolvanen 	task_scs(tsk) = s;
64d08b9f0cSSami Tolvanen 	task_scs_offset(tsk) = 0;
65628d06a4SSami Tolvanen 	scs_account(tsk, 1);
66d08b9f0cSSami Tolvanen 	return 0;
67d08b9f0cSSami Tolvanen }
68d08b9f0cSSami Tolvanen 
69*5bbaf9d1SSami Tolvanen static void scs_check_usage(struct task_struct *tsk)
70*5bbaf9d1SSami Tolvanen {
71*5bbaf9d1SSami Tolvanen 	static unsigned long highest;
72*5bbaf9d1SSami Tolvanen 
73*5bbaf9d1SSami Tolvanen 	unsigned long *p, prev, curr = highest, used = 0;
74*5bbaf9d1SSami Tolvanen 
75*5bbaf9d1SSami Tolvanen 	if (!IS_ENABLED(CONFIG_DEBUG_STACK_USAGE))
76*5bbaf9d1SSami Tolvanen 		return;
77*5bbaf9d1SSami Tolvanen 
78*5bbaf9d1SSami Tolvanen 	for (p = task_scs(tsk); p < __scs_magic(tsk); ++p) {
79*5bbaf9d1SSami Tolvanen 		if (!READ_ONCE_NOCHECK(*p))
80*5bbaf9d1SSami Tolvanen 			break;
81*5bbaf9d1SSami Tolvanen 		used++;
82*5bbaf9d1SSami Tolvanen 	}
83*5bbaf9d1SSami Tolvanen 
84*5bbaf9d1SSami Tolvanen 	while (used > curr) {
85*5bbaf9d1SSami Tolvanen 		prev = cmpxchg_relaxed(&highest, curr, used);
86*5bbaf9d1SSami Tolvanen 
87*5bbaf9d1SSami Tolvanen 		if (prev == curr) {
88*5bbaf9d1SSami Tolvanen 			pr_info("%s (%d): highest shadow stack usage: %lu bytes\n",
89*5bbaf9d1SSami Tolvanen 				tsk->comm, task_pid_nr(tsk), used);
90*5bbaf9d1SSami Tolvanen 			break;
91*5bbaf9d1SSami Tolvanen 		}
92*5bbaf9d1SSami Tolvanen 
93*5bbaf9d1SSami Tolvanen 		curr = prev;
94*5bbaf9d1SSami Tolvanen 	}
95*5bbaf9d1SSami Tolvanen }
96*5bbaf9d1SSami Tolvanen 
97d08b9f0cSSami Tolvanen void scs_release(struct task_struct *tsk)
98d08b9f0cSSami Tolvanen {
99d08b9f0cSSami Tolvanen 	void *s = task_scs(tsk);
100d08b9f0cSSami Tolvanen 
101d08b9f0cSSami Tolvanen 	if (!s)
102d08b9f0cSSami Tolvanen 		return;
103d08b9f0cSSami Tolvanen 
104d08b9f0cSSami Tolvanen 	WARN(scs_corrupted(tsk), "corrupted shadow stack detected when freeing task\n");
105*5bbaf9d1SSami Tolvanen 	scs_check_usage(tsk);
106628d06a4SSami Tolvanen 	scs_account(tsk, -1);
107d08b9f0cSSami Tolvanen 	scs_free(s);
108d08b9f0cSSami Tolvanen }
109