Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | // SPDX-License-Identifier: GPL-2.0 /* * This file contains core tag-based KASAN code. * * Copyright (c) 2018 Google, Inc. * Author: Andrey Konovalov <andreyknvl@google.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/export.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/kasan.h> #include <linux/kernel.h> #include <linux/kmemleak.h> #include <linux/linkage.h> #include <linux/memblock.h> #include <linux/memory.h> #include <linux/mm.h> #include <linux/module.h> #include <linux/printk.h> #include <linux/random.h> #include <linux/sched.h> #include <linux/sched/task_stack.h> #include <linux/slab.h> #include <linux/stacktrace.h> #include <linux/string.h> #include <linux/types.h> #include <linux/vmalloc.h> #include <linux/bug.h> #include "kasan.h" #include "../slab.h" static DEFINE_PER_CPU(u32, prng_state); void kasan_init_tags(void) { int cpu; for_each_possible_cpu(cpu) per_cpu(prng_state, cpu) = (u32)get_cycles(); } /* * If a preemption happens between this_cpu_read and this_cpu_write, the only * side effect is that we'll give a few allocated in different contexts objects * the same tag. Since tag-based KASAN is meant to be used a probabilistic * bug-detection debug feature, this doesn't have significant negative impact. * * Ideally the tags use strong randomness to prevent any attempts to predict * them during explicit exploit attempts. But strong randomness is expensive, * and we did an intentional trade-off to use a PRNG. This non-atomic RMW * sequence has in fact positive effect, since interrupts that randomly skew * PRNG at unpredictable points do only good. */ u8 random_tag(void) { u32 state = this_cpu_read(prng_state); state = 1664525 * state + 1013904223; this_cpu_write(prng_state, state); return (u8)(state % (KASAN_TAG_MAX + 1)); } void *kasan_reset_tag(const void *addr) { return reset_tag(addr); } bool check_memory_region(unsigned long addr, size_t size, bool write, unsigned long ret_ip) { u8 tag; u8 *shadow_first, *shadow_last, *shadow; void *untagged_addr; if (unlikely(size == 0)) return true; tag = get_tag((const void *)addr); /* * Ignore accesses for pointers tagged with 0xff (native kernel * pointer tag) to suppress false positives caused by kmap. * * Some kernel code was written to account for archs that don't keep * high memory mapped all the time, but rather map and unmap particular * pages when needed. Instead of storing a pointer to the kernel memory, * this code saves the address of the page structure and offset within * that page for later use. Those pages are then mapped and unmapped * with kmap/kunmap when necessary and virt_to_page is used to get the * virtual address of the page. For arm64 (that keeps the high memory * mapped all the time), kmap is turned into a page_address call. * The issue is that with use of the page_address + virt_to_page * sequence the top byte value of the original pointer gets lost (gets * set to KASAN_TAG_KERNEL (0xFF)). */ if (tag == KASAN_TAG_KERNEL) return true; untagged_addr = reset_tag((const void *)addr); if (unlikely(untagged_addr < kasan_shadow_to_mem((void *)KASAN_SHADOW_START))) { kasan_report(addr, size, write, ret_ip); return false; } shadow_first = kasan_mem_to_shadow(untagged_addr); shadow_last = kasan_mem_to_shadow(untagged_addr + size - 1); for (shadow = shadow_first; shadow <= shadow_last; shadow++) { if (*shadow != tag) { kasan_report(addr, size, write, ret_ip); return false; } } return true; } #define DEFINE_HWASAN_LOAD_STORE(size) \ void __hwasan_load##size##_noabort(unsigned long addr) \ { \ check_memory_region(addr, size, false, _RET_IP_); \ } \ EXPORT_SYMBOL(__hwasan_load##size##_noabort); \ void __hwasan_store##size##_noabort(unsigned long addr) \ { \ check_memory_region(addr, size, true, _RET_IP_); \ } \ EXPORT_SYMBOL(__hwasan_store##size##_noabort) DEFINE_HWASAN_LOAD_STORE(1); DEFINE_HWASAN_LOAD_STORE(2); DEFINE_HWASAN_LOAD_STORE(4); DEFINE_HWASAN_LOAD_STORE(8); DEFINE_HWASAN_LOAD_STORE(16); void __hwasan_loadN_noabort(unsigned long addr, unsigned long size) { check_memory_region(addr, size, false, _RET_IP_); } EXPORT_SYMBOL(__hwasan_loadN_noabort); void __hwasan_storeN_noabort(unsigned long addr, unsigned long size) { check_memory_region(addr, size, true, _RET_IP_); } EXPORT_SYMBOL(__hwasan_storeN_noabort); void __hwasan_tag_memory(unsigned long addr, u8 tag, unsigned long size) { kasan_poison_shadow((void *)addr, size, tag); } EXPORT_SYMBOL(__hwasan_tag_memory); |