blob: 7004fba15809b733ebe7c0ae903dc93cdf034e79 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
// Undefined Behavior commands.
// These commands can be used to test the undefined behavior sanitizer.
// A kernel compiled with the `kubsan` variant should be able to detect
// each of them.
//
// Most of the functions use inline assembly as an attempt to avoid the compiler
// from optimizing the operations away or throwing warnings.
#include <inttypes.h>
#include <lib/console.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#if __has_feature(undefined_behavior_sanitizer)
namespace {
// The compiler cannot assume it knows the return value of Launder.
template <typename T>
T Launder(T x) {
__asm__("" : "=r"(x) : "0"(x));
return x;
}
void overflow_signed_int_add() {
// Signed integer overflow, where the result of a signed integer computation
// cannot be represented in its type.
int32_t x = Launder(INT32_MAX);
int32_t y = Launder(1);
printf("integer overflow: %d + %d\n", x, y);
int32_t res = x + y;
printf("result: %d\n", res);
}
void overflow_signed_int_shift() {
// Shift operators where the amount shifted is greater or equal to the
// promoted bit-width of the left hand side or less than zero, or where the
// left hand side is negative.
int64_t big_val = Launder(0x1000000);
size_t shift = Launder(50);
printf("shift overflowed: %" PRId64 " << %zu\n", big_val, shift);
int64_t res = big_val << shift;
printf("result: %" PRId64 "\n", res);
}
void overflow_ptr() {
// Performing pointer arithmetic which overflows, or where either the old or
// new pointer value is a null pointer (or in C, when they both are).
uint8_t local_variable = 0x01;
uint8_t* ptr = &local_variable;
size_t overflower = Launder(UINT64_MAX);
printf("pointer overflow: %p + 0x%zx\n", ptr, overflower);
uint8_t* newptr = ptr + overflower;
printf("result: %p\n", newptr);
}
void misaligned_ptr() {
// Use of a misaligned pointer or creation of a misaligned reference.
uint64_t aligned = 0;
uint32_t* addr = reinterpret_cast<uint32_t*>(Launder(reinterpret_cast<uintptr_t>(&aligned)) + 1);
printf("misaligned pointer access: *%p\n", addr);
uint32_t val = *addr;
printf("result: %x\n", val);
}
void unaligned_assumption() {
// Make a false alignment assumption on a pointer.
uint64_t aligned = 0;
uint32_t* addr = reinterpret_cast<uint32_t*>(Launder(reinterpret_cast<uintptr_t>(&aligned)) + 1);
printf("assuming that %p is aligned to 256 bytes.\n", addr);
uint32_t* __attribute__((align_value(256))) p = addr;
printf("p: %x\n", *p);
}
void array_oob() {
// Out of bounds array indexing, in cases where the array bound can be
// statically determined
uint32_t buf[] = {0, 1, 2};
size_t index = Launder(3);
printf("array read out of bounds: buf[%zu]\n", index);
uint32_t val = buf[index];
printf("result: %u\n", val);
}
void undefined_bool() {
// Load of a bool value that is neither true nor false.
static_assert(sizeof(uint64_t) >= sizeof(bool), "bool is larger than uint64_t");
uint64_t garbage = Launder(uint64_t{0xdeadbeef});
printf("loading a bool with value: %" PRIu64 "\n", garbage);
bool val;
memcpy(&val, &garbage, sizeof(val));
bool b = val;
uint64_t res = 0;
memcpy(&res, &b, sizeof(b));
printf("load of bad bool value: %" PRIu64 "\n", res);
}
void unreachable() {
// Execute unreachable code.
printf("About to execute unreachable code\n");
__builtin_unreachable();
}
void undefined_enum() {
// Load of a value of an enumerated type which is not in the range of
// representable values for that enumerated type.
enum Stuff : uint8_t { Foo, Bar, Baz };
uint32_t garbage = Launder(0xdeadbeef);
printf("loading an enum with value: %" PRIu32 "\n", garbage);
Stuff val;
memcpy(&val, &garbage, sizeof(val));
Stuff b = val;
printf("load of invalid enum value: %d\n", b);
}
} // namespace
static int cmd_ub(int argc, const cmd_args* argv, uint32_t flags);
STATIC_COMMAND_START
STATIC_COMMAND("ub", "trigger undefined behavior", &cmd_ub)
STATIC_COMMAND_END(mem)
static int cmd_usage(const char* cmd_name) {
printf("usage:\n");
printf("%s array_oob : array out of bounds access\n", cmd_name);
printf("%s misaligned_ptr : use a misaligned pointer\n", cmd_name);
printf("%s overflow_ptr : pointer arithmetic that overflows\n", cmd_name);
printf("%s overflow_signed_int_add : signed integer addition that overflows\n", cmd_name);
printf("%s overflow_signed_int_shift: signed integer shift that overflows\n", cmd_name);
printf("%s unaligned_assumption : make a wrong alignment assumption\n", cmd_name);
printf("%s undefined_enum : use an undefined value in a enum\n", cmd_name);
printf("%s undefined_bool : use a bool that is not true nor false\n", cmd_name);
printf("%s unreachable : execute unreachable code.\n", cmd_name);
return ZX_ERR_INTERNAL;
}
static int cmd_ub(int argc, const cmd_args* argv, uint32_t flags) {
const char* name = argv[0].str;
if (argc < 2) {
printf("Not enough arguments.\n");
return cmd_usage(name);
}
if (!strcmp(argv[1].str, "array_oob")) {
array_oob();
} else if (!strcmp(argv[1].str, "misaligned_ptr")) {
misaligned_ptr();
} else if (!strcmp(argv[1].str, "overflow_ptr")) {
overflow_ptr();
} else if (!strcmp(argv[1].str, "overflow_signed_int_add")) {
overflow_signed_int_add();
} else if (!strcmp(argv[1].str, "overflow_signed_int_shift")) {
overflow_signed_int_shift();
} else if (!strcmp(argv[1].str, "unaligned_assumption")) {
unaligned_assumption();
} else if (!strcmp(argv[1].str, "undefined_enum")) {
undefined_enum();
} else if (!strcmp(argv[1].str, "undefined_bool")) {
undefined_bool();
} else if (!strcmp(argv[1].str, "unreachable")) {
unreachable();
} else if (!strcmp(argv[1].str, "help")) {
return cmd_usage(name);
}
return 0;
}
#endif // __has_feature(undefined_behavior_sanitizer)