coreboot-kgpe-d16/src/lib/asan.c
Harshit Sharma 51593dd0c6 arch/x86: Add support for ASan to memory functions
Compiler's instrumentation cannot insert asan memory checks in
case of memory functions like memset, memcpy and memmove as they
are written in assembly.

So, we need to manually check the memory state before performing
each of these operations to ensure that ASan is triggered in case
of bad access.

Change-Id: I2030437636c77aea7cccda8efe050df4b77c15c7
Signed-off-by: Harshit Sharma <harshitsharmajs@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/44307
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Werner Zeh <werner.zeh@siemens.com>
2020-08-21 07:46:04 +00:00

438 lines
10 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Address sanitizer support.
*
* Parts of this file are based on mm/kasan
* from the Linux kernel 4.19.137.
*
*/
#include <symbols.h>
#include <assert.h>
#include <arch/symbols.h>
#include <asan.h>
static inline void *asan_mem_to_shadow(const void *addr)
{
#if ENV_ROMSTAGE
return (void *)((uintptr_t)&_asan_shadow + (((uintptr_t)addr -
(uintptr_t)&_car_region_start) >> ASAN_SHADOW_SCALE_SHIFT));
#elif ENV_RAMSTAGE
return (void *)((uintptr_t)&_asan_shadow + (((uintptr_t)addr -
(uintptr_t)&_data) >> ASAN_SHADOW_SCALE_SHIFT));
#endif
}
static inline const void *asan_shadow_to_mem(const void *shadow_addr)
{
#if ENV_ROMSTAGE
return (void *)((uintptr_t)&_car_region_start + (((uintptr_t)shadow_addr -
(uintptr_t)&_asan_shadow) << ASAN_SHADOW_SCALE_SHIFT));
#elif ENV_RAMSTAGE
return (void *)((uintptr_t)&_data + (((uintptr_t)shadow_addr -
(uintptr_t)&_asan_shadow) << ASAN_SHADOW_SCALE_SHIFT));
#endif
}
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 ENV_ROMSTAGE
if (((uintptr_t)addr < (uintptr_t)&_car_region_start) ||
((uintptr_t)addr > (uintptr_t)&_ebss))
return;
#elif ENV_RAMSTAGE
if (((uintptr_t)addr < (uintptr_t)&_data) ||
((uintptr_t)addr > (uintptr_t)&_eheap))
return;
#endif
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);
}
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)
{
#if ENV_ROMSTAGE
return (uintptr_t)&_asan_shadow - (((uintptr_t)&_car_region_start) >>
ASAN_SHADOW_SCALE_SHIFT);
#elif ENV_RAMSTAGE
return (uintptr_t)&_asan_shadow - (((uintptr_t)&_data) >>
ASAN_SHADOW_SCALE_SHIFT);
#endif
}
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.
*/
#if ENV_RAMSTAGE
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)();
}
#endif
void asan_init(void)
{
#if ENV_ROMSTAGE
size_t size = (size_t)&_ebss - (size_t)&_car_region_start;
asan_unpoison_shadow((void *)&_car_region_start, size);
#elif ENV_RAMSTAGE
size_t size = (size_t)&_eheap - (size_t)&_data;
asan_unpoison_shadow((void *)&_data, size);
asan_ctors();
#endif
}
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);
DEFINE_ASAN_LOAD_STORE(4);
DEFINE_ASAN_LOAD_STORE(8);
DEFINE_ASAN_LOAD_STORE(16);
void __asan_loadN(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_);
}