| // 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. |
| static char env[ARG_MAX]; |
| static ssize_t env_size; |
| |
| // 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"); |
| env_size = read(fd, env, sizeof(env)); |
| if (env_size < 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); |
| if (env_size < key_len) return nullptr; |
| 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 < env_size - 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 against 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 |