Browse Source

Crash: Add a "Test All Crash Types" option

Add an option "-A", that will run all of the crash types in the crash
program. In this mode, all crash tests are run in a child process so
that the crash program does not crash.

Crash uses the return status of the child process to ascertain whether
the crash happened as expected.
Shannon Booth 5 years ago
parent
commit
d0f9906c17
2 changed files with 226 additions and 103 deletions
  1. 2 0
      Base/usr/share/man/man1/crash.md
  2. 224 103
      Userland/crash.cpp

+ 2 - 0
Base/usr/share/man/man1/crash.md

@@ -16,6 +16,7 @@ kinds of crashes.
 
 
 ## Options
 ## Options
 
 
+* `-A`: Test that all of the following crashes crash as intended.
 * `-s`: Perform a segmentation violation by dereferencing an invalid pointer.
 * `-s`: Perform a segmentation violation by dereferencing an invalid pointer.
 * `-d`: Perform a division by zero.
 * `-d`: Perform a division by zero.
 * `-i`: Execute an illegal CPU instruction.
 * `-i`: Execute an illegal CPU instruction.
@@ -36,5 +37,6 @@ kinds of crashes.
 
 
 ```sh
 ```sh
 $ crash -F
 $ crash -F
+Testing: "Write to freed memory"
 Shell: crash(33) exitied due to signal "Segmentation violation"
 Shell: crash(33) exitied due to signal "Segmentation violation"
 ```
 ```

+ 224 - 103
Userland/crash.cpp

@@ -1,19 +1,88 @@
+#include <AK/Function.h>
 #include <AK/String.h>
 #include <AK/String.h>
 #include <Kernel/Syscall.h>
 #include <Kernel/Syscall.h>
 #include <stdio.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdlib.h>
 #include <sys/mman.h>
 #include <sys/mman.h>
+#include <sys/wait.h>
+
+#pragma GCC optimize("O0")
 
 
 static void print_usage_and_exit()
 static void print_usage_and_exit()
 {
 {
-    printf("usage: crash -[sdiamfMFTtSxyX]\n");
+    printf("usage: crash -[AsdiamfMFTtSxyX]\n");
     exit(0);
     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::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)
 int main(int argc, char** argv)
 {
 {
     enum Mode {
     enum Mode {
+        TestAllCrashTypes,
         SegmentationViolation,
         SegmentationViolation,
         DivisionByZero,
         DivisionByZero,
         IllegalInstruction,
         IllegalInstruction,
@@ -35,7 +104,9 @@ int main(int argc, char** argv)
     if (argc != 2)
     if (argc != 2)
         print_usage_and_exit();
         print_usage_and_exit();
 
 
-    if (String(argv[1]) == "-s")
+    if (String(argv[1]) == "-A")
+        mode = TestAllCrashTypes;
+    else if (String(argv[1]) == "-s")
         mode = SegmentationViolation;
         mode = SegmentationViolation;
     else if (String(argv[1]) == "-d")
     else if (String(argv[1]) == "-d")
         mode = DivisionByZero;
         mode = DivisionByZero;
@@ -68,137 +139,187 @@ int main(int argc, char** argv)
     else
     else
         print_usage_and_exit();
         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 == DivisionByZero) {
-        volatile int lala = 10;
-        volatile int zero = 0;
-        volatile int test = lala / zero;
-        (void)test;
-        ASSERT_NOT_REACHED();
+    if (mode == SegmentationViolation || mode == TestAllCrashTypes) {
+        Crash("Segmentation violation", []() {
+            volatile int* crashme = nullptr;
+            *crashme = 0xbeef;
+            return Crash::Failure::DidNotCrash;
+        }).run(run_type);
     }
     }
 
 
-    if (mode == IllegalInstruction) {
-        asm volatile("ud2");
-        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 == Abort) {
-        abort();
-        ASSERT_NOT_REACHED();
+    if (mode == IllegalInstruction || mode == TestAllCrashTypes) {
+        Crash("Illegal instruction", []() {
+            asm volatile("ud2");
+            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 == Abort || mode == TestAllCrashTypes) {
+        Crash("Abort", []() {
+            abort();
+            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 == ReadFromUninitializedMallocMemory || mode == TestAllCrashTypes) {
+        Crash("Read from uninitialized malloc memory", []() {
+            auto* uninitialized_memory = (volatile u32**)malloc(1024);
+            if (!uninitialized_memory)
+                return Crash::Failure::UnexpectedError;
 
 
-    if (mode == WriteToUninitializedMallocMemory) {
-        auto* uninitialized_memory = (volatile u32**)malloc(1024);
-        uninitialized_memory[4][0] = 1;
-        ASSERT_NOT_REACHED();
+            volatile auto x = uninitialized_memory[0][0];
+            UNUSED_PARAM(x);
+            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 == 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 == 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 == 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 == 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");
-
-        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();
+    if (mode == WriteToFreedMemory || mode == TestAllCrashTypes) {
+        Crash("Write to freed memory", []() {
+            auto* uninitialized_memory = (volatile u32**)malloc(1024);
+            if (!uninitialized_memory)
+                return Crash::Failure::UnexpectedError;
 
 
-        ASSERT_NOT_REACHED();
+            free(uninitialized_memory);
+            uninitialized_memory[4][0] = 1;
+            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 == 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 == SyscallFromWritableMemory) {
-        u8 buffer[] = { 0xb8, Syscall::SC_getuid, 0, 0, 0, 0xcd, 0x82 };
-        ((void(*)())buffer)();
+    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 == ReadFromFreedMemoryStillCachedByMalloc) {
-        auto* ptr = (u8*)malloc(1024);
-        free(ptr);
-        dbgprintf("ptr = %p\n", ptr);
-        volatile auto foo = *ptr;
-        (void)foo;
-        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 == WriteToFreedMemoryStillCachedByMalloc) {
-        auto* ptr = (u8*)malloc(1024);
-        free(ptr);
-        dbgprintf("ptr = %p\n", ptr);
-        *ptr = 'x';
-        ASSERT_NOT_REACHED();
+    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 == ExecuteNonExecutableMemory) {
-        auto* ptr = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
-        ASSERT(ptr != MAP_FAILED);
+    if (mode == ReadFromFreedMemoryStillCachedByMalloc || mode == TestAllCrashTypes) {
+        Crash("Read from memory still cached by malloc", []() {
+            auto* ptr = (u8*)malloc(1024);
+            if (!ptr)
+                return Crash::Failure::UnexpectedError;
 
 
-        ptr[0] = 0xc3; // ret
+            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);
+    }
 
 
-        typedef void* (*CrashyFunctionPtr)();
-        ((CrashyFunctionPtr)ptr)();
+    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;
 
 
-        ASSERT_NOT_REACHED();
+            ptr[0] = 0xc3; // ret
+            typedef void* (*CrashyFunctionPtr)();
+            ((CrashyFunctionPtr)ptr)();
+            return Crash::Failure::DidNotCrash;
+        }).run(run_type);
     }
     }
 
 
-    ASSERT_NOT_REACHED();
     return 0;
     return 0;
 }
 }
+