mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 13:18:13 +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:
parent
74a18c86c9
commit
d0f9906c17
2 changed files with 225 additions and 102 deletions
|
@ -1,19 +1,88 @@
|
|||
#include <AK/Function.h>
|
||||
#include <AK/String.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 -[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::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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue