| // Copyright 2020 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // Copyright 2019 Google LLC |
| // |
| // 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. |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <poll.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "libreprl.h" |
| |
| // Well-known file descriptor numbers for fuzzer <-> fuzzee communication on child process side. |
| #define CRFD 100 |
| #define CWFD 101 |
| #define DRFD 102 |
| #define DWFD 103 |
| |
| #define CHECK_SUCCESS(cond) if((cond) < 0) { perror(#cond); abort(); } |
| #define CHECK(cond) if(!(cond)) { fprintf(stderr, "(" #cond ") failed!"); abort(); } |
| |
| static uint64_t current_millis() |
| { |
| struct timespec ts; |
| clock_gettime(CLOCK_REALTIME, &ts); |
| return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; |
| } |
| |
| int reprl_spawn_child(char** argv, char** envp, struct reprl_child_process* child) |
| { |
| // We need to make sure that our fds don't end up being 100 - 104. |
| if (fcntl(CRFD, F_GETFD) == -1) { |
| int devnull = open("/dev/null", O_RDWR); |
| dup2(devnull, CRFD); |
| dup2(devnull, CWFD); |
| dup2(devnull, DRFD); |
| dup2(devnull, DWFD); |
| close(devnull); |
| } |
| |
| int crpipe[2] = { 0, 0 }; // control channel child -> fuzzer |
| int cwpipe[2] = { 0, 0 }; // control channel fuzzer -> child |
| int drpipe[2] = { 0, 0 }; // data channel child -> fuzzer |
| int dwpipe[2] = { 0, 0 }; // data channel fuzzer -> child |
| |
| int res = 0; |
| res |= pipe(crpipe); |
| res |= pipe(cwpipe); |
| res |= pipe(drpipe); |
| res |= pipe(dwpipe); |
| if (res != 0) { |
| if (crpipe[0] != 0) { close(crpipe[0]); close(crpipe[1]); } |
| if (cwpipe[0] != 0) { close(cwpipe[0]); close(cwpipe[1]); } |
| if (drpipe[0] != 0) { close(drpipe[0]); close(drpipe[1]); } |
| if (dwpipe[0] != 0) { close(dwpipe[0]); close(dwpipe[1]); } |
| fprintf(stderr, "[REPRL] Could not setup pipes for communication with child: %s\n", strerror(errno)); |
| return -1; |
| } |
| |
| child->crfd = crpipe[0]; |
| child->cwfd = cwpipe[1]; |
| child->drfd = drpipe[0]; |
| child->dwfd = dwpipe[1]; |
| |
| int flags; |
| flags = fcntl(child->drfd, F_GETFL, 0); |
| fcntl(child->drfd, F_SETFL, flags | O_NONBLOCK); |
| |
| fcntl(child->crfd, F_SETFD, FD_CLOEXEC); |
| fcntl(child->cwfd, F_SETFD, FD_CLOEXEC); |
| fcntl(child->drfd, F_SETFD, FD_CLOEXEC); |
| fcntl(child->dwfd, F_SETFD, FD_CLOEXEC); |
| |
| int pid = fork(); |
| if (pid == 0) { |
| dup2(cwpipe[0], CRFD); |
| dup2(crpipe[1], CWFD); |
| dup2(dwpipe[0], DRFD); |
| dup2(drpipe[1], DWFD); |
| close(cwpipe[0]); |
| close(crpipe[1]); |
| close(dwpipe[0]); |
| close(drpipe[1]); |
| |
| int devnull = open("/dev/null", O_RDWR); |
| dup2(devnull, 0); |
| dup2(devnull, 1); |
| dup2(devnull, 2); |
| close(devnull); |
| |
| execve(argv[0], argv, envp); |
| fprintf(stderr, "[REPRL] Failed to spawn child process\n"); |
| _exit(-1); |
| } else if (pid < 0) { |
| fprintf(stderr, "[REPRL] Failed to fork\n"); |
| return -1; |
| } |
| |
| close(crpipe[1]); |
| close(cwpipe[0]); |
| close(drpipe[1]); |
| close(dwpipe[0]); |
| |
| child->pid = pid; |
| |
| int helo; |
| if (read(child->crfd, &helo, 4) != 4 || write(child->cwfd, &helo, 4) != 4) { |
| fprintf(stderr, "[REPRL] Failed to communicate with child process\n"); |
| close(child->crfd); |
| close(child->cwfd); |
| close(child->drfd); |
| close(child->dwfd); |
| int status; |
| kill(pid, SIGKILL); |
| waitpid(pid, &status, 0); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static char* fetch_output(int fd, size_t* outsize) |
| { |
| ssize_t rv; |
| *outsize = 0; |
| size_t remaining = 0x1000; |
| char* outbuf = malloc(remaining + 1); |
| |
| do { |
| rv = read(fd, outbuf + *outsize, remaining); |
| if (rv == -1) { |
| if (errno != EAGAIN && errno != EWOULDBLOCK) { |
| fprintf(stderr, "[REPRL] Error while receiving data: %s\n", strerror(errno)); |
| } |
| break; |
| } |
| |
| *outsize += rv; |
| remaining -= rv; |
| |
| if (remaining == 0) { |
| remaining = *outsize; |
| outbuf = realloc(outbuf, *outsize * 2 + 1); |
| if (!outbuf) { |
| fprintf(stderr, "[REPRL] Could not allocate output buffer"); |
| _exit(-1); |
| } |
| } |
| } while (rv > 0); |
| |
| outbuf[*outsize] = 0; |
| |
| return outbuf; |
| } |
| |
| // Execute one script, wait for its completion, and return the result. |
| int reprl_execute_script(int pid, int crfd, int cwfd, int drfd, int dwfd, int timeout, const char* script, int64_t script_length, struct reprl_result* result) |
| { |
| uint64_t start_time = current_millis(); |
| |
| if (write(cwfd, "exec", 4) != 4 || |
| write(cwfd, &script_length, 8) != 8) { |
| fprintf(stderr, "[REPRL] Failed to send command to child process\n"); |
| return -1; |
| } |
| |
| int64_t remaining = script_length; |
| while (remaining > 0) { |
| ssize_t rv = write(dwfd, script, remaining); |
| if (rv <= 0) { |
| fprintf(stderr, "[REPRL] Failed to send script to child process\n"); |
| return -1; |
| } |
| remaining -= rv; |
| script += rv; |
| } |
| |
| struct pollfd fds = {.fd = crfd, .events = POLLIN, .revents = 0}; |
| if (poll(&fds, 1, timeout) != 1) { |
| kill(pid, SIGKILL); |
| waitpid(pid, &result->status, 0); |
| result->child_died = 1; |
| } else { |
| result->child_died = 0; |
| ssize_t rv = read(crfd, &result->status, 4); |
| if (rv != 4) { |
| // This should not happen... |
| kill(pid, SIGKILL); |
| waitpid(pid, &result->status, 0); |
| result->child_died = 1; |
| } |
| } |
| |
| result->output = fetch_output(drfd, &result->output_size); |
| result->exec_time = current_millis() - start_time; |
| |
| return 0; |
| } |