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:
parent
74a18c86c9
commit
d0f9906c17
2 changed files with 225 additions and 102 deletions
|
@ -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"
|
||||||
```
|
```
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue