/* * Copyright (c) 2019 Google Inc. All rights reserved * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ubsan.h" #include #include #include #include #include #include #include #include /* * in_ubsan_* is used to track whether we are currently processing a UBSan * report. This is useful information so that if UBSan gets tripped again * (due to e.g. a bug in printf, or the logging code) we don't sit in an * infinite recursion trying to report the bug over and over. */ static bool in_ubsan_get(void); static void in_ubsan_set(bool); #ifdef TRUSTY_USERSPACE /* TODO Once TLS is available, make this __thread */ static bool in_ubsan = false; static inline bool in_ubsan_get(void) { return in_ubsan; } static inline void in_ubsan_set(bool val) { in_ubsan = val; } #else /* * Single copy of in_ubsan for when we don't have a current thread, e.g., * during early boot */ static bool in_ubsan_early = false; #include static inline bool in_ubsan_get(void) { thread_t* curr = get_current_thread(); return curr ? thread_tls_get(curr, TLS_ENTRY_UBSAN) : in_ubsan_early; } static inline void in_ubsan_set(bool val) { thread_t* curr = get_current_thread(); if (curr) { thread_tls_set(curr, TLS_ENTRY_UBSAN, val); } else { in_ubsan_early = val; } } #endif #define VALUE_RENDER_SIZE 64 #define DETAIL_RENDER_SIZE 1024 static int64_t val_signed(const struct type_descriptor* type, value_handle_t val) { if (type_is_inline(type)) { /* Sign extend if smaller than ssize_t-bits */ size_t undefined_bits = sizeof(size_t) * 8 - type_width_bits(type); val <<= undefined_bits; ssize_t out = (ssize_t)val; out >>= undefined_bits; return out; } else { /* * This truncates, but we don't have a good way to deal with * ints larger than 64 bits and it at least gets *some* data. * * For values larger than 64 bits, this will also have the wrong * sign. */ return *(int64_t*)val; } } static uint64_t val_unsigned(const struct type_descriptor* type, value_handle_t val) { if (type_is_inline(type)) { return (uint64_t)val; } else { /* This truncates, but gets some data out */ return *(uint64_t*)val; } } static void render_val(char* out, size_t out_size, const struct type_descriptor* type, value_handle_t val) { size_t width = type_width_bits(type); if (type_is_signed_integer(type)) { if (width > sizeof(int64_t) * 8) { size_t warn_len = scnprintf(out, out_size, "~int%zu_t->int64_t:truncated ", width); out += warn_len; out_size -= warn_len; } scnprintf(out, out_size, "%" PRId64, val_signed(type, val)); } else if (type_is_unsigned_integer(type)) { if (width > sizeof(uint64_t) * 8) { size_t warn_len = scnprintf( out, out_size, "~uint%zu_t->uint64_t:truncated ", width); out += warn_len; out_size -= warn_len; } scnprintf(out, out_size, "%" PRIu64, val_unsigned(type, val)); } else if (type_is_float(type)) { /* * Printing floating point correctly requires a more powerful printf * which may not be available, and printing large floats will pull in * softfloat support. * Since it is unlikely the exact value of a float triggering a * sanitizer will be important, we don't format it. */ scnprintf(out, out_size, ""); } else { scnprintf(out, out_size, ""); } } static void log(struct source_location* location, const char* kind, const char* details) { fprintf(stderr, "UBSan: (%s) %s:%d:%d\nDetails: %s\n", kind, location->filename, location->line, location->column, details); } static void ubsan_fail(const char* msg) { #ifdef TRUSTY_USERSPACE fprintf(stderr, "ubsan panic: %s\n", msg); *(volatile char*)0 = 0; #else panic("%s\n", msg); #endif } static bool start(void) { if (in_ubsan_get()) { return false; } in_ubsan_set(true); return true; } static void finish(void) { assert(in_ubsan_get()); ubsan_fail("UBSan violation"); } /* * UBSAN_START should be used at the beginning of each ubsan handler. * It will abort if we are already processing a UBSan report, and set the * flag if we are. */ #define UBSAN_START \ if (!start()) { \ return; \ } /* * UBSAN_FINISH should be used at the end of each ubsan handler. * It will mark us as having left the handler, and terminate due to the error * report. */ #define UBSAN_FINISH finish(); static void handle_overflow(struct overflow_data* data, value_handle_t lhs, value_handle_t rhs, const char* op) { UBSAN_START; char rendered_lhs[VALUE_RENDER_SIZE]; char rendered_rhs[VALUE_RENDER_SIZE]; char details[DETAIL_RENDER_SIZE]; char overflow_kind[16]; scnprintf(overflow_kind, sizeof(overflow_kind), "overflow:%s", op); const struct type_descriptor* type = data->type; render_val(rendered_lhs, sizeof(rendered_lhs), type, lhs); render_val(rendered_rhs, sizeof(rendered_rhs), type, rhs); scnprintf(details, sizeof(details), "%s integer overflow: %s %s %s cannot be represented in type" " %s\n", type_is_signed_integer(type) ? "signed" : "unsigned", rendered_lhs, op, rendered_rhs, type->name); log(&data->loc, overflow_kind, details); UBSAN_FINISH; } UBSAN_HANDLER(add_overflow, struct overflow_data* data, value_handle_t lhs, value_handle_t rhs) { handle_overflow(data, lhs, rhs, "+"); } UBSAN_HANDLER(sub_overflow, struct overflow_data* data, value_handle_t lhs, value_handle_t rhs) { handle_overflow(data, lhs, rhs, "-"); } UBSAN_HANDLER(mul_overflow, struct overflow_data* data, value_handle_t lhs, value_handle_t rhs) { handle_overflow(data, lhs, rhs, "*"); } UBSAN_HANDLER(divrem_overflow, struct overflow_data* data, value_handle_t lhs, value_handle_t rhs) { handle_overflow(data, lhs, rhs, "/%"); } UBSAN_HANDLER(negate_overflow, struct overflow_data* data, value_handle_t val) { UBSAN_START; char rendered_val[VALUE_RENDER_SIZE]; char details[DETAIL_RENDER_SIZE]; render_val(rendered_val, sizeof(rendered_val), data->type, val); scnprintf(details, sizeof(details), "negation of %s cannot be represented in type %s", rendered_val, data->type->name); log(&data->loc, "negation overflow", details); UBSAN_FINISH; } UBSAN_HANDLER(pointer_overflow, struct pointer_overflow_data* data, uintptr_t base, uintptr_t result) { UBSAN_START; char details[DETAIL_RENDER_SIZE]; snprintf_filtered(details, sizeof(details), "pointer arithmetic on %p overflowed resulting in %p", (void*)base, (void*)result); log(&data->loc, "pointer_overflow", details); UBSAN_FINISH; } UBSAN_HANDLER(implicit_conversion, struct implicit_conversion_data* data, value_handle_t src, value_handle_t dst) { UBSAN_START; char rendered_src[VALUE_RENDER_SIZE]; char rendered_dst[VALUE_RENDER_SIZE]; char details[DETAIL_RENDER_SIZE]; const char* kind_str; if (data->check_kind < sizeof(implicit_conversion_check_kinds) / sizeof(const char*)) { kind_str = implicit_conversion_check_kinds[data->check_kind]; } else { kind_str = "unknown"; } render_val(rendered_src, sizeof(rendered_src), data->from_type, src); render_val(rendered_dst, sizeof(rendered_dst), data->to_type, dst); scnprintf(details, sizeof(details), "implicit conversion (%s) from %s to %s\n", kind_str, rendered_src, rendered_dst); log(&data->loc, "implicit conversion", details); UBSAN_FINISH; } UBSAN_HANDLER(invalid_builtin, struct invalid_builtin_data* data) { UBSAN_START; const char* details; switch (data->check_kind) { case BCK_CTZ_PASSED_ZERO: details = "zero passed to ctz"; break; case BCK_CLZ_PASSED_ZERO: details = "zero passed to clz"; break; default: details = "unknown builtin misuse kind"; } log(&data->loc, "invalid builtin usage", details); UBSAN_FINISH; } UBSAN_HANDLER(type_mismatch_v1, struct type_mismatch_data* data, value_handle_t ptr) { UBSAN_START; char details[DETAIL_RENDER_SIZE]; intptr_t alignment = 1 << data->log_alignment; if (!ptr) { scnprintf(details, sizeof(details), "%s null pointer type %s", type_check_kinds[data->type_check_kind], data->type->name); } else if (ptr & (alignment - 1)) { snprintf_filtered( details, sizeof(details), "%s misaligned pointer %p for type %s which requires %d byte alignment", type_check_kinds[data->type_check_kind], (void*)ptr, data->type->name, (int)alignment); } else { snprintf_filtered( details, sizeof(details), "%s pointer %p points at a region with insufficient space for a value of type %s", type_check_kinds[data->type_check_kind], (void*)ptr, data->type->name); } log(&data->loc, "type mismatch", details); UBSAN_FINISH; } UBSAN_HANDLER(builtin_unreachable, struct unreachable_data* data) { UBSAN_START; log(&data->loc, "hit a supposedly unreachable point", ""); UBSAN_FINISH; ubsan_fail("executing through unreachable would be unwise"); } UBSAN_HANDLER(missing_return, struct unreachable_data* data) { UBSAN_START; log(&data->loc, "hit a missing return statement", ""); UBSAN_FINISH; ubsan_fail("executing past the end of a function would be unwise"); } static bool is_negative(const struct type_descriptor* type, value_handle_t val) { if (type_is_signed_integer(type)) { return val_signed(type, val) < 0; } return false; } UBSAN_HANDLER(shift_out_of_bounds, struct shift_out_of_bounds_data* data, value_handle_t lhs, value_handle_t rhs) { UBSAN_START; char rendered_lhs[VALUE_RENDER_SIZE]; char rendered_rhs[VALUE_RENDER_SIZE]; char details[DETAIL_RENDER_SIZE]; render_val(rendered_lhs, sizeof(rendered_lhs), data->lhs_type, lhs); render_val(rendered_rhs, sizeof(rendered_rhs), data->rhs_type, rhs); uint64_t rhs_u64 = val_unsigned(data->rhs_type, rhs); if (is_negative(data->rhs_type, rhs)) { scnprintf(details, sizeof(details), "shift amount is negative: %s", rendered_rhs); } else if (type_width_bits(data->lhs_type) < rhs_u64) { scnprintf(details, sizeof(details), "shift amount %s is too large for type %s", rendered_rhs, data->lhs_type->name); } else if (is_negative(data->lhs_type, lhs)) { /* At this point, we know we are dealing with a left shift, as right * shift is covered by the above two cases */ scnprintf(details, sizeof(details), "left shifting a negative value: %s", rendered_lhs); } else { scnprintf(details, sizeof(details), "%s << %s does not fit in %s", rendered_lhs, rendered_rhs, data->lhs_type->name); } log(&data->loc, "shift out of bounds", details); UBSAN_FINISH; } UBSAN_HANDLER(out_of_bounds, struct out_of_bounds_data* data, value_handle_t index) { UBSAN_START; char rendered_index[VALUE_RENDER_SIZE]; char details[DETAIL_RENDER_SIZE]; render_val(rendered_index, sizeof(rendered_index), data->index_type, index); scnprintf(details, sizeof(details), "index %s out of bounds for %s\n", rendered_index, data->array_type->name); log(&data->loc, "out of bounds access", details); UBSAN_FINISH; } UBSAN_HANDLER(load_invalid_value, struct invalid_value_data* data, value_handle_t val) { UBSAN_START; char rendered_val[VALUE_RENDER_SIZE]; char details[DETAIL_RENDER_SIZE]; render_val(rendered_val, sizeof(rendered_val), data->type, val); scnprintf(details, sizeof(details), "load of value %s outside of range for type %s", rendered_val, data->type->name); log(&data->loc, "invalid value", details); UBSAN_FINISH; } UBSAN_HANDLER(float_cast_overflow, struct float_cast_overflow_data* data, value_handle_t val) { UBSAN_START; /* Since we aren't rendering floats, there's not much point in details */ log(&data->loc, "float cast overflow", ""); UBSAN_FINISH; } UBSAN_HANDLER(cfi_check_fail_abort, struct cfi_check_fail_data* data, value_handle_t val, uintptr_t vtable_is_valid) { UBSAN_START; char rendered_val[VALUE_RENDER_SIZE]; char details[DETAIL_RENDER_SIZE]; render_val(rendered_val, sizeof(rendered_val), data->type, val); scnprintf(details, sizeof(details), "type of the value: %s type name: %s", rendered_val, data->type->name); log(&data->loc, "cfi check fail abort", details); UBSAN_FINISH; } UBSAN_HANDLER(vla_bound_not_positive, struct vla_bound_data* data, value_handle_t val) { UBSAN_START; char rendered_val[VALUE_RENDER_SIZE]; char details[DETAIL_RENDER_SIZE]; render_val(rendered_val, sizeof(rendered_val), data->type, val); scnprintf(details, sizeof(details), "bound %s is not positive for type %s", rendered_val, data->type->name); log(&data->loc, "variable length array bound evaluates to non-positive value", details); UBSAN_FINISH; } UBSAN_HANDLER(alignment_assumption, struct alignment_assumption_data* data, value_handle_t pointer, value_handle_t alignment, value_handle_t offset) { UBSAN_START; char details[DETAIL_RENDER_SIZE]; if (location_is_valid(data->assumption_loc)) { log(&data->assumption_loc, "alignment assumption was specified here", /* Details: */ "below"); } if (!offset) { scnprintf(details, sizeof(details), "assumption of %" PRIuPTR " byte alignment for pointer of type %s failed", alignment, data->type->name); } else { scnprintf(details, sizeof(details), "assumption of %" PRIuPTR " byte alignment (with offset of %" PRIuPTR " bytes) for pointer of type %s failed", alignment, offset, data->type->name); } log(&data->loc, "alignment assumption is incorrect", details); UBSAN_FINISH; }