blob: ce8a0ba73bd458ed2a6b104941e2d5d689d7c982 [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/boot-options/boot-options.h>
#include <lib/console.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <ktl/algorithm.h>
#include <ktl/iterator.h>
#include <ktl/span.h>
#include <ktl/string_view.h>
#include <ktl/enforce.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 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 invalid_builtin_clz() {
int zero = Launder(0);
printf("__builtin_clz(0)\n");
int result = __builtin_clz(zero);
printf("result: %d\n", result);
}
void invalid_builtin_ctz() {
int zero = Launder(0);
printf("__builtin_ctz(0)\n");
int result = __builtin_ctz(zero);
printf("result: %d\n", result);
}
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);
}
[[gnu::returns_nonnull]] void* nonnull_return_helper() { return Launder<void*>(nullptr); }
void nonnull_return() {
printf("function declared [[gnu::returns_nonnull]] returns nullptr\n");
printf("result: %p\n", nonnull_return_helper());
}
void* _Nonnull nullability_return_helper() { return Launder<void*>(nullptr); }
void nullability_return() {
printf("function declared `T* _Nonnull` returns nullptr\n");
printf("result: %p\n", nullability_return_helper());
}
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 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");
// There is no version of unreachable code that can be recovered from,
// because the compiler will always treat it as a "noreturn" path and omit
// the epilogue of the function entirely.
__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);
}
struct UndefinedBehaviorCommand {
ktl::string_view name;
void (*func)();
const char* description;
bool cannot_continue;
};
constexpr UndefinedBehaviorCommand kUbCommands[] = {
{"all", nullptr, "run each subcommand in turn (requires kernel.ubsan.panic=false)"},
{"array_oob", &array_oob, "array out of bounds access"},
{"invalid_builtin_clz", &invalid_builtin_clz, "call __builtin_clz with 0"},
{"invalid_builtin_ctz", &invalid_builtin_ctz, "call __builtin_ctz with 0"},
{"misaligned_ptr", &misaligned_ptr, "use a misaligned pointer"},
{"nonnull_return", &nonnull_return, "return nullptr from returns_nonnull function"},
{"nullability_return", &nullability_return, "return nullptr from _Nonnull function"},
{"overflow_ptr", &overflow_ptr, "pointer arithmetic that overflows"},
{"overflow_signed_int_add", &overflow_signed_int_add, "signed integer addition that overflows"},
{"overflow_signed_int_shift", &overflow_signed_int_shift,
"signed integer shift that overflows"},
{"unaligned_assumption", &unaligned_assumption, "make a wrong alignment assumption"},
{"undefined_enum", &undefined_enum, "use an undefined value in a enum"},
{"undefined_bool", &undefined_bool, "use a bool that is not true nor false"},
{"unreachable", &unreachable, "execute unreachable code.", true},
};
constexpr size_t kMaxCommandNameSize = [] {
size_t size = 0;
for (const auto& ub_cmd : kUbCommands) {
size = ktl::max(size, ub_cmd.name.size());
}
return size;
}();
int cmd_usage(const char* cmd_name) {
printf("usage:\n");
for (const auto& ub_cmd : kUbCommands) {
printf("%s %-*s : %s\n", cmd_name, static_cast<int>(kMaxCommandNameSize), ub_cmd.name.data(),
ub_cmd.description);
}
return ZX_ERR_INTERNAL;
}
int cmd_ub(int argc, const cmd_args* argv, uint32_t flags) {
const char* name = argv[0].str;
if (argc != 2) {
printf("Exactly one argument required.\n");
return cmd_usage(name);
}
ktl::string_view subcommand = argv[1].str;
for (const auto& ub_cmd : kUbCommands) {
if (ub_cmd.name == subcommand) {
if (ub_cmd.func) {
ub_cmd.func();
} else {
for (const auto& next_ub_cmd : ktl::span(kUbCommands).subspan(1)) {
if (gBootOptions->ubsan_action == CheckFail::kOops && next_ub_cmd.cannot_continue) {
printf("*** Skipping `ub %s`, which cannot avoid panic ***\n", next_ub_cmd.name.data());
} else {
printf("*** ub %s\n", next_ub_cmd.name.data());
next_ub_cmd.func();
}
}
}
return 0;
}
}
return cmd_usage(name);
}
STATIC_COMMAND_START
STATIC_COMMAND("ub", "trigger undefined behavior", &cmd_ub)
STATIC_COMMAND_END(mem)
} // namespace
#endif // __has_feature(undefined_behavior_sanitizer)