Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASan: Enable for globals #251

Merged
merged 2 commits into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions LibOS/shim/src/arch/x86_64/shim.lds
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ SECTIONS
.dynamic : { *(.dynamic) }
.got : { *(.got) *(.igot) }
.got.plt : { *(.got.plt) *(.igot.plt) }
.init_array : {
__init_array_start = .;
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) .init_array))
__init_array_end = .;
}
. = DATA_SEGMENT_RELRO_END (0, .);
.data :
{
Expand Down
12 changes: 12 additions & 0 deletions LibOS/shim/src/shim_call.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ static int run_test_asan_stack(void) {

return 0;
}

/* Test: write past the end of a global (static local) buffer (ASan only) */
__attribute__((no_sanitize("undefined")))
static int run_test_asan_global(void) {
static char buf[30];
char* c = buf + 30;
*c = 1;

return 0;
}

#endif /* ASAN */

static const struct shim_test {
Expand All @@ -62,6 +73,7 @@ static const struct shim_test {
#ifdef ASAN
{ "asan_heap", &run_test_asan_heap },
{ "asan_stack", &run_test_asan_stack },
{ "asan_global", &run_test_asan_global },
#endif
{ NULL, NULL },
};
Expand Down
8 changes: 6 additions & 2 deletions LibOS/shim/src/shim_checkpoint.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,10 @@ BEGIN_CP_FUNC(migratable) {

size_t len = &__migratable_end - &__migratable[0];
size_t off = ADD_CP_OFFSET(len);
memcpy((char*)base + off, &__migratable[0], len);
/* Use `_real_memcpy` to bypass ASan: we are accessing the whole `.migratable` section,
* including the redzones after global variables, and using normal `memcpy` would cause ASan to
* complain. */
_real_memcpy((char*)base + off, &__migratable[0], len);
ADD_CP_FUNC_ENTRY(off);
}
END_CP_FUNC(migratable)
Expand All @@ -178,7 +181,8 @@ BEGIN_RS_FUNC(migratable) {
__UNUSED(rebase);

const char* data = (char*)base + GET_CP_FUNC_ENTRY();
memcpy(&__migratable[0], data, &__migratable_end - &__migratable[0]);
/* Use `_real_memcpy` to bypass ASan (same as above). */
_real_memcpy(&__migratable[0], data, &__migratable_end - &__migratable[0]);
}
END_RS_FUNC(migratable)

Expand Down
3 changes: 3 additions & 0 deletions LibOS/shim/src/shim_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "api.h"
#include "hex.h"
#include "init.h"
#include "pal.h"
#include "pal_error.h"
#include "shim_checkpoint.h"
Expand Down Expand Up @@ -383,6 +384,8 @@ noreturn void* shim_init(int argc, const char** argv, const char** envp) {

log_setprefix(shim_get_tcb());

call_init_array();

extern const char g_gramine_commit_hash[];
log_debug("Gramine was built from commit: %s", g_gramine_commit_hash);

Expand Down
20 changes: 11 additions & 9 deletions LibOS/shim/test/regression/test_libos.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,21 @@ def test_010_gramine_run_test(self):
def test_020_ubsan(self):
self._test_abort('ubsan_int_overflow', ['ubsan: overflow'])

@unittest.skipUnless(os.environ.get('ASAN') == '1', 'test only enabled with ASAN=1')
def test_021_asan_heap(self):
expected_list = ['asan: heap-buffer-overflow']
if self.has_debug():
expected_list.append('asan: location: run_test_asan_heap at shim_call.c')
self._test_abort('asan_heap', expected_list)
self._test_asan('heap', 'heap-buffer-overflow')

@unittest.skipUnless(os.environ.get('ASAN') == '1', 'test only enabled with ASAN=1')
def test_022_asan_stack(self):
expected_list = ['asan: stack-buffer-overflow']
self._test_asan('stack', 'stack-buffer-overflow')

def test_023_asan_stack(self):
self._test_asan('global', 'global-buffer-overflow')

@unittest.skipUnless(os.environ.get('ASAN') == '1', 'test only enabled with ASAN=1')
def _test_asan(self, case, desc):
expected_list = [f'asan: {desc}']
if self.has_debug():
expected_list.append('asan: location: run_test_asan_stack at shim_call.c')
self._test_abort('asan_stack', expected_list)
expected_list.append(f'asan: location: run_test_asan_{case} at shim_call.c')
self._test_abort(f'asan_{case}', expected_list)

def _test_abort(self, test_name, expected_list):
try:
Expand Down
3 changes: 3 additions & 0 deletions Pal/src/host/Linux-SGX/db_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "enclave_pages.h"
#include "enclave_pf.h"
#include "enclave_tf.h"
#include "init.h"
#include "pal.h"
#include "pal_error.h"
#include "pal_internal.h"
Expand Down Expand Up @@ -668,6 +669,8 @@ noreturn void pal_linux_main(char* uptr_libpal_uri, size_t libpal_uri_len, char*
ocall_exit(1, /*is_exitgroup=*/true);
}

call_init_array();

/* Initialize alloc_align as early as possible, a lot of PAL APIs depend on this being set. */
g_pal_state.alloc_align = g_page_size;
assert(IS_POWER_OF_2(g_pal_state.alloc_align));
Expand Down
5 changes: 5 additions & 0 deletions Pal/src/host/Linux-SGX/enclave.lds
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ SECTIONS
.jcr : { KEEP(*(.jcr)) }
.got : { *(.got) *(.igot) }
.got.plt : { *(.got.plt) *(.igot.plt) }
.init_array : {
__init_array_start = .;
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) .init_array))
__init_array_end = .;
}
. = DATA_SEGMENT_RELRO_END (0, .);
.data :
{
Expand Down
14 changes: 10 additions & 4 deletions Pal/src/host/Linux-SGX/sgx_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,16 @@ noreturn static void print_usage_and_exit(const char* argv_0) {
}

#ifdef ASAN
/*
* HACK: `setup_asan` is not called inside `main`, but defined as a constructor with a priority of
* 0, which is normally reserved. This is because we need to run it before module constructors
* generated by ASan itself (which have a priority of 1).
*
* Note that this is necessary only because we're in an executable linked against Glibc. In other
* cases, we invoke the constructors from `.init_array` directly, so we have full control over
* initialization.
*/
__attribute((constructor(0)))
__attribute_no_sanitize_address
static void setup_asan(void) {
int prot = PROT_READ | PROT_WRITE;
Expand All @@ -1096,10 +1106,6 @@ int main(int argc, char* argv[], char* envp[]) {
bool need_gsgx = true;
char* manifest = NULL;

#ifdef ASAN
setup_asan();
#endif

#ifdef DEBUG
ret = debug_map_init_from_proc_maps();
if (ret < 0) {
Expand Down
5 changes: 5 additions & 0 deletions Pal/src/host/Linux/arch/x86_64/pal.lds
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ SECTIONS
.dynamic : { *(.dynamic) }
.got : { *(.got) *(.igot) }
.got.plt : { *(.got.plt) *(.igot.plt) }
.init_array : {
__init_array_start = .;
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) .init_array))
__init_array_end = .;
}
. = DATA_SEGMENT_RELRO_END (0, .);
.data :
{
Expand Down
3 changes: 3 additions & 0 deletions Pal/src/host/Linux/db_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "asan.h"
#include "debug_map.h"
#include "elf/elf.h"
#include "init.h"
#include "linux_utils.h"
#include "pal.h"
#include "pal_error.h"
Expand Down Expand Up @@ -186,6 +187,8 @@ noreturn void pal_linux_main(void* initial_rsp, void* fini_callback) {
if (ret < 0)
INIT_FAIL(-ret, "_DkSystemTimeQuery() failed");

call_init_array();

/* Initialize alloc_align as early as possible, a lot of PAL APIs depend on this being set. */
g_pal_state.alloc_align = g_page_size;
assert(IS_POWER_OF_2(g_pal_state.alloc_align));
Expand Down
5 changes: 5 additions & 0 deletions Pal/src/host/Skeleton/arch/x86_64/pal.lds
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ SECTIONS
.dynamic : { *(.dynamic) }
.got : { *(.got) *(.igot) }
.got.plt : { *(.got.plt) *(.igot.plt) }
.init_array : {
__init_array_start = .;
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) .init_array))
__init_array_end = .;
}
.data :
{
/* the rest of data segment */
Expand Down
35 changes: 32 additions & 3 deletions common/include/asan.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,17 @@
* unpoisoned before unmapping (in case ASan-unaware code uses this part of address space
* later).
*
* - Make sure to call the constructors in the `.init_array` section (see `init.h` for a helper
* function). This is necessary for global variables sanitization (see `__asan_register_globals`
* below).
*
* - You should compile the program with:
*
* -fsanitize=address
* -fno-sanitize-link-runtime
* -mllvm -asan-mapping-offset=0x18000000000
* -mllvm -asan-use-after-return=0
* -mllvm -asan-stack=0
* -mllvm -asan-globals=0
* -DASAN
*
* If you want to enable stack sanitization (`-mllvm -asan-stack=1`), you need to also handle
Expand Down Expand Up @@ -149,6 +152,7 @@
#define ASAN_POISON_STACK_AFTER_SCOPE 0xf8
#define ASAN_POISON_ALLOCA_LEFT 0xca
#define ASAN_POISON_ALLOCA_RIGHT 0xcb
#define ASAN_POISON_GLOBAL 0xf9
#define ASAN_POISON_USER 0xf7 /* currently used for unallocated SGX memory */

/* Size of `alloca` redzone (hardcoded in LLVM: `kAllocaRzSize`); see `asan_alloca_poison` below. */
Expand All @@ -167,8 +171,8 @@ void asan_unpoison_region(uintptr_t addr, size_t size);
* prints a warning instead. */
void asan_unpoison_current_stack(uintptr_t addr, size_t size);

/* Initialization callbacks. Generated in object .init sections. Graphene doesn't call these anyway,
* so this needs to be a no-op. */
/* Initialization callbacks, called in `.init_array`. In Gramine, these do nothing, and we
* initialize ASan separately. */
void __asan_init(void);
void __asan_version_mismatch_check_v8(void);

Expand Down Expand Up @@ -227,6 +231,31 @@ void __asan_alloca_poison(uintptr_t addr, size_t size);
/* Unpoison the stack area from `start` to `end`. Do nothing if `start` is zero. */
void __asan_allocas_unpoison(uintptr_t start, uintptr_t end);

/*
* Description of an instrumented global variable. See LLVM (`asan_interface_internal.h`) for
* original definition.
*
* Currently, we read only `addr`, `size`, and `size_with_redzone` fields.
*/
struct __asan_global {
uintptr_t addr; /* in LLVM: `beg` */
size_t size;
size_t size_with_redzone;
const char* name;
const char* module_name;
size_t has_dynamic_init;
void* location; /* in LLVM: `__asan_global_source_location* location` */
uintptr_t odr_indicator;
};

/* Register global variables. Called in an `.init_array` constructor generated by ASan. Our
* implementation poisons the right redzone (`size .. size_with_redzone`). */
void __asan_register_globals(struct __asan_global* globals, size_t n);

/* Unregister global variables. Called in a `.fini_array` destructor generated by ASan. Our
* implementation does nothing (and we don't even handle `.fini_array`). */
void __asan_unregister_globals(struct __asan_global* globals, size_t n);

/* Callbacks for setting the shadow memory to specific values. As with load/store callbacks, LLVM
* normally generates inline stores and calls these functions only for bigger areas. This is
* controlled by `-mllvm -asan-max-inline-poisoning-size=N`. */
Expand Down
17 changes: 17 additions & 0 deletions common/include/init.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2021 Intel Corporation
* Paweł Marczewski <pawel@invisiblethingslab.com>
*/

#ifndef INIT_H_
#define INIT_H_

/*
* Call the constructors specified in `.init_array`. Should be called during initialization.
*
* NOTE: Glibc handles `.init_array` by itself, so normal executables compiled against Glibc (e.g.
* Linux-SGX untrusted runtime) should not call this.
*/
void call_init_array(void);

#endif /* INIT_H_ */
16 changes: 16 additions & 0 deletions common/src/asan.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ static void asan_find_problem(uintptr_t addr, size_t size, uintptr_t* out_bad_ad
case ASAN_POISON_ALLOCA_RIGHT:
bug_type = "dynamic-stack-buffer-overflow";
break;
case ASAN_POISON_GLOBAL:
bug_type = "global-buffer-overflow";
break;
default:
bug_type = "unknown-crash";
break;
Expand Down Expand Up @@ -196,6 +199,7 @@ static void asan_dump(uintptr_t bad_addr) {
log_error("asan: %22s %02x", "stack after scope:", ASAN_POISON_STACK_AFTER_SCOPE);
log_error("asan: %22s %02x", "alloca left redzone:", ASAN_POISON_ALLOCA_LEFT);
log_error("asan: %22s %02x", "alloca right redzone:", ASAN_POISON_ALLOCA_RIGHT);
log_error("asan: %22s %02x", "global redzone:", ASAN_POISON_GLOBAL);
log_error("asan: %22s %02x", "user-poisoned:", ASAN_POISON_USER);
}

Expand Down Expand Up @@ -343,6 +347,18 @@ void __asan_allocas_unpoison(uintptr_t start, uintptr_t end) {
}
}

void __asan_register_globals(struct __asan_global* globals, size_t n) {
for (size_t i = 0; i < n; i++) {
asan_poison_right_redzone(globals[i].addr, globals[i].size, globals[i].size_with_redzone,
ASAN_POISON_GLOBAL);
}
}

void __asan_unregister_globals(struct __asan_global* globals, size_t n) {
__UNUSED(globals);
__UNUSED(n);
}

#define DEFINE_ASAN_SET_SHADOW(name, value) \
__attribute__((noinline)) \
void __asan_set_shadow_ ## name (uintptr_t addr, size_t size) { \
Expand Down
23 changes: 23 additions & 0 deletions common/src/init.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2021 Intel Corporation
* Paweł Marczewski <pawel@invisiblethingslab.com>
*/

#include "init.h"

/*
* Helper symbols for accessing the `.init_array` section at run time. These are part of default
* linker script (you can see it by running `ld --verbose`) as well as our custom linker scripts.
*
* NOTE: We rely on the fact that each ELF object (PAL, LibOS) contains its own copy of this module,
* referring to `__init_array_start` and `__init_array_end` in that object.
*/
extern void (*__init_array_start)(void);
extern void (*__init_array_end)(void);

void call_init_array(void) {
void (**func)(void);
for (func = &__init_array_start; func < &__init_array_end; func++) {
(*func)();
}
}
1 change: 1 addition & 0 deletions common/src/meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
common_src = files(
'avl_tree.c',
'init.c',
'location.c',
'network/hton.c',
'network/inet_pton.c',
Expand Down
1 change: 0 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ if asan
'-fno-sanitize-link-runtime',
'-mllvm', '-asan-mapping-offset=' + asan_shadow_start,
'-mllvm', '-asan-use-after-return=0',
'-mllvm', '-asan-globals=0',
'-DASAN',
]
endif
Expand Down