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 63*51189c7aSWill Deacon task_scs(tsk) = task_scs_sp(tsk) = s; 64628d06a4SSami Tolvanen scs_account(tsk, 1); 65d08b9f0cSSami Tolvanen return 0; 66d08b9f0cSSami Tolvanen } 67d08b9f0cSSami Tolvanen 685bbaf9d1SSami Tolvanen static void scs_check_usage(struct task_struct *tsk) 695bbaf9d1SSami Tolvanen { 705bbaf9d1SSami Tolvanen static unsigned long highest; 715bbaf9d1SSami Tolvanen 725bbaf9d1SSami Tolvanen unsigned long *p, prev, curr = highest, used = 0; 735bbaf9d1SSami Tolvanen 745bbaf9d1SSami Tolvanen if (!IS_ENABLED(CONFIG_DEBUG_STACK_USAGE)) 755bbaf9d1SSami Tolvanen return; 765bbaf9d1SSami Tolvanen 775bbaf9d1SSami Tolvanen for (p = task_scs(tsk); p < __scs_magic(tsk); ++p) { 785bbaf9d1SSami Tolvanen if (!READ_ONCE_NOCHECK(*p)) 795bbaf9d1SSami Tolvanen break; 805bbaf9d1SSami Tolvanen used++; 815bbaf9d1SSami Tolvanen } 825bbaf9d1SSami Tolvanen 835bbaf9d1SSami Tolvanen while (used > curr) { 845bbaf9d1SSami Tolvanen prev = cmpxchg_relaxed(&highest, curr, used); 855bbaf9d1SSami Tolvanen 865bbaf9d1SSami Tolvanen if (prev == curr) { 875bbaf9d1SSami Tolvanen pr_info("%s (%d): highest shadow stack usage: %lu bytes\n", 885bbaf9d1SSami Tolvanen tsk->comm, task_pid_nr(tsk), used); 895bbaf9d1SSami Tolvanen break; 905bbaf9d1SSami Tolvanen } 915bbaf9d1SSami Tolvanen 925bbaf9d1SSami Tolvanen curr = prev; 935bbaf9d1SSami Tolvanen } 945bbaf9d1SSami Tolvanen } 955bbaf9d1SSami Tolvanen 96d08b9f0cSSami Tolvanen void scs_release(struct task_struct *tsk) 97d08b9f0cSSami Tolvanen { 98d08b9f0cSSami Tolvanen void *s = task_scs(tsk); 99d08b9f0cSSami Tolvanen 100d08b9f0cSSami Tolvanen if (!s) 101d08b9f0cSSami Tolvanen return; 102d08b9f0cSSami Tolvanen 103d08b9f0cSSami Tolvanen WARN(scs_corrupted(tsk), "corrupted shadow stack detected when freeing task\n"); 1045bbaf9d1SSami Tolvanen scs_check_usage(tsk); 105628d06a4SSami Tolvanen scs_account(tsk, -1); 106d08b9f0cSSami Tolvanen scs_free(s); 107d08b9f0cSSami Tolvanen } 108