diff options
-rw-r--r-- | src/Kconfig | 8 | ||||
-rw-r--r-- | src/lib/Makefile.inc | 4 | ||||
-rw-r--r-- | src/lib/ubsan.c | 360 |
3 files changed, 372 insertions, 0 deletions
diff --git a/src/Kconfig b/src/Kconfig index 50a054a128..637f5757e7 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -200,6 +200,14 @@ config COVERAGE coverage information in CBMEM for extraction from user space. If unsure, say N. +config UBSAN + bool "Undefined behavior sanitizer support" + default n + help + Instrument the code with checks for undefined behavior. If unsure, + say N because it adds a small performance penalty and may abort + on code that happens to work in spite of the UB. + config RELOCATABLE_RAMSTAGE depends on EARLY_CBMEM_INIT bool "Build the ramstage to be relocatable in 32-bit address space." diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index 5079bbfea5..e05b3fcc5d 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -15,6 +15,10 @@ subdirs-y += loaders subdirs-y += gnat +ifeq ($(CONFIG_UBSAN),y) +ramstage-y += ubsan.c +CFLAGS_ramstage += -fsanitize=undefined +endif ifneq ($(CONFIG_BOOTBLOCK_CUSTOM),y) bootblock-y += bootblock.c diff --git a/src/lib/ubsan.c b/src/lib/ubsan.c new file mode 100644 index 0000000000..1cd0612cf4 --- /dev/null +++ b/src/lib/ubsan.c @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2014, 2015 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * ubsan/ubsan.c + * Undefined behavior sanitizer runtime support. + * + * Adapted from: + * https://gitlab.com/sortix/sortix/raw/master/libc/ubsan/ubsan.c + */ + +#include <stdint.h> +#include <console/console.h> + +struct ubsan_source_location { + const char *filename; + uint32_t line; + uint32_t column; +}; + +struct ubsan_type_descriptor { + uint16_t type_kind; + uint16_t type_info; + char type_name[]; +}; + +typedef uintptr_t ubsan_value_handle_t; + +/* +* Keep the compiler happy -- it wants prototypes but nobody +* except the compiler should be touching these functions. +*/ +#pragma GCC diagnostic ignored "-Wmissing-prototypes" + +__attribute__((noreturn)) +static void ubsan_abort(const struct ubsan_source_location *location, + const char *violation) { + static const struct ubsan_source_location unknown_location = { + "<unknown file>", + 0, + 0, + }; + + if (!location || !location->filename) + location = &unknown_location; + printk(BIOS_ERR, "%s %s:%lu:%lu\n", violation, location->filename, + (unsigned long int)location->line, + (unsigned long int)location->column); + die("ubsan: unrecoverable error.\n"); +} + +#define ABORT_VARIANT(name, params, call) \ +__attribute__((noreturn)) \ +void __ubsan_handle_##name##_abort params; \ +__attribute__((noreturn)) \ +void __ubsan_handle_##name##_abort params { \ + __ubsan_handle_##name call; \ + __builtin_unreachable(); \ +} + +#define ABORT_VARIANT_VP(name) \ +ABORT_VARIANT(name, (void *a), (a)) +#define ABORT_VARIANT_VP_VP(name) \ +ABORT_VARIANT(name, (void *a, void *b), (a, b)) +#define ABORT_VARIANT_VP_IP(name) \ +ABORT_VARIANT(name, (void *a, intptr_t b), (a, b)) +#define ABORT_VARIANT_VP_VP_VP(name) \ +ABORT_VARIANT(name, (void *a, void *b, void *c), (a, b, c)) + +struct ubsan_type_mismatch_data { + struct ubsan_source_location location; + struct ubsan_type_descriptor *type; + uintptr_t alignment; + unsigned char type_check_kind; +}; + +void __ubsan_handle_type_mismatch(void *data_raw, void *pointer_raw) +{ + const struct ubsan_type_mismatch_data *data = + (struct ubsan_type_mismatch_data *)data_raw; + ubsan_value_handle_t pointer = (ubsan_value_handle_t)pointer_raw; + const char *violation = "type mismatch"; + if (!pointer) + violation = "null pointer access"; + else if (data->alignment && (pointer & (data->alignment - 1))) + violation = "unaligned access"; + ubsan_abort(&data->location, violation); +} + +ABORT_VARIANT_VP_VP(type_mismatch); + +struct ubsan_overflow_data { + struct ubsan_source_location location; + struct ubsan_type_descriptor *type; +}; + +void __ubsan_handle_add_overflow(void *data_raw, void *lhs_raw, + void *rhs_raw) +{ + const struct ubsan_overflow_data *data + = (struct ubsan_overflow_data *)data_raw; + ubsan_value_handle_t lhs = (ubsan_value_handle_t)lhs_raw; + ubsan_value_handle_t rhs = (ubsan_value_handle_t)rhs_raw; + (void)lhs; + (void)rhs; + ubsan_abort(&data->location, "addition overflow"); +} + +ABORT_VARIANT_VP_VP_VP(add_overflow); + +void __ubsan_handle_sub_overflow(void *data_raw, void *lhs_raw, + void *rhs_raw) +{ + const struct ubsan_overflow_data *data + = (struct ubsan_overflow_data *)data_raw; + ubsan_value_handle_t lhs = (ubsan_value_handle_t)lhs_raw; + ubsan_value_handle_t rhs = (ubsan_value_handle_t)rhs_raw; + (void)lhs; + (void)rhs; + ubsan_abort(&data->location, "subtraction overflow"); +} + +ABORT_VARIANT_VP_VP_VP(sub_overflow); + +void __ubsan_handle_mul_overflow(void *data_raw, void *lhs_raw, + void *rhs_raw) +{ + const struct ubsan_overflow_data *data + = (struct ubsan_overflow_data *)data_raw; + ubsan_value_handle_t lhs = (ubsan_value_handle_t)lhs_raw; + ubsan_value_handle_t rhs = (ubsan_value_handle_t)rhs_raw; + (void)lhs; + (void)rhs; + ubsan_abort(&data->location, "multiplication overflow"); +} + +ABORT_VARIANT_VP_VP_VP(mul_overflow); + +void __ubsan_handle_negate_overflow(void *data_raw, void *old_value_raw) +{ + const struct ubsan_overflow_data *data + = (struct ubsan_overflow_data *)data_raw; + ubsan_value_handle_t old_value + = (ubsan_value_handle_t)old_value_raw; + (void) old_value; + ubsan_abort(&data->location, "negation overflow"); +} + +ABORT_VARIANT_VP_VP(negate_overflow); + +void __ubsan_handle_divrem_overflow(void *data_raw, void *lhs_raw, + void *rhs_raw) +{ + const struct ubsan_overflow_data *data + = (struct ubsan_overflow_data *)data_raw; + ubsan_value_handle_t lhs = (ubsan_value_handle_t)lhs_raw; + ubsan_value_handle_t rhs = (ubsan_value_handle_t)rhs_raw; + (void)lhs; + (void)rhs; + ubsan_abort(&data->location, "division remainder overflow"); +} + +ABORT_VARIANT_VP_VP_VP(divrem_overflow); + +struct ubsan_shift_out_of_bounds_data { + struct ubsan_source_location location; + struct ubsan_type_descriptor *lhs_type; + struct ubsan_type_descriptor *rhs_type; +}; + +void __ubsan_handle_shift_out_of_bounds(void *data_raw, void *lhs_raw, + void *rhs_raw) +{ + const struct ubsan_shift_out_of_bounds_data *data = + (struct ubsan_shift_out_of_bounds_data *)data_raw; + ubsan_value_handle_t lhs = (ubsan_value_handle_t)lhs_raw; + ubsan_value_handle_t rhs = (ubsan_value_handle_t)rhs_raw; + (void)lhs; + (void)rhs; + ubsan_abort(&data->location, "shift out of bounds"); +} + +ABORT_VARIANT_VP_VP_VP(shift_out_of_bounds); + +struct ubsan_out_of_bounds_data { + struct ubsan_source_location location; + struct ubsan_type_descriptor *array_type; + struct ubsan_type_descriptor *index_type; +}; + +void __ubsan_handle_out_of_bounds(void *data_raw, void *index_raw) +{ + const struct ubsan_out_of_bounds_data *data = + (struct ubsan_out_of_bounds_data *)data_raw; + ubsan_value_handle_t index = (ubsan_value_handle_t)index_raw; + (void)index; + ubsan_abort(&data->location, "out of bounds"); +} + +ABORT_VARIANT_VP_VP(out_of_bounds); + +struct ubsan_unreachable_data { + struct ubsan_source_location location; +}; + +__attribute__((noreturn)) +void __ubsan_handle_builtin_unreachable(void *data_raw) +{ + struct ubsan_unreachable_data *data = + (struct ubsan_unreachable_data *)data_raw; + ubsan_abort(&data->location, "reached unreachable"); +} + +__attribute__((noreturn)) +void __ubsan_handle_missing_return(void *data_raw) +{ + const struct ubsan_unreachable_data *data = + (struct ubsan_unreachable_data *)data_raw; + ubsan_abort(&data->location, "missing return"); +} + +struct ubsan_vla_bound_data { + struct ubsan_source_location location; + struct ubsan_type_descriptor *type; +}; + +void __ubsan_handle_vla_bound_not_positive(void *data_raw, void *bound_raw) +{ + const struct ubsan_vla_bound_data *data + = (struct ubsan_vla_bound_data *)data_raw; + ubsan_value_handle_t bound = (ubsan_value_handle_t)bound_raw; + (void)bound; + ubsan_abort(&data->location, "negative variable array length"); +} + +ABORT_VARIANT_VP_VP(vla_bound_not_positive); + +struct ubsan_float_cast_overflow_data { +/* +* TODO: Remove this GCC 5.x compatibility after switching to GCC 6.x. The +* GCC developers accidentally forgot the source location. Their +* libubsan probes to see if it looks like a path, but we don't need +* to maintain compatibility with multiple gcc releases. See below. +*/ +#if !(defined(__GNUC__) && __GNUC__ < 6) + struct ubsan_source_location location; +#endif + struct ubsan_type_descriptor *from_type; + struct ubsan_type_descriptor *to_type; +}; + +void __ubsan_handle_float_cast_overflow(void *data_raw, void *from_raw) +{ + struct ubsan_float_cast_overflow_data *data = + (struct ubsan_float_cast_overflow_data *)data_raw; + ubsan_value_handle_t from = (ubsan_value_handle_t)from_raw; + (void) from; +#if !(defined(__GNUC__) && __GNUC__ < 6) + ubsan_abort(&data->location, "float cast overflow"); +#else + ubsan_abort(((void) data, &unknown_location), "float cast overflow"); +#endif +} + +ABORT_VARIANT_VP_VP(float_cast_overflow); + +struct ubsan_invalid_value_data { + struct ubsan_source_location location; + struct ubsan_type_descriptor *type; +}; + +void __ubsan_handle_load_invalid_value(void *data_raw, void *value_raw) +{ + const struct ubsan_invalid_value_data *data = + (struct ubsan_invalid_value_data *)data_raw; + ubsan_value_handle_t value = (ubsan_value_handle_t)value_raw; + (void)value; + ubsan_abort(&data->location, "invalid value load"); +} + +ABORT_VARIANT_VP_VP(load_invalid_value); + +struct ubsan_function_type_mismatch_data { + struct ubsan_source_location location; + struct ubsan_type_descriptor *type; +}; + +void __ubsan_handle_function_type_mismatch(void *data_raw, void *value_raw) +{ + const struct ubsan_function_type_mismatch_data *data = + (struct ubsan_function_type_mismatch_data *)data_raw; + ubsan_value_handle_t value = (ubsan_value_handle_t)value_raw; + (void)value; + ubsan_abort(&data->location, "function type mismatch"); +} + +ABORT_VARIANT_VP_VP(function_type_mismatch); + +struct ubsan_nonnull_return_data { + struct ubsan_source_location location; + struct ubsan_source_location attr_location; +}; + +void __ubsan_handle_nonnull_return(void *data_raw) +{ + const struct ubsan_nonnull_return_data *data = + (struct ubsan_nonnull_return_data *)data_raw; + ubsan_abort(&data->location, "null return"); +} + +ABORT_VARIANT_VP(nonnull_return); + +struct ubsan_nonnull_arg_data { + struct ubsan_source_location location; + struct ubsan_source_location attr_location; +}; + +/* +* TODO: GCC's libubsan does not have the second parameter, but its builtin +* somehow has it and conflict if we don't match it. +*/ +void __ubsan_handle_nonnull_arg(void *data_raw, intptr_t index_raw) +{ + const struct ubsan_nonnull_arg_data *data = + (struct ubsan_nonnull_arg_data *)data_raw; + ubsan_value_handle_t index = (ubsan_value_handle_t)index_raw; + (void)index; + ubsan_abort(&data->location, "null argument"); +} + +ABORT_VARIANT_VP_IP(nonnull_arg); + +struct ubsan_cfi_bad_icall_data { + struct ubsan_source_location location; + struct ubsan_type_descriptor *type; +}; + +void __ubsan_handle_cfi_bad_icall(void *data_raw, void *value_raw) +{ + static const char *abort_text + = "cfi: integrity failure during indirect call."; + const struct ubsan_cfi_bad_icall_data *data = + (struct ubsan_cfi_bad_icall_data *)data_raw; + ubsan_value_handle_t value = (ubsan_value_handle_t)value_raw; + (void)value; + ubsan_abort(&data->location, abort_text); +} + +ABORT_VARIANT_VP_VP(cfi_bad_icall); |