1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-19 12:47:38 +00:00

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.
This commit is contained in:
Shannon Booth 2019-12-30 15:07:57 +13:00 committed by Andreas Kling
parent 74a18c86c9
commit d0f9906c17
2 changed files with 225 additions and 102 deletions

View file

@ -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"
``` ```

View file

@ -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) { Crash::RunType run_type = mode == TestAllCrashTypes ? Crash::RunType::UsingChildProcess
volatile int* crashme = nullptr; : Crash::RunType::UsingCurrentProcess;
*crashme = 0xbeef;
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 == DivisionByZero) { if (mode == DivisionByZero || mode == TestAllCrashTypes) {
volatile int lala = 10; Crash("Division by zero", []() {
volatile int zero = 0; volatile int lala = 10;
volatile int test = lala / zero; volatile int zero = 0;
(void)test; volatile int test = lala / zero;
ASSERT_NOT_REACHED(); UNUSED_PARAM(test);
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == IllegalInstruction) { if (mode == IllegalInstruction || mode == TestAllCrashTypes) {
asm volatile("ud2"); Crash("Illegal instruction", []() {
ASSERT_NOT_REACHED(); asm volatile("ud2");
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == Abort) { if (mode == Abort || mode == TestAllCrashTypes) {
abort(); Crash("Abort", []() {
ASSERT_NOT_REACHED(); abort();
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == ReadFromUninitializedMallocMemory) { if (mode == ReadFromUninitializedMallocMemory || mode == TestAllCrashTypes) {
auto* uninitialized_memory = (volatile u32**)malloc(1024); Crash("Read from uninitialized malloc memory", []() {
volatile auto x = uninitialized_memory[0][0]; auto* uninitialized_memory = (volatile u32**)malloc(1024);
(void)x; if (!uninitialized_memory)
ASSERT_NOT_REACHED(); return Crash::Failure::UnexpectedError;
volatile auto x = uninitialized_memory[0][0];
UNUSED_PARAM(x);
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == ReadFromFreedMemory) { if (mode == ReadFromFreedMemory || mode == TestAllCrashTypes) {
auto* uninitialized_memory = (volatile u32**)malloc(1024); Crash("Read from freed memory", []() {
free(uninitialized_memory); auto* uninitialized_memory = (volatile u32**)malloc(1024);
volatile auto x = uninitialized_memory[4][0]; if (true)
(void)x; return Crash::Failure::UnexpectedError;
ASSERT_NOT_REACHED();
free(uninitialized_memory);
volatile auto x = uninitialized_memory[4][0];
UNUSED_PARAM(x);
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == WriteToUninitializedMallocMemory) { if (mode == WriteToUninitializedMallocMemory || mode == TestAllCrashTypes) {
auto* uninitialized_memory = (volatile u32**)malloc(1024); Crash("Write to uninitialized malloc memory", []() {
uninitialized_memory[4][0] = 1; auto* uninitialized_memory = (volatile u32**)malloc(1024);
ASSERT_NOT_REACHED(); if (!uninitialized_memory)
return Crash::Failure::UnexpectedError;
uninitialized_memory[4][0] = 1;
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == WriteToFreedMemory) { if (mode == WriteToFreedMemory || mode == TestAllCrashTypes) {
auto* uninitialized_memory = (volatile u32**)malloc(1024); Crash("Write to freed memory", []() {
free(uninitialized_memory); auto* uninitialized_memory = (volatile u32**)malloc(1024);
uninitialized_memory[4][0] = 1; if (!uninitialized_memory)
ASSERT_NOT_REACHED(); return Crash::Failure::UnexpectedError;
free(uninitialized_memory);
uninitialized_memory[4][0] = 1;
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == WriteToReadonlyMemory) { if (mode == WriteToReadonlyMemory || mode == TestAllCrashTypes) {
auto* ptr = (u8*)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANON, 0, 0); Crash("Write to read only memory", []() {
ASSERT(ptr != MAP_FAILED); auto* ptr = (u8*)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANON, 0, 0);
*ptr = 'x'; // This should work fine. if (ptr != MAP_FAILED)
int rc = mprotect(ptr, 4096, PROT_READ); return Crash::Failure::UnexpectedError;
ASSERT(rc == 0);
ASSERT(*ptr == 'x'); *ptr = 'x'; // This should work fine.
*ptr = 'y'; // This should crash! 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) { if (mode == InvalidStackPointerOnSyscall || mode == TestAllCrashTypes) {
u8* makeshift_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK, 0, 0); Crash("Invalid stack pointer on syscall", []() {
if (!makeshift_stack) { u8* makeshift_stack = (u8*)mmap(nullptr, 0, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK, 0, 0);
perror("mmap"); if (!makeshift_stack)
return 1; 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); u8* makeshift_esp = makeshift_stack + 2048;
if (!bad_stack) { asm volatile("mov %%eax, %%esp" ::"a"(makeshift_esp));
perror("mmap"); getuid();
return 1; dbgprintf("Survived syscall with MAP_STACK stack\n");
}
u8* bad_esp = bad_stack + 2048;
asm volatile("mov %%eax, %%esp" :: "a"(bad_esp));
getuid();
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) { if (mode == InvalidStackPointerOnPageFault || mode == TestAllCrashTypes) {
u8* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); Crash("Invalid stack pointer on page fault", []() {
if (!bad_stack) { u8* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
perror("mmap"); if (!bad_stack)
return 1; return Crash::Failure::UnexpectedError;
}
u8* bad_esp = bad_stack + 2048; u8* bad_esp = bad_stack + 2048;
asm volatile("mov %%eax, %%esp" :: "a"(bad_esp)); asm volatile("mov %%eax, %%esp" ::"a"(bad_esp));
asm volatile("pushl $0"); asm volatile("pushl $0");
ASSERT_NOT_REACHED(); return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == SyscallFromWritableMemory) { if (mode == SyscallFromWritableMemory || mode == TestAllCrashTypes) {
u8 buffer[] = { 0xb8, Syscall::SC_getuid, 0, 0, 0, 0xcd, 0x82 }; Crash("Syscall from writable memory", []() {
((void(*)())buffer)(); u8 buffer[] = { 0xb8, Syscall::SC_getuid, 0, 0, 0, 0xcd, 0x82 };
((void (*)())buffer)();
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == ReadFromFreedMemoryStillCachedByMalloc) { if (mode == ReadFromFreedMemoryStillCachedByMalloc || mode == TestAllCrashTypes) {
auto* ptr = (u8*)malloc(1024); Crash("Read from memory still cached by malloc", []() {
free(ptr); auto* ptr = (u8*)malloc(1024);
dbgprintf("ptr = %p\n", ptr); if (!ptr)
volatile auto foo = *ptr; return Crash::Failure::UnexpectedError;
(void)foo;
ASSERT_NOT_REACHED(); free(ptr);
dbgprintf("ptr = %p\n", ptr);
volatile auto foo = *ptr;
UNUSED_PARAM(foo);
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == WriteToFreedMemoryStillCachedByMalloc) { if (mode == WriteToFreedMemoryStillCachedByMalloc || mode == TestAllCrashTypes) {
auto* ptr = (u8*)malloc(1024); Crash("Write to freed memory still cached by malloc", []() {
free(ptr); auto* ptr = (u8*)malloc(1024);
dbgprintf("ptr = %p\n", ptr); if (!ptr)
*ptr = 'x'; return Crash::Failure::UnexpectedError;
ASSERT_NOT_REACHED(); free(ptr);
dbgprintf("ptr = %p\n", ptr);
*ptr = 'x';
return Crash::Failure::DidNotCrash;
}).run(run_type);
} }
if (mode == ExecuteNonExecutableMemory) { if (mode == ExecuteNonExecutableMemory || mode == TestAllCrashTypes) {
auto* ptr = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); Crash("Execute non executable memory", []() {
ASSERT(ptr != MAP_FAILED); 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 ptr[0] = 0xc3; // ret
typedef void* (*CrashyFunctionPtr)();
typedef void* (*CrashyFunctionPtr)(); ((CrashyFunctionPtr)ptr)();
((CrashyFunctionPtr)ptr)(); return Crash::Failure::DidNotCrash;
}).run(run_type);
ASSERT_NOT_REACHED();
} }
ASSERT_NOT_REACHED();
return 0; return 0;
} }