Serenity Operating System
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}