Serenity Operating System
at master 168 lines 5.8 kB view raw
1/* 2 * Copyright (c) 2021, Ben Wiederhake <BenWiederhake.GitHub@gmx.de> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/DeprecatedString.h> 8#include <AK/Format.h> 9#include <AK/Random.h> 10#include <AK/StringBuilder.h> 11#include <AK/Vector.h> 12#include <Kernel/API/SyscallString.h> 13#include <errno.h> 14#include <signal.h> 15#include <stdio.h> 16#include <stdlib.h> 17#include <string.h> 18#include <sys/mman.h> 19#include <syscall.h> 20 21static bool is_deadly_syscall(int fn) 22{ 23 return fn == SC_exit || fn == SC_fork || fn == SC_sigreturn || fn == SC_exit_thread; 24} 25 26static bool is_unfuzzable_syscall(int fn) 27{ 28 return fn == SC_dump_backtrace || fn == SC_munmap || fn == SC_kill || fn == SC_killpg; 29} 30 31static bool is_nosys_syscall(int fn) 32{ 33 return fn == SC_futex || fn == SC_emuctl; 34} 35 36static bool is_bad_idea(int fn, size_t const* direct_sc_args, size_t const* fake_sc_params, char const* some_string) 37{ 38 switch (fn) { 39 case SC_mprotect: 40 // This would mess with future tests or crash the fuzzer. 41 return direct_sc_args[0] == (size_t)fake_sc_params || direct_sc_args[0] == (size_t)some_string; 42 case SC_read: 43 case SC_readv: 44 // FIXME: Known bug: https://github.com/SerenityOS/serenity/issues/5328 45 return direct_sc_args[0] == 1; 46 case SC_write: 47 case SC_pwritev: 48 // FIXME: Known bug: https://github.com/SerenityOS/serenity/issues/5328 49 return direct_sc_args[0] == 0; 50 case SC_pledge: 51 // Equivalent to pledge(nullptr, _), which would kill the fuzzer. 52 return direct_sc_args[0] == (size_t)fake_sc_params && fake_sc_params[1] == 0; 53 default: 54 return false; 55 } 56} 57 58static void do_systematic_tests() 59{ 60 int rc; 61 62 for (int i = 0; i < Syscall::Function::__Count; ++i) { 63 dbgln("Testing syscall #{} ({})", i, Syscall::to_string((Syscall::Function)i)); 64 if (is_deadly_syscall(i)) { 65 dbgln("(skipping deadly syscall)"); 66 continue; 67 } 68 // This is pure torture 69 rc = syscall(Syscall::Function(i), 0xc0000001, 0xc0000002, 0xc0000003); 70 VERIFY(rc != -ENOSYS || is_nosys_syscall(i)); 71 } 72 73 // Finally, test invalid syscalls: 74 dbgln("Testing syscall #{} (n+1)", (int)Syscall::Function::__Count); 75 rc = syscall(Syscall::Function::__Count, 0xc0000001, 0xc0000002, 0xc0000003); 76 VERIFY(rc == -ENOSYS); 77 dbgln("Testing syscall #-1"); 78 rc = syscall(Syscall::Function(-1), 0xc0000001, 0xc0000002, 0xc0000003); 79 VERIFY(rc == -ENOSYS); 80} 81 82static void randomize_from(size_t* buffer, size_t len, Vector<size_t> const& values) 83{ 84 for (size_t i = 0; i < len; ++i) { 85 buffer[i] = values[get_random_uniform(values.size())]; 86 } 87} 88 89// The largest SC_*_params struct is SC_mmap_params with 9 size_ts (36 bytes on x86, 72 on x86_64). 90static constexpr size_t fake_params_count = sizeof(Syscall::SC_mmap_params) / sizeof(size_t); 91 92static void do_weird_call(size_t attempt, int syscall_fn, size_t arg1, size_t arg2, size_t arg3, size_t* fake_params) 93{ 94 // Report to dbg what we're about to do, in case it's interesting: 95 StringBuilder builder; 96 builder.appendff("#{}: Calling {}({:p}, {:p}, {:p}) with {:p} containing [", 97 attempt, Syscall::to_string((Syscall::Function)syscall_fn), arg1, arg2, arg3, fake_params); 98 for (size_t i = 0; i < fake_params_count; ++i) { 99 if (i != 0) 100 builder.append(", "sv); 101 builder.appendff("{:p}", fake_params[i]); 102 } 103 builder.append(']'); 104 dbgln("{}", builder.to_deprecated_string()); 105 106 // Actually do the syscall ('fake_params' is passed indirectly, if any of arg1, arg2, or arg3 point to it. 107 int rc = syscall(Syscall::Function(syscall_fn), arg1, arg2, arg3); 108 VERIFY(rc != -ENOSYS || is_nosys_syscall(syscall_fn)); 109} 110 111static void do_random_tests() 112{ 113 // Make it less likely to kill ourselves due to sys$alarm(1): 114 { 115 struct sigaction act_ignore = { { SIG_IGN }, 0, 0 }; 116 int rc = sigaction(SIGALRM, &act_ignore, nullptr); 117 VERIFY(rc == 0); 118 } 119 120 // Note that we will also make lots of syscalls for randomness and debugging. 121 const size_t fuzz_syscall_count = 10000; 122 123 size_t direct_sc_args[3] = { 0 }; 124 // Isolate to a separate region to make corruption less likely, because we will write to it: 125 auto* fake_sc_params = reinterpret_cast<size_t*>(mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_RANDOMIZED, 0, 0)); 126 char const* some_string = "Hello, world!"; 127 Vector<size_t> interesting_values = { 128 0, 129 1, 130 reinterpret_cast<size_t>(some_string), 131 strlen(some_string), 132 reinterpret_cast<size_t>(fake_sc_params), 133 0xc0000000, 134 0xc0000000 - PAGE_SIZE, 135 0xffffffff, 136 }; 137 dbgln("Doing a few random syscalls with:"); 138 for (auto const& interesting_value : interesting_values) { 139 dbgln(" {0} ({0:p})", interesting_value); 140 } 141 for (size_t i = 0; i < fuzz_syscall_count; ++i) { 142 // Construct a nice syscall: 143 int syscall_fn = get_random_uniform(Syscall::Function::__Count); 144 randomize_from(direct_sc_args, array_size(direct_sc_args), interesting_values); 145 randomize_from(fake_sc_params, fake_params_count, interesting_values); 146 147 if (is_deadly_syscall(syscall_fn) 148 || is_unfuzzable_syscall(syscall_fn) 149 || is_bad_idea(syscall_fn, direct_sc_args, fake_sc_params, some_string)) { 150 // Retry, and don't count towards syscall limit. 151 --i; 152 continue; 153 } 154 155 do_weird_call(i, syscall_fn, direct_sc_args[0], direct_sc_args[1], direct_sc_args[2], fake_sc_params); 156 } 157} 158 159int main() 160{ 161 do_systematic_tests(); 162 163 do_random_tests(); 164 165 // If the Kernel survived, pass. 166 printf("PASS\n"); 167 return 0; 168}