summaryrefslogtreecommitdiff
path: root/src/lib/asan.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/asan.c')
-rw-r--r--src/lib/asan.c412
1 files changed, 394 insertions, 18 deletions
diff --git a/src/lib/asan.c b/src/lib/asan.c
index e4a1012e13..0c186d62a3 100644
--- a/src/lib/asan.c
+++ b/src/lib/asan.c
@@ -1,16 +1,337 @@
-#include <stddef.h>
+/* SPDX-License-Identifier: GPL-2.0 */
-#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+/*
+ * Address sanitizer support.
+ *
+ * Parts of this file are based on mm/kasan
+ * from the Linux kernel 4.19.137.
+ *
+ */
-#define DEFINE_ASAN_LOAD_STORE(size) \
- void __asan_load##size(unsigned long addr) \
- {} \
- void __asan_load##size##_noabort(unsigned long addr) \
- {} \
- void __asan_store##size(unsigned long addr) \
- {} \
- void __asan_store##size##_noabort(unsigned long addr) \
- {}
+#include <symbols.h>
+#include <assert.h>
+#include <asan.h>
+
+static inline void *asan_mem_to_shadow(const void *addr)
+{
+ return (void *)((uintptr_t)&_asan_shadow + (((uintptr_t)addr -
+ (uintptr_t)&_data) >> ASAN_SHADOW_SCALE_SHIFT));
+}
+
+static inline const void *asan_shadow_to_mem(const void *shadow_addr)
+{
+ return (void *)((uintptr_t)&_data + (((uintptr_t)shadow_addr -
+ (uintptr_t)&_asan_shadow) << ASAN_SHADOW_SCALE_SHIFT));
+}
+
+static void asan_poison_shadow(const void *address, size_t size, u8 value)
+{
+ void *shadow_start, *shadow_end;
+
+ shadow_start = asan_mem_to_shadow(address);
+ shadow_end = asan_mem_to_shadow(address + size);
+
+ __builtin_memset(shadow_start, value, shadow_end - shadow_start);
+}
+
+void asan_unpoison_shadow(const void *address, size_t size)
+{
+ asan_poison_shadow(address, size, 0);
+
+ if (size & ASAN_SHADOW_MASK) {
+ u8 *shadow = (u8 *)asan_mem_to_shadow(address + size);
+ *shadow = size & ASAN_SHADOW_MASK;
+ }
+}
+
+static __always_inline bool memory_is_poisoned_1(unsigned long addr)
+{
+ s8 shadow_value = *(s8 *)asan_mem_to_shadow((void *)addr);
+
+ if (unlikely(shadow_value)) {
+ s8 last_accessible_byte = addr & ASAN_SHADOW_MASK;
+ return unlikely(last_accessible_byte >= shadow_value);
+ }
+
+ return false;
+}
+
+static __always_inline bool memory_is_poisoned_2_4_8(unsigned long addr,
+ unsigned long size)
+{
+ u8 *shadow_addr = (u8 *)asan_mem_to_shadow((void *)addr);
+
+ if (unlikely(((addr + size - 1) & ASAN_SHADOW_MASK) < size - 1))
+ return *shadow_addr || memory_is_poisoned_1(addr + size - 1);
+
+ return memory_is_poisoned_1(addr + size - 1);
+}
+
+static __always_inline bool memory_is_poisoned_16(unsigned long addr)
+{
+ u16 *shadow_addr = (u16 *)asan_mem_to_shadow((void *)addr);
+
+ if (unlikely(!IS_ALIGNED(addr, ASAN_SHADOW_SCALE_SIZE)))
+ return *shadow_addr || memory_is_poisoned_1(addr + 15);
+
+ return *shadow_addr;
+}
+
+static __always_inline unsigned long bytes_is_nonzero(const u8 *start,
+ size_t size)
+{
+ while (size) {
+ if (unlikely(*start))
+ return (unsigned long)start;
+ start++;
+ size--;
+ }
+
+ return 0;
+}
+
+static __always_inline unsigned long memory_is_nonzero(const void *start,
+ const void *end)
+{
+ unsigned int words;
+ unsigned long ret;
+ unsigned int prefix = (unsigned long)start % 8;
+
+ if (end - start <= 16)
+ return bytes_is_nonzero(start, end - start);
+
+ if (prefix) {
+ prefix = 8 - prefix;
+ ret = bytes_is_nonzero(start, prefix);
+ if (unlikely(ret))
+ return ret;
+ start += prefix;
+ }
+
+ words = (end - start) / 8;
+ while (words) {
+ if (unlikely(*(u64 *)start))
+ return bytes_is_nonzero(start, 8);
+ start += 8;
+ words--;
+ }
+
+ return bytes_is_nonzero(start, (end - start) % 8);
+}
+
+static __always_inline bool memory_is_poisoned_n(unsigned long addr,
+ size_t size)
+{
+ unsigned long ret;
+
+ ret = memory_is_nonzero(asan_mem_to_shadow((void *)addr),
+ asan_mem_to_shadow((void *)addr + size - 1) + 1);
+
+ if (unlikely(ret)) {
+ unsigned long last_byte = addr + size - 1;
+ s8 *last_shadow = (s8 *)asan_mem_to_shadow((void *)last_byte);
+
+ if (unlikely(ret != (unsigned long)last_shadow ||
+ ((long)(last_byte & ASAN_SHADOW_MASK) >= *last_shadow)))
+ return true;
+ }
+ return false;
+}
+
+static __always_inline bool memory_is_poisoned(unsigned long addr, size_t size)
+{
+ if (__builtin_constant_p(size)) {
+ switch (size) {
+ case 1:
+ return memory_is_poisoned_1(addr);
+ case 2:
+ case 4:
+ case 8:
+ return memory_is_poisoned_2_4_8(addr, size);
+ case 16:
+ return memory_is_poisoned_16(addr);
+ default:
+ assert(0);
+ }
+ }
+
+ return memory_is_poisoned_n(addr, size);
+}
+
+static const void *find_first_bad_addr(const void *addr, size_t size)
+{
+ u8 shadow_val = *(u8 *)asan_mem_to_shadow(addr);
+ const void *first_bad_addr = addr;
+
+ while (!shadow_val && first_bad_addr < addr + size) {
+ first_bad_addr += ASAN_SHADOW_SCALE_SIZE;
+ shadow_val = *(u8 *)asan_mem_to_shadow(first_bad_addr);
+ }
+ return first_bad_addr;
+}
+
+static const char *get_shadow_bug_type(const void *addr, size_t size)
+{
+ const char *bug_type = "unknown-crash";
+ u8 *shadow_addr;
+ const void *first_bad_addr;
+
+ if (addr < asan_shadow_to_mem((void *) &_asan_shadow))
+ return bug_type;
+
+ first_bad_addr = find_first_bad_addr(addr, size);
+
+ shadow_addr = (u8 *)asan_mem_to_shadow(first_bad_addr);
+
+ if (*shadow_addr > 0 && *shadow_addr <= ASAN_SHADOW_SCALE_SIZE - 1)
+ shadow_addr++;
+
+ switch (*shadow_addr) {
+ case 0 ... ASAN_SHADOW_SCALE_SIZE - 1:
+ bug_type = "out-of-bounds";
+ break;
+ case ASAN_GLOBAL_REDZONE:
+ bug_type = "global-out-of-bounds";
+ break;
+ case ASAN_STACK_LEFT:
+ case ASAN_STACK_MID:
+ case ASAN_STACK_RIGHT:
+ case ASAN_STACK_PARTIAL:
+ bug_type = "stack-out-of-bounds";
+ break;
+ case ASAN_USE_AFTER_SCOPE:
+ bug_type = "use-after-scope";
+ break;
+ default:
+ bug_type = "unknown-crash";
+ }
+
+ return bug_type;
+}
+
+void asan_report(unsigned long addr, size_t size, bool is_write,
+ unsigned long ip)
+{
+ const char *bug_type = get_shadow_bug_type((void *) addr, size);
+ printk(BIOS_ERR, "\n");
+ printk(BIOS_ERR, "ASan: %s in %p\n", bug_type, (void *) ip);
+ printk(BIOS_ERR, "%s of %zu byte%s at addr %p\n",
+ is_write ? "Write" : "Read", size, (size > 1 ? "s" : ""),
+ (void *) addr);
+ printk(BIOS_ERR, "\n");
+}
+
+static __always_inline void check_memory_region_inline(unsigned long addr,
+ size_t size, bool write,
+ unsigned long ret_ip)
+{
+ if (((uintptr_t)addr < (uintptr_t)&_data) ||
+ ((uintptr_t)addr > (uintptr_t)&_eheap))
+ return;
+
+ if (unlikely(size == 0))
+ return;
+
+ if (unlikely((void *)addr <
+ asan_shadow_to_mem((void *) &_asan_shadow))) {
+ asan_report(addr, size, write, ret_ip);
+ return;
+ }
+
+ if (likely(!memory_is_poisoned(addr, size)))
+ return;
+
+ asan_report(addr, size, write, ret_ip);
+}
+
+static void check_memory_region(unsigned long addr, size_t size, bool write,
+ unsigned long ret_ip)
+{
+ check_memory_region_inline(addr, size, write, ret_ip);
+}
+
+uintptr_t __asan_shadow_offset(uintptr_t addr)
+{
+ return (uintptr_t)&_asan_shadow - (((uintptr_t)&_data) >>
+ ASAN_SHADOW_SCALE_SHIFT);
+}
+
+static void register_global(struct asan_global *global)
+{
+ size_t aligned_size = ALIGN_UP(global->size, ASAN_SHADOW_SCALE_SIZE);
+
+ asan_unpoison_shadow(global->beg, global->size);
+
+ asan_poison_shadow(global->beg + aligned_size,
+ global->size_with_redzone - aligned_size,
+ ASAN_GLOBAL_REDZONE);
+}
+
+void __asan_register_globals(struct asan_global *globals, size_t size)
+{
+ int i;
+
+ for (i = 0; i < size; i++)
+ register_global(&globals[i]);
+}
+
+void __asan_unregister_globals(struct asan_global *globals, size_t size)
+{
+}
+
+/*
+ * GCC adds constructors invoking __asan_register_globals() and passes
+ * information about global variable (address, size, size with redzone ...)
+ * to it so we could poison variable's redzone.
+ * This function calls those constructors.
+ */
+static void asan_ctors(void)
+{
+ extern long __CTOR_LIST__;
+ typedef void (*func_ptr)(void);
+ func_ptr *ctor = (func_ptr *) &__CTOR_LIST__;
+ if (ctor == NULL)
+ return;
+
+ for (; *ctor != (func_ptr) 0; ctor++)
+ (*ctor)();
+}
+
+void asan_init(void)
+{
+ size_t size = (size_t)&_eheap - (size_t)&_data;
+ asan_unpoison_shadow((void *)&_data, size);
+ asan_ctors();
+}
+
+void __asan_poison_stack_memory(const void *addr, size_t size)
+{
+ asan_poison_shadow(addr, ALIGN_UP(size, ASAN_SHADOW_SCALE_SIZE),
+ ASAN_USE_AFTER_SCOPE);
+}
+
+void __asan_unpoison_stack_memory(const void *addr, size_t size)
+{
+ asan_unpoison_shadow(addr, size);
+}
+
+#define DEFINE_ASAN_LOAD_STORE(size) \
+ void __asan_load##size(unsigned long addr) \
+ { \
+ check_memory_region_inline(addr, size, false, _RET_IP_);\
+ } \
+ void __asan_load##size##_noabort(unsigned long addr) \
+ { \
+ check_memory_region_inline(addr, size, false, _RET_IP_);\
+ } \
+ void __asan_store##size(unsigned long addr) \
+ { \
+ check_memory_region_inline(addr, size, true, _RET_IP_); \
+ } \
+ void __asan_store##size##_noabort(unsigned long addr) \
+ { \
+ check_memory_region_inline(addr, size, true, _RET_IP_); \
+ }
DEFINE_ASAN_LOAD_STORE(1);
DEFINE_ASAN_LOAD_STORE(2);
@@ -19,16 +340,71 @@ DEFINE_ASAN_LOAD_STORE(8);
DEFINE_ASAN_LOAD_STORE(16);
void __asan_loadN(unsigned long addr, size_t size)
-{}
-
-void __asan_loadN_noabort(unsigned long addr, size_t size)
-{}
+{
+ check_memory_region(addr, size, false, _RET_IP_);
+}
void __asan_storeN(unsigned long addr, size_t size)
-{}
+{
+ check_memory_region(addr, size, true, _RET_IP_);
+}
+
+void __asan_loadN_noabort(unsigned long addr, size_t size)
+{
+ check_memory_region(addr, size, false, _RET_IP_);
+}
void __asan_storeN_noabort(unsigned long addr, size_t size)
-{}
+{
+ check_memory_region(addr, size, true, _RET_IP_);
+}
void __asan_handle_no_return(void)
-{}
+{
+}
+
+#define DEFINE_ASAN_SET_SHADOW(byte) \
+ void __asan_set_shadow_##byte(const void *addr, size_t size) \
+ { \
+ __builtin_memset((void *)addr, 0x##byte, size); \
+ }
+
+DEFINE_ASAN_SET_SHADOW(00);
+DEFINE_ASAN_SET_SHADOW(f1);
+DEFINE_ASAN_SET_SHADOW(f2);
+DEFINE_ASAN_SET_SHADOW(f3);
+DEFINE_ASAN_SET_SHADOW(f5);
+DEFINE_ASAN_SET_SHADOW(f8);
+
+#define DEFINE_ASAN_REPORT_LOAD(size) \
+void __asan_report_load##size##_noabort(unsigned long addr) \
+{ \
+ asan_report(addr, size, false, _RET_IP_); \
+}
+
+#define DEFINE_ASAN_REPORT_STORE(size) \
+void __asan_report_store##size##_noabort(unsigned long addr) \
+{ \
+ asan_report(addr, size, true, _RET_IP_); \
+}
+
+DEFINE_ASAN_REPORT_LOAD(1);
+DEFINE_ASAN_REPORT_LOAD(2);
+DEFINE_ASAN_REPORT_LOAD(4);
+DEFINE_ASAN_REPORT_LOAD(8);
+DEFINE_ASAN_REPORT_LOAD(16);
+DEFINE_ASAN_REPORT_STORE(1);
+DEFINE_ASAN_REPORT_STORE(2);
+DEFINE_ASAN_REPORT_STORE(4);
+DEFINE_ASAN_REPORT_STORE(8);
+DEFINE_ASAN_REPORT_STORE(16);
+
+void __asan_report_load_n_noabort(unsigned long addr, size_t size)
+{
+ asan_report(addr, size, false, _RET_IP_);
+}
+
+void __asan_report_store_n_noabort(unsigned long addr, size_t size)
+{
+ asan_report(addr, size, true, _RET_IP_);
+}