scs: Add page accounting for shadow call stack allocations
[linux-block.git] / kernel / scs.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Shadow Call Stack support.
4  *
5  * Copyright (C) 2019 Google LLC
6  */
7
8 #include <linux/kasan.h>
9 #include <linux/mm.h>
10 #include <linux/scs.h>
11 #include <linux/slab.h>
12 #include <linux/vmstat.h>
13 #include <asm/scs.h>
14
15 static struct kmem_cache *scs_cache;
16
17 static void *scs_alloc(int node)
18 {
19         void *s;
20
21         s = kmem_cache_alloc_node(scs_cache, GFP_SCS, node);
22         if (s) {
23                 *__scs_magic(s) = SCS_END_MAGIC;
24                 /*
25                  * Poison the allocation to catch unintentional accesses to
26                  * the shadow stack when KASAN is enabled.
27                  */
28                 kasan_poison_object_data(scs_cache, s);
29         }
30
31         return s;
32 }
33
34 static void scs_free(void *s)
35 {
36         kasan_unpoison_object_data(scs_cache, s);
37         kmem_cache_free(scs_cache, s);
38 }
39
40 void __init scs_init(void)
41 {
42         scs_cache = kmem_cache_create("scs_cache", SCS_SIZE, 0, 0, NULL);
43 }
44
45 static struct page *__scs_page(struct task_struct *tsk)
46 {
47         return virt_to_page(task_scs(tsk));
48 }
49
50 static void scs_account(struct task_struct *tsk, int account)
51 {
52         mod_zone_page_state(page_zone(__scs_page(tsk)), NR_KERNEL_SCS_KB,
53                 account * (SCS_SIZE / 1024));
54 }
55
56 int scs_prepare(struct task_struct *tsk, int node)
57 {
58         void *s = scs_alloc(node);
59
60         if (!s)
61                 return -ENOMEM;
62
63         task_scs(tsk) = s;
64         task_scs_offset(tsk) = 0;
65         scs_account(tsk, 1);
66
67         return 0;
68 }
69
70 void scs_release(struct task_struct *tsk)
71 {
72         void *s = task_scs(tsk);
73
74         if (!s)
75                 return;
76
77         WARN(scs_corrupted(tsk), "corrupted shadow stack detected when freeing task\n");
78         scs_account(tsk, -1);
79         scs_free(s);
80 }