From 9452281bec427c467d3784dd567c741bbaf7d7b8 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Wed, 10 Feb 2021 20:42:41 +0100 Subject: [PATCH] Tests: Merge and extend syscall tests into a syscall fuzzer This found the previous bugs :^) --- Userland/Tests/Kernel/fuzz-syscalls.cpp | 168 ++++++++++++++++++ .../Kernel/invalid-any-pointer-assert.cpp | 49 ----- .../Kernel/invalid-path-pointer-assert.cpp | 56 ------ Userland/Utilities/syscall.cpp | 2 +- 4 files changed, 169 insertions(+), 106 deletions(-) create mode 100644 Userland/Tests/Kernel/fuzz-syscalls.cpp delete mode 100644 Userland/Tests/Kernel/invalid-any-pointer-assert.cpp delete mode 100644 Userland/Tests/Kernel/invalid-path-pointer-assert.cpp diff --git a/Userland/Tests/Kernel/fuzz-syscalls.cpp b/Userland/Tests/Kernel/fuzz-syscalls.cpp new file mode 100644 index 0000000000..c38d6333d5 --- /dev/null +++ b/Userland/Tests/Kernel/fuzz-syscalls.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2021, Ben Wiederhake + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool is_deadly_syscall(int fn) +{ + return fn == SC_exit || fn == SC_fork || fn == SC_sigreturn || fn == SC_exit_thread || fn == SC_abort; +} + +static bool is_unfuzzable_syscall(int fn) +{ + return fn == SC_dump_backtrace || fn == SC_munmap || fn == SC_kill || fn == SC_killpg; +} + +static bool is_nosys_syscall(int fn) +{ + return fn == SC_futex; +} + +static void do_systematic_tests() +{ + int rc; + + for (int i = 0; i < Syscall::Function::__Count; ++i) { + dbgln("Testing syscall #{} ({})", i, Syscall::to_string((Syscall::Function)i)); + if (is_deadly_syscall(i)) { + dbgln("(skipping deadly syscall)"); + continue; + } + // This is pure torture + rc = syscall(Syscall::Function(i), 0xc0000001, 0xc0000002, 0xc0000003); + ASSERT(rc != -ENOSYS); + } + + // Finally, test invalid syscalls: + dbgln("Testing syscall #{} (n+1)", (int)Syscall::Function::__Count); + rc = syscall(Syscall::Function::__Count, 0xc0000001, 0xc0000002, 0xc0000003); + ASSERT(rc == -ENOSYS); + dbgln("Testing syscall #-1"); + rc = syscall(Syscall::Function(-1), 0xc0000001, 0xc0000002, 0xc0000003); + ASSERT(rc == -ENOSYS); +} + +static void randomize_from(size_t* buffer, size_t len, const Vector& values) +{ + for (size_t i = 0; i < len; ++i) { + buffer[i] = values[arc4random_uniform(values.size())]; + } +} + +// The largest SC_*_params struct is SC_mmap_params with 36 bytes. +static_assert(sizeof(size_t) == 4, "Cannot handle size_t != 4 bytes"); +static const size_t fake_params_count = 36 / sizeof(size_t); + +static void do_weird_call(size_t attempt, int syscall_fn, size_t arg1, size_t arg2, size_t arg3, size_t* fake_params) +{ + // Report to dbg what we're about to do, in case it's interesting: + StringBuilder builder; + builder.appendff("#{}: Calling {}({:p}, {:p}, {:p}) with {:p} containing [", + attempt, Syscall::to_string((Syscall::Function)syscall_fn), arg1, arg2, arg3, fake_params); + for (size_t i = 0; i < fake_params_count; ++i) { + if (i != 0) + builder.append(", "); + builder.appendff("{:p}", fake_params[i]); + } + builder.append("]"); + dbgln("{}", builder.build()); + + // Actually do the syscall ('fake_params' is passed indirectly, if any of arg1, arg2, or arg3 point to it. + int rc = syscall(Syscall::Function(syscall_fn), arg1, arg2, arg3); + ASSERT(rc != -ENOSYS || is_nosys_syscall(syscall_fn)); +} + +static void do_random_tests() +{ + // Make it less likely to kill ourselves due to sys$alarm(1): + { + struct sigaction act_ignore = { { SIG_IGN }, 0, 0 }; + int rc = sigaction(SIGALRM, &act_ignore, nullptr); + ASSERT(rc == 0); + } + + // Note that we will also make lots of syscalls for randomness and debugging. + const size_t fuzz_syscall_count = 10000; + + size_t direct_sc_args[3] = { 0 }; + // Isolate to a separate region to make corruption less likely, because we will write to it: + size_t* fake_sc_params = reinterpret_cast(mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_RANDOMIZED, 0, 0)); + const char* some_string = "Hello, world!"; + Vector interesting_values = { + 0, + 1, + reinterpret_cast(some_string), + strlen(some_string), + reinterpret_cast(fake_sc_params), + 0xc0000000, + 0xc0000000 - PAGE_SIZE, + 0xffffffff, + }; + dbgln("Doing a few random syscalls with:"); + for (size_t i = 0; i < interesting_values.size(); ++i) { + dbgln(" {0} ({0:p})", interesting_values[i]); + } + for (size_t i = 0; i < fuzz_syscall_count; ++i) { + // Construct a nice syscall: + int syscall_fn = arc4random_uniform(Syscall::Function::__Count); + if (is_deadly_syscall(syscall_fn) || is_unfuzzable_syscall(syscall_fn)) { + // Retry, and don't count towards syscall limit. + --i; + continue; + } + randomize_from(direct_sc_args, array_size(direct_sc_args), interesting_values); + randomize_from(fake_sc_params, fake_params_count, interesting_values); + + if (syscall_fn == SC_mprotect && direct_sc_args[0] == (size_t)fake_sc_params) { + // Actually, that's a terrible idea. + // Pretend we don't want to try that. + continue; + } + + do_weird_call(i, syscall_fn, direct_sc_args[0], direct_sc_args[1], direct_sc_args[2], fake_sc_params); + } +} + +int main() +{ + do_systematic_tests(); + + do_random_tests(); + + // If the Kernel survived, pass. + printf("PASS\n"); + return 0; +} diff --git a/Userland/Tests/Kernel/invalid-any-pointer-assert.cpp b/Userland/Tests/Kernel/invalid-any-pointer-assert.cpp deleted file mode 100644 index 0491b79c91..0000000000 --- a/Userland/Tests/Kernel/invalid-any-pointer-assert.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2021, Ben Wiederhake - * 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 -#include -#include -#include - -int main() -{ - for (int i = 0; i < Syscall::Function::__Count; ++i) { - dbgln("Testing syscall #{}", i); - // This is pure torture - syscall(Syscall::Function(i), 0xc0000001, 0xc0000002, 0xc0000003); - } - - // Finally, test invalid syscalls: - dbgln("Testing syscall #{} (n+1)", (int)Syscall::Function::__Count); - syscall(Syscall::Function::__Count, 0xc0000001, 0xc0000002, 0xc0000003); - dbgln("Testing syscall #-1"); - syscall(Syscall::Function(-1), 0xc0000001, 0xc0000002, 0xc0000003); - - // If the Kernel survived, pass. - printf("PASS\n"); - return 0; -} diff --git a/Userland/Tests/Kernel/invalid-path-pointer-assert.cpp b/Userland/Tests/Kernel/invalid-path-pointer-assert.cpp deleted file mode 100644 index 85001f8dd6..0000000000 --- a/Userland/Tests/Kernel/invalid-path-pointer-assert.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021, Ben Wiederhake - * 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 -#include -#include -#include - -int main() -{ - struct stat statbuf; - // stat(3) would call strlen, and we can't have that. - Syscall::SC_stat_params params { - // Hey Kernel, please try to read the path from this totally valid location! - { (const char*)0xc000dead, 50 }, - &statbuf, false - }; - int rc = syscall(SC_stat, ¶ms); - - if (rc == 0) { - printf("stat passed?!\n"); - printf("FAIL\n"); - return 1; - } - if (rc != EFAULT) { - printf("error other than EFAULT?! rc = %d\n", rc); - printf("FAIL\n"); - return 1; - } - - printf("PASS\n"); - return 0; -} diff --git a/Userland/Utilities/syscall.cpp b/Userland/Utilities/syscall.cpp index 9e6d508b52..ba051a0148 100644 --- a/Userland/Utilities/syscall.cpp +++ b/Userland/Utilities/syscall.cpp @@ -121,7 +121,7 @@ static FlatPtr as_buf(Vector params_vec) builder.appendff(" {:p}", params_vec[i]); } builder.appendff(" ] at {:p}", (FlatPtr)buf); - dbgln(builder.to_string()); + dbgln("{}", builder.to_string()); } // Leak the buffer here. We need to keep it until the special syscall happens,