/* * Copyright (c) 2018-2020, Andreas Kling * 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 #include #include #include #include #include #include #include #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_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, 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; }