blob: 9459009cc7c5834cd09a8a9cca1e9d6faa1af0eb [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2016, Linaro Limited
*/
#include <assert.h>
#include <compiler.h>
#include <keep.h>
#include <kernel/asan.h>
#include <kernel/panic.h>
#include <string.h>
#include <trace.h>
#include <types_ext.h>
#include <util.h>
#if __GCC_VERSION >= 70000
#define ASAN_ABI_VERSION 7
#else
#define ASAN_ABI_VERSION 6
#endif
struct asan_source_location {
const char *file_name;
int line_no;
int column_no;
};
struct asan_global {
uintptr_t beg;
uintptr_t size;
uintptr_t size_with_redzone;
const char *name;
const char *module_name;
uintptr_t has_dynamic_init;
struct asan_source_location *location;
#if ASAN_ABI_VERSION >= 7
uintptr_t odr_indicator;
#endif
};
static vaddr_t asan_va_base;
static size_t asan_va_size;
static bool asan_active;
static int8_t *va_to_shadow(const void *va)
{
vaddr_t sa = ((vaddr_t)va / ASAN_BLOCK_SIZE) + CFG_ASAN_SHADOW_OFFSET;
return (int8_t *)sa;
}
static size_t va_range_to_shadow_size(const void *begin, const void *end)
{
return ((vaddr_t)end - (vaddr_t)begin) / ASAN_BLOCK_SIZE;
}
static bool va_range_inside_shadow(const void *begin, const void *end)
{
vaddr_t b = (vaddr_t)begin;
vaddr_t e = (vaddr_t)end;
if (b >= e)
return false;
return (b >= asan_va_base) && (e <= (asan_va_base + asan_va_size));
}
static bool va_range_outside_shadow(const void *begin, const void *end)
{
vaddr_t b = (vaddr_t)begin;
vaddr_t e = (vaddr_t)end;
if (b >= e)
return false;
return (e <= asan_va_base) || (b >= (asan_va_base + asan_va_size));
}
static size_t va_misalignment(const void *va)
{
return (vaddr_t)va & ASAN_BLOCK_MASK;
}
static bool va_is_well_aligned(const void *va)
{
return !va_misalignment(va);
}
void asan_set_shadowed(const void *begin, const void *end)
{
vaddr_t b = (vaddr_t)begin;
vaddr_t e = (vaddr_t)end;
assert(!asan_va_base);
assert(va_is_well_aligned(begin));
assert(va_is_well_aligned(end));
assert(b < e);
asan_va_base = b;
asan_va_size = e - b;
}
void asan_tag_no_access(const void *begin, const void *end)
{
assert(va_is_well_aligned(begin));
assert(va_is_well_aligned(end));
assert(va_range_inside_shadow(begin, end));
asan_memset_unchecked(va_to_shadow(begin), ASAN_DATA_RED_ZONE,
va_range_to_shadow_size(begin, end));
}
void asan_tag_access(const void *begin, const void *end)
{
if (!asan_va_base || (begin == end))
return;
assert(va_range_inside_shadow(begin, end));
assert(va_is_well_aligned(begin));
asan_memset_unchecked(va_to_shadow(begin), 0,
va_range_to_shadow_size(begin, end));
if (!va_is_well_aligned(end))
*va_to_shadow(end) = ASAN_BLOCK_SIZE - va_misalignment(end);
}
void asan_tag_heap_free(const void *begin, const void *end)
{
if (!asan_va_base)
return;
assert(va_range_inside_shadow(begin, end));
assert(va_is_well_aligned(begin));
assert(va_is_well_aligned(end));
asan_memset_unchecked(va_to_shadow(begin), ASAN_HEAP_RED_ZONE,
va_range_to_shadow_size(begin, end));
}
void *asan_memset_unchecked(void *s, int c, size_t n)
{
uint8_t *b = s;
size_t m;
for (m = 0; m < n; m++)
b[m] = c;
return s;
}
void *asan_memcpy_unchecked(void *__restrict dst, const void *__restrict src,
size_t len)
{
uint8_t *__restrict d = dst;
const uint8_t *__restrict s = src;
size_t n;
for (n = 0; n < len; n++)
d[n] = s[n];
return dst;
}
void asan_start(void)
{
assert(asan_va_base && !asan_active);
asan_active = true;
}
static void check_access(vaddr_t addr, size_t size)
{
void *begin = (void *)addr;
void *end = (void *)(addr + size);
int8_t *a;
int8_t *e;
if (!asan_active || !size)
return;
if (va_range_outside_shadow(begin, end))
return;
/*
* If it isn't outside it has to be completely inside or there's a
* problem.
*/
if (!va_range_inside_shadow(begin, end))
panic();
e = va_to_shadow((void *)(addr + size - 1));
for (a = va_to_shadow(begin); a <= e; a++)
if (*a < 0)
panic();
if (!va_is_well_aligned(end) &&
va_misalignment(end) > (size_t)(*e - ASAN_BLOCK_SIZE))
panic();
}
static void check_load(vaddr_t addr, size_t size)
{
check_access(addr, size);
}
static void check_store(vaddr_t addr, size_t size)
{
check_access(addr, size);
}
static void __noreturn report_load(vaddr_t addr __unused, size_t size __unused)
{
panic();
}
static void __noreturn report_store(vaddr_t addr __unused, size_t size __unused)
{
panic();
}
#define DEFINE_ASAN_FUNC(type, size) \
void __asan_##type##size(vaddr_t addr); \
void __asan_##type##size(vaddr_t addr) \
{ check_##type(addr, size); } \
void __asan_##type##size##_noabort(vaddr_t addr); \
void __asan_##type##size##_noabort(vaddr_t addr) \
{ check_##type(addr, size); } \
void __asan_report_##type##size##_noabort(vaddr_t addr);\
void __noreturn __asan_report_##type##size##_noabort(vaddr_t addr) \
{ report_##type(addr, size); }
DEFINE_ASAN_FUNC(load, 1)
DEFINE_ASAN_FUNC(load, 2)
DEFINE_ASAN_FUNC(load, 4)
DEFINE_ASAN_FUNC(load, 8)
DEFINE_ASAN_FUNC(load, 16)
DEFINE_ASAN_FUNC(store, 1)
DEFINE_ASAN_FUNC(store, 2)
DEFINE_ASAN_FUNC(store, 4)
DEFINE_ASAN_FUNC(store, 8)
DEFINE_ASAN_FUNC(store, 16)
void __asan_loadN_noabort(vaddr_t addr, size_t size);
void __asan_loadN_noabort(vaddr_t addr, size_t size)
{
check_load(addr, size);
}
void __asan_storeN_noabort(vaddr_t addr, size_t size);
void __asan_storeN_noabort(vaddr_t addr, size_t size)
{
check_store(addr, size);
}
void __asan_report_load_n_noabort(vaddr_t addr, size_t size);
void __noreturn __asan_report_load_n_noabort(vaddr_t addr, size_t size)
{
report_load(addr, size);
}
void __asan_report_store_n_noabort(vaddr_t addr, size_t size);
void __noreturn __asan_report_store_n_noabort(vaddr_t addr, size_t size)
{
report_store(addr, size);
}
void __asan_handle_no_return(void);
void __asan_handle_no_return(void)
{
}
void __asan_register_globals(struct asan_global *globals, size_t size);
void __asan_register_globals(struct asan_global *globals, size_t size)
{
size_t n;
for (n = 0; n < size; n++)
asan_tag_access((void *)globals[n].beg,
(void *)(globals[n].beg + globals[n].size));
}
KEEP_INIT(__asan_register_globals);
void __asan_unregister_globals(struct asan_global *globals, size_t size);
void __asan_unregister_globals(struct asan_global *globals __unused,
size_t size __unused)
{
}