blob: 9e2ea529aea65735ee67172dda95c725475f505e [file]
/*
* 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 <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <trusty/string.h>
/*
* 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 <kernel/thread.h>
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, "<floating point value>");
} else {
scnprintf(out, out_size, "<value with unknown type>");
}
}
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;
}