ladybird/Userland/crash.cpp
Andreas Kling 38f93ef13b Kernel: Disable x86 RDTSC instruction in userspace
It's still possible to read the TSC via the read_tsc() syscall, but we
will now clear some of the bottom bits for unprivileged users.
2020-01-01 18:22:20 +01:00

357 lines
12 KiB
C++

#include <AK/Function.h>
#include <AK/String.h>
#include <Kernel/IO.h>
#include <Kernel/Syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
#pragma GCC optimize("O0")
static void print_usage_and_exit()
{
printf("usage: crash -[AsdiamfMFTtSxyXUIc]\n");
exit(0);
}
class Crash {
public:
enum class RunType {
UsingChildProcess,
UsingCurrentProcess,
};
enum class Failure {
DidNotCrash,
UnexpectedError,
};
Crash(String test_type, Function<Crash::Failure()> crash_function)
: m_type(test_type)
, m_crash_function(move(crash_function))
{
}
void run(RunType run_type)
{
printf("\x1B[33mTesting\x1B[0m: \"%s\"\n", m_type.characters());
auto run_crash_and_print_if_error = [this]() {
auto failure = m_crash_function();
// If we got here something went wrong
printf("\x1B[31mFAIL\x1B[0m: ");
switch (failure) {
case Failure::DidNotCrash:
printf("Did not crash!\n");
break;
case Failure::UnexpectedError:
printf("Unexpected error!\n");
break;
default:
ASSERT_NOT_REACHED();
}
};
if (run_type == RunType::UsingCurrentProcess) {
run_crash_and_print_if_error();
} else {
// Run the test in a child process so that we do not crash the crash program :^)
pid_t pid = fork();
if (pid < 0) {
perror("fork");
ASSERT_NOT_REACHED();
} else if (pid == 0) {
run_crash_and_print_if_error();
exit(0);
}
int status;
waitpid(pid, &status, 0);
if (WIFSIGNALED(status))
printf("\x1B[32mPASS\x1B[0m: Terminated with signal %d\n", WTERMSIG(status));
}
}
private:
String m_type;
Function<Crash::Failure()> m_crash_function;
};
int main(int argc, char** argv)
{
enum Mode {
TestAllCrashTypes,
SegmentationViolation,
DivisionByZero,
IllegalInstruction,
Abort,
WriteToUninitializedMallocMemory,
WriteToFreedMemory,
ReadFromUninitializedMallocMemory,
ReadFromFreedMemory,
WriteToReadonlyMemory,
InvalidStackPointerOnSyscall,
InvalidStackPointerOnPageFault,
SyscallFromWritableMemory,
WriteToFreedMemoryStillCachedByMalloc,
ReadFromFreedMemoryStillCachedByMalloc,
ExecuteNonExecutableMemory,
TriggerUserModeInstructionPrevention,
UseIOInstruction,
ReadTimestampCounter,
};
Mode mode = SegmentationViolation;
if (argc != 2)
print_usage_and_exit();
if (String(argv[1]) == "-A")
mode = TestAllCrashTypes;
else if (String(argv[1]) == "-s")
mode = SegmentationViolation;
else if (String(argv[1]) == "-d")
mode = DivisionByZero;
else if (String(argv[1]) == "-i")
mode = IllegalInstruction;
else if (String(argv[1]) == "-a")
mode = Abort;
else if (String(argv[1]) == "-m")
mode = ReadFromUninitializedMallocMemory;
else if (String(argv[1]) == "-f")
mode = ReadFromFreedMemory;
else if (String(argv[1]) == "-M")
mode = WriteToUninitializedMallocMemory;
else if (String(argv[1]) == "-F")
mode = WriteToFreedMemory;
else if (String(argv[1]) == "-r")
mode = WriteToReadonlyMemory;
else if (String(argv[1]) == "-T")
mode = InvalidStackPointerOnSyscall;
else if (String(argv[1]) == "-t")
mode = InvalidStackPointerOnPageFault;
else if (String(argv[1]) == "-S")
mode = SyscallFromWritableMemory;
else if (String(argv[1]) == "-x")
mode = ReadFromFreedMemoryStillCachedByMalloc;
else if (String(argv[1]) == "-y")
mode = WriteToFreedMemoryStillCachedByMalloc;
else if (String(argv[1]) == "-X")
mode = ExecuteNonExecutableMemory;
else if (String(argv[1]) == "-U")
mode = TriggerUserModeInstructionPrevention;
else if (String(argv[1]) == "-I")
mode = UseIOInstruction;
else if (String(argv[1]) == "-c")
mode = ReadTimestampCounter;
else
print_usage_and_exit();
Crash::RunType run_type = mode == TestAllCrashTypes ? Crash::RunType::UsingChildProcess
: Crash::RunType::UsingCurrentProcess;
if (mode == SegmentationViolation || mode == TestAllCrashTypes) {
Crash("Segmentation violation", []() {
volatile int* crashme = nullptr;
*crashme = 0xbeef;
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == DivisionByZero || mode == TestAllCrashTypes) {
Crash("Division by zero", []() {
volatile int lala = 10;
volatile int zero = 0;
volatile int test = lala / zero;
UNUSED_PARAM(test);
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == IllegalInstruction || mode == TestAllCrashTypes) {
Crash("Illegal instruction", []() {
asm volatile("ud2");
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == Abort || mode == TestAllCrashTypes) {
Crash("Abort", []() {
abort();
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == ReadFromUninitializedMallocMemory || mode == TestAllCrashTypes) {
Crash("Read from uninitialized malloc memory", []() {
auto* uninitialized_memory = (volatile u32**)malloc(1024);
if (!uninitialized_memory)
return Crash::Failure::UnexpectedError;
volatile auto x = uninitialized_memory[0][0];
UNUSED_PARAM(x);
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == ReadFromFreedMemory || mode == TestAllCrashTypes) {
Crash("Read from freed memory", []() {
auto* uninitialized_memory = (volatile u32**)malloc(1024);
if (!uninitialized_memory)
return Crash::Failure::UnexpectedError;
free(uninitialized_memory);
volatile auto x = uninitialized_memory[4][0];
UNUSED_PARAM(x);
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == WriteToUninitializedMallocMemory || mode == TestAllCrashTypes) {
Crash("Write to uninitialized malloc memory", []() {
auto* uninitialized_memory = (volatile u32**)malloc(1024);
if (!uninitialized_memory)
return Crash::Failure::UnexpectedError;
uninitialized_memory[4][0] = 1;
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == WriteToFreedMemory || mode == TestAllCrashTypes) {
Crash("Write to freed memory", []() {
auto* uninitialized_memory = (volatile u32**)malloc(1024);
if (!uninitialized_memory)
return Crash::Failure::UnexpectedError;
free(uninitialized_memory);
uninitialized_memory[4][0] = 1;
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == WriteToReadonlyMemory || mode == TestAllCrashTypes) {
Crash("Write to read only memory", []() {
auto* ptr = (u8*)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANON, 0, 0);
if (ptr != MAP_FAILED)
return Crash::Failure::UnexpectedError;
*ptr = 'x'; // This should work fine.
int rc = mprotect(ptr, 4096, PROT_READ);
if (rc != 0 || *ptr != 'x')
return Crash::Failure::UnexpectedError;
*ptr = 'y'; // This should crash!
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == InvalidStackPointerOnSyscall || mode == TestAllCrashTypes) {
Crash("Invalid stack pointer on syscall", []() {
u8* makeshift_stack = (u8*)mmap(nullptr, 0, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK, 0, 0);
if (!makeshift_stack)
return Crash::Failure::UnexpectedError;
u8* makeshift_esp = makeshift_stack + 2048;
asm volatile("mov %%eax, %%esp" ::"a"(makeshift_esp));
getuid();
dbgprintf("Survived syscall with MAP_STACK stack\n");
u8* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (!bad_stack)
return Crash::Failure::UnexpectedError;
u8* bad_esp = bad_stack + 2048;
asm volatile("mov %%eax, %%esp" ::"a"(bad_esp));
getuid();
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == InvalidStackPointerOnPageFault || mode == TestAllCrashTypes) {
Crash("Invalid stack pointer on page fault", []() {
u8* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (!bad_stack)
return Crash::Failure::UnexpectedError;
u8* bad_esp = bad_stack + 2048;
asm volatile("mov %%eax, %%esp" ::"a"(bad_esp));
asm volatile("pushl $0");
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == SyscallFromWritableMemory || mode == TestAllCrashTypes) {
Crash("Syscall from writable memory", []() {
u8 buffer[] = { 0xb8, Syscall::SC_getuid, 0, 0, 0, 0xcd, 0x82 };
((void (*)())buffer)();
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == ReadFromFreedMemoryStillCachedByMalloc || mode == TestAllCrashTypes) {
Crash("Read from memory still cached by malloc", []() {
auto* ptr = (u8*)malloc(1024);
if (!ptr)
return Crash::Failure::UnexpectedError;
free(ptr);
dbgprintf("ptr = %p\n", ptr);
volatile auto foo = *ptr;
UNUSED_PARAM(foo);
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == WriteToFreedMemoryStillCachedByMalloc || mode == TestAllCrashTypes) {
Crash("Write to freed memory still cached by malloc", []() {
auto* ptr = (u8*)malloc(1024);
if (!ptr)
return Crash::Failure::UnexpectedError;
free(ptr);
dbgprintf("ptr = %p\n", ptr);
*ptr = 'x';
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == ExecuteNonExecutableMemory || mode == TestAllCrashTypes) {
Crash("Execute non executable memory", []() {
auto* ptr = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (ptr == MAP_FAILED)
return Crash::Failure::UnexpectedError;
ptr[0] = 0xc3; // ret
typedef void* (*CrashyFunctionPtr)();
((CrashyFunctionPtr)ptr)();
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == TriggerUserModeInstructionPrevention || mode == TestAllCrashTypes) {
Crash("Trigger x86 User Mode Instruction Prevention", []() {
asm volatile("str %eax");
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == UseIOInstruction || mode == TestAllCrashTypes) {
Crash("Attempt to use an I/O instruction", [] {
u8 keyboard_status = IO::in8(0x64);
printf("Keyboard status: %#02x\n", keyboard_status);
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
if (mode == ReadTimestampCounter || mode == TestAllCrashTypes) {
Crash("Read the CPU timestamp counter", [] {
asm volatile("rdtsc");
return Crash::Failure::DidNotCrash;
}).run(run_type);
}
return 0;
}