51593dd0c6
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>
438 lines
10 KiB
C
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_);
|
|
}
|