220 lines
6.4 KiB
C
220 lines
6.4 KiB
C
|
// 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;
|
||
|
}
|