serenity/Userland/crash.cpp
Andreas Kling 94ca55cefd Meta: Add license header to source files
As suggested by Joshua, this commit adds the 2-clause BSD license as a
comment block to the top of every source file.

For the first pass, I've just added myself for simplicity. I encourage
everyone to add themselves as copyright holders of any file they've
added or modified in some significant way. If I've added myself in
error somewhere, feel free to replace it with the appropriate copyright
holder instead.

Going forward, all new source files should include a license header.
2020-01-18 09:45:54 +01:00

383 lines
13 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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;
}