diff --git a/Base/usr/share/man/man1/crash.md b/Base/usr/share/man/man1/crash.md index 174295c2a8..be4cc17597 100644 --- a/Base/usr/share/man/man1/crash.md +++ b/Base/usr/share/man/man1/crash.md @@ -16,6 +16,7 @@ kinds of crashes. ## Options +* `-A`: Test that all of the following crashes crash as intended. * `-s`: Perform a segmentation violation by dereferencing an invalid pointer. * `-d`: Perform a division by zero. * `-i`: Execute an illegal CPU instruction. @@ -36,5 +37,6 @@ kinds of crashes. ```sh $ crash -F +Testing: "Write to freed memory" Shell: crash(33) exitied due to signal "Segmentation violation" ``` diff --git a/Userland/crash.cpp b/Userland/crash.cpp index 48445ae70e..09c8554260 100644 --- a/Userland/crash.cpp +++ b/Userland/crash.cpp @@ -1,19 +1,88 @@ +#include #include #include #include #include #include +#include + +#pragma GCC optimize("O0") static void print_usage_and_exit() { - printf("usage: crash -[sdiamfMFTtSxyX]\n"); + printf("usage: crash -[AsdiamfMFTtSxyX]\n"); exit(0); } -#pragma GCC optimize("O0") +class Crash { +public: + enum class RunType { + UsingChildProcess, + UsingCurrentProcess, + }; + + enum class Failure { + DidNotCrash, + UnexpectedError, + }; + + Crash(String test_type, Function 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 m_crash_function; +}; + int main(int argc, char** argv) { enum Mode { + TestAllCrashTypes, SegmentationViolation, DivisionByZero, IllegalInstruction, @@ -35,7 +104,9 @@ int main(int argc, char** argv) if (argc != 2) print_usage_and_exit(); - if (String(argv[1]) == "-s") + if (String(argv[1]) == "-A") + mode = TestAllCrashTypes; + else if (String(argv[1]) == "-s") mode = SegmentationViolation; else if (String(argv[1]) == "-d") mode = DivisionByZero; @@ -68,137 +139,187 @@ int main(int argc, char** argv) else print_usage_and_exit(); - if (mode == SegmentationViolation) { - volatile int* crashme = nullptr; - *crashme = 0xbeef; - ASSERT_NOT_REACHED(); + 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) { - volatile int lala = 10; - volatile int zero = 0; - volatile int test = lala / zero; - (void)test; - ASSERT_NOT_REACHED(); + 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) { - asm volatile("ud2"); - ASSERT_NOT_REACHED(); + if (mode == IllegalInstruction || mode == TestAllCrashTypes) { + Crash("Illegal instruction", []() { + asm volatile("ud2"); + return Crash::Failure::DidNotCrash; + }).run(run_type); } - if (mode == Abort) { - abort(); - ASSERT_NOT_REACHED(); + if (mode == Abort || mode == TestAllCrashTypes) { + Crash("Abort", []() { + abort(); + return Crash::Failure::DidNotCrash; + }).run(run_type); } - if (mode == ReadFromUninitializedMallocMemory) { - auto* uninitialized_memory = (volatile u32**)malloc(1024); - volatile auto x = uninitialized_memory[0][0]; - (void)x; - ASSERT_NOT_REACHED(); + 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) { - auto* uninitialized_memory = (volatile u32**)malloc(1024); - free(uninitialized_memory); - volatile auto x = uninitialized_memory[4][0]; - (void)x; - ASSERT_NOT_REACHED(); + if (mode == ReadFromFreedMemory || mode == TestAllCrashTypes) { + Crash("Read from freed memory", []() { + auto* uninitialized_memory = (volatile u32**)malloc(1024); + if (true) + 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) { - auto* uninitialized_memory = (volatile u32**)malloc(1024); - uninitialized_memory[4][0] = 1; - ASSERT_NOT_REACHED(); + 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) { - auto* uninitialized_memory = (volatile u32**)malloc(1024); - free(uninitialized_memory); - uninitialized_memory[4][0] = 1; - ASSERT_NOT_REACHED(); + 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) { - auto* ptr = (u8*)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANON, 0, 0); - ASSERT(ptr != MAP_FAILED); - *ptr = 'x'; // This should work fine. - int rc = mprotect(ptr, 4096, PROT_READ); - ASSERT(rc == 0); - ASSERT(*ptr == 'x'); - *ptr = 'y'; // This should crash! + 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) { - u8* makeshift_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK, 0, 0); - if (!makeshift_stack) { - perror("mmap"); - return 1; - } - u8* makeshift_esp = makeshift_stack + 2048; - asm volatile("mov %%eax, %%esp" :: "a"(makeshift_esp)); - getuid(); - dbgprintf("Survived syscall with MAP_STACK stack\n"); + 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* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - if (!bad_stack) { - perror("mmap"); - return 1; - } - u8* bad_esp = bad_stack + 2048; - asm volatile("mov %%eax, %%esp" :: "a"(bad_esp)); - getuid(); + u8* makeshift_esp = makeshift_stack + 2048; + asm volatile("mov %%eax, %%esp" ::"a"(makeshift_esp)); + getuid(); + dbgprintf("Survived syscall with MAP_STACK stack\n"); - ASSERT_NOT_REACHED(); + 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) { - u8* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - if (!bad_stack) { - perror("mmap"); - return 1; - } - u8* bad_esp = bad_stack + 2048; - asm volatile("mov %%eax, %%esp" :: "a"(bad_esp)); - asm volatile("pushl $0"); - ASSERT_NOT_REACHED(); + 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) { - u8 buffer[] = { 0xb8, Syscall::SC_getuid, 0, 0, 0, 0xcd, 0x82 }; - ((void(*)())buffer)(); + 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) { - auto* ptr = (u8*)malloc(1024); - free(ptr); - dbgprintf("ptr = %p\n", ptr); - volatile auto foo = *ptr; - (void)foo; - ASSERT_NOT_REACHED(); + 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) { - auto* ptr = (u8*)malloc(1024); - free(ptr); - dbgprintf("ptr = %p\n", ptr); - *ptr = 'x'; - ASSERT_NOT_REACHED(); + 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) { - auto* ptr = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - ASSERT(ptr != MAP_FAILED); + 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)(); - - ASSERT_NOT_REACHED(); + ptr[0] = 0xc3; // ret + typedef void* (*CrashyFunctionPtr)(); + ((CrashyFunctionPtr)ptr)(); + return Crash::Failure::DidNotCrash; + }).run(run_type); } - ASSERT_NOT_REACHED(); return 0; } +