forked from google/centipede
-
Notifications
You must be signed in to change notification settings - Fork 5
/
runner_fork_server.cc
183 lines (170 loc) · 7.16 KB
/
runner_fork_server.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright 2022 The Centipede Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Fork server, a.k.a. a process Zygote, for the Centipede runner.
//
// Startup:
// * Centipede creates two named FIFO pipes: pipe0 and pipe1.
// * Centipede runs the target in background, and passes the FIFO names to it
// using two environment variables: CENTIPEDE_FORK_SERVER_FIFO[01].
// * Centipede opens the pipe0 for writing, pipe1 for reading.
// These would block until the same pipes are open in the runner.
// * Runner, early at startup, checks if it is given the pipe names.
// If so, it opens pipe0 for reading, pipe1 for writing,
// and enters the infinite fork-server loop.
// Loop:
// * Centipede writes a byte to pipe0.
// * Runner blocks until it reads a byte from pipe0, then forks and waits.
// This is where the child process executes and does the work.
// This works because every execution of the target has the same arguments.
// * Runner receives the child exit status and writes it to pipe1.
// * Centipede blocks until it reads the status from pipe1.
// Exit:
// * Centipede closes the pipes (and then deletes them).
// * Runner (the fork server) fails on the next read from pipe0 and exits.
//
// The fork server code kicks in super-early in the process startup,
// via injecting itself into the .preinit_array.
// Ensure that this code is not dropped from linking (alwayslink=1).
//
// The main benefts of the fork server over plain fork/exec or system() are:
// * Dynamic linking happens once at the fork-server startup.
// * fork is cheaper than fork/exec, especially when running multiple threads.
//
// Other than performance, using fork server should be the same as not using it.
//
// Similar ideas:
// * lcamtuf.blogspot.com/2014/10/fuzzing-binaries-without-execve.html
// * Android Zygote.
//
// We try to avoid any high-level code here, even most of libc because this code
// works too early in the process. E.g. getenv() will not work yet.
#include <fcntl.h>
#include <linux/limits.h> // ARG_MAX
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
namespace centipede {
// Writes a C string to stderr when debugging, no-op otherwise.
void Log(const char *str) {
// Uncomment these lines to debug.
// (void)write(STDERR_FILENO, str, strlen(str));
// fsync(STDERR_FILENO);
}
// Maybe writes the `reason` to stderr; then calls _exit.
void Exit(const char *reason) {
Log(reason);
_exit(0); // The exit code does not matter, it won't be checked anyway.
}
// Contents of /proc/self/environ. We avoid malloc, so it's a fixed-size global.
// The fork server will fail to initialize if /proc/self/environ is too large.
char env[ARG_MAX];
// Reads /proc/self/environ into env.
void GetAllEnv() {
int fd = open("/proc/self/environ", O_RDONLY);
if (fd < 0) Exit("GetEnv: can't open /proc/self/environ\n");
if (read(fd, env, sizeof(env)) < 0) Exit("GetEnv: can't read to env\n");
if (close(fd) != 0) Exit("GetEnv: can't close /proc/self/environ\n");
env[sizeof(env) - 1] = 0; // Just in case.
}
// Gets a zero-terminated string matching the environment `key` (ends with '=').
const char *GetOneEnv(const char *key) {
size_t key_len = strlen(key);
bool in_the_beginning_of_key = true;
// env is not a C string.
// It is an array of bytes, with '\0' between individual key=val pairs.
for (size_t idx = 0; idx < sizeof(env) - key_len; ++idx) {
if (env[idx] == 0) {
in_the_beginning_of_key = true;
continue;
}
if (in_the_beginning_of_key && 0 == memcmp(env + idx, key, key_len))
return &env[idx + key_len]; // zero-terminated.
in_the_beginning_of_key = false;
}
return nullptr;
}
// Starts the fork server if the pipes are given.
// This function is called from .preinit_array when linked statically,
// or from the DSO constructor when injected via LD_PRELOAD.
// Note: it must run before the GlobalRunnerState constructor because
// GlobalRunnerState may terminate the process early due to an error,
// then we never open the fifos and the corresponding opens in centipede
// hang forever.
// The priority 150 is chosen on the lower end (higher priority)
// of the user-available range (101-999) to allow ordering with other
// constructors and C++ constructors (init_priority). Note: constructors
// without explicitly specified priority run after all constructors with
// explicitly specified priority, thus we still run before most
// "normal" constructors.
__attribute__((constructor(150))) void ForkServerCallMeVeryEarly() {
// Guard from calling twice.
static bool called_already = false;
if (called_already) return;
called_already = true;
// Startup.
GetAllEnv();
const char *pipe0_name = GetOneEnv("CENTIPEDE_FORK_SERVER_FIFO0=");
const char *pipe1_name = GetOneEnv("CENTIPEDE_FORK_SERVER_FIFO1=");
if (!pipe0_name || !pipe1_name) return;
Log("###Centipede fork server requested\n");
int pipe0 = open(pipe0_name, O_RDONLY);
if (pipe0 < 0) Exit("###open pipe0 failed\n");
int pipe1 = open(pipe1_name, O_WRONLY);
if (pipe1 < 0) Exit("###open pipe1 failed\n");
Log("###Centipede fork server ready\n");
// Loop.
while (true) {
Log("###Centipede fork server blocking on pipe0\n");
// This read will fail when Centipede shuts down the pipes.
char ch = 0;
if (read(pipe0, &ch, 1) != 1) Exit("###read from pipe0 failed\n");
Log("###Centipede starting fork\n");
auto pid = fork();
if (pid < 0) {
Exit("###fork failed\n");
} else if (pid == 0) {
// Child process. Reset stdout/stderr and let it run normally.
for (int fd = 1; fd <= 2; fd++) {
lseek(fd, 0, SEEK_SET);
// NOTE: Allow ftruncate() to fail by ignoring its return; that okay to
// happen when the stdout/stderr are not redirected to a file.
(void)ftruncate(fd, 0);
}
return;
} else {
// Parent process.
int status = -1;
if (waitpid(pid, &status, 0) < 0) Exit("###waitpid failed\n");
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == EXIT_SUCCESS)
Log("###Centipede fork returned EXIT_SUCCESS\n");
else if (WEXITSTATUS(status) == EXIT_FAILURE)
Log("###Centipede fork returned EXIT_FAILURE\n");
else
Log("###Centipede fork returned unknown failure status\n");
} else {
Log("###Centipede fork crashed\n");
}
Log("###Centipede fork writing status to pipe1\n");
if (write(pipe1, &status, sizeof(status)) == -1)
Exit("###write to pipe1 failed\n");
}
}
// The only way out of the loop is via Exit() or return.
__builtin_unreachable();
}
__attribute__((section(".preinit_array"))) auto call_very_early =
ForkServerCallMeVeryEarly;
} // namespace centipede