mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
382 lines
13 KiB
C++
382 lines
13 KiB
C++
/*
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
* 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 <AK/Function.h>
|
|
#include <AK/String.h>
|
|
#include <LibBareMetal/IO.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 -[AsdiamfMFTtSxyXUIc]\n");
|
|
exit(0);
|
|
}
|
|
|
|
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,
|
|
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;
|
|
}
|