Integrate fuzzilli into v8

Fuzzilli is open source fuzzer by Samuel Groß (saelo@google.com)
that can be used to find bugs in v8 javascript engine. As we want
to automate fuzzing for current versions of v8, we want to merge
fuzzilli toolkit into v8 code, so that fuzzer can automatically
update to the newest version.
So far Fuzzilli has been maintained at
https://github.com/googleprojectzero/fuzzilli .


Bug tracker Id: https://bugs.chromium.org/p/v8/issues/detail?id=10571

Change-Id: I83ddc7e8bb31664c19e4044395bb9044a1c12031
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2201760
Reviewed-by: Tamer Tas <tmrts@chromium.org>
Reviewed-by: Michael Achenbach <machenbach@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Commit-Queue: Michael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68132}
This commit is contained in:
Peter Ralbovsky 2020-06-02 10:14:03 +00:00 committed by Commit Bot
parent d9337dc100
commit 70eb08982c
16 changed files with 749 additions and 98 deletions

View File

@ -577,6 +577,9 @@ config("features") {
if (v8_enable_nci_code) {
defines += [ "V8_ENABLE_NCI_CODE" ]
}
if (v8_fuzzilli) {
defines += [ "V8_FUZZILLI" ]
}
}
config("toolchain") {
@ -3235,6 +3238,13 @@ v8_source_set("v8_base_without_compiler") {
]
}
if (v8_fuzzilli) {
sources += [
"src/d8/cov.cc",
"src/d8/cov.h",
]
}
if (v8_check_header_includes) {
# This file will be generated by tools/generate-header-include-checks.py
# if the "check_v8_header_includes" gclient variable is set.

View File

@ -57,11 +57,14 @@ declare_args() {
# Implement tracing using Perfetto (https://perfetto.dev).
v8_use_perfetto = false
# Override global symbol level setting for v8
# Override global symbol level setting for v8.
v8_symbol_level = symbol_level
# Enable WebAssembly debugging via GDB-remote protocol.
v8_enable_wasm_gdb_remote_debugging = false
# Add fuzzilli fuzzer support.
v8_fuzzilli = false
}
if (v8_use_external_startup_data == "") {

View File

@ -503,7 +503,11 @@ void Utils::ReportOOMFailure(i::Isolate* isolate, const char* location,
if (fatal_callback == nullptr) {
base::OS::PrintError("\n#\n# Fatal %s OOM in %s\n#\n\n",
is_heap_oom ? "javascript" : "process", location);
#ifdef V8_FUZZILLI
exit(0);
#else
base::OS::Abort();
#endif // V8_FUZZILLI
} else {
fatal_callback(location,
is_heap_oom

74
src/d8/cov.cc Normal file
View File

@ -0,0 +1,74 @@
// 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.
#include "src/d8/cov.h"
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#define SHM_SIZE 0x100000
#define MAX_EDGES ((SHM_SIZE - 4) * 8)
struct shmem_data {
uint32_t num_edges;
unsigned char edges[];
};
struct shmem_data* shmem;
uint32_t *__edges_start, *__edges_stop;
void __sanitizer_cov_reset_edgeguards() {
uint32_t N = 0;
for (uint32_t* x = __edges_start; x < __edges_stop && N < MAX_EDGES; x++)
*x = ++N;
}
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t* start,
uint32_t* stop) {
// Map the shared memory region
const char* shm_key = getenv("SHM_ID");
if (!shm_key) {
puts("[COV] no shared memory bitmap available, skipping");
shmem = (struct shmem_data*)malloc(SHM_SIZE);
} else {
int fd = shm_open(shm_key, O_RDWR, S_IREAD | S_IWRITE);
if (fd <= -1) {
fprintf(stderr, "[COV] Failed to open shared memory region\n");
_exit(-1);
}
shmem = (struct shmem_data*)mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (shmem == MAP_FAILED) {
fprintf(stderr, "[COV] Failed to mmap shared memory region\n");
_exit(-1);
}
}
__edges_start = start;
__edges_stop = stop;
__sanitizer_cov_reset_edgeguards();
shmem->num_edges = static_cast<uint32_t>(stop - start);
printf("[COV] edge counters initialized. Shared memory: %s with %u edges\n",
shm_key, shmem->num_edges);
}
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t* guard) {
// There's a small race condition here: if this function executes in two
// threads for the same edge at the same time, the first thread might disable
// the edge (by setting the guard to zero) before the second thread fetches
// the guard value (and thus the index). However, our instrumentation ignores
// the first edge (see libcoverage.c) and so the race is unproblematic.
uint32_t index = *guard;
shmem->edges[index / 8] |= 1 << (index % 8);
*guard = 0;
}

15
src/d8/cov.h Normal file
View File

@ -0,0 +1,15 @@
// 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.
#ifndef V8_D8_COV_H_
#define V8_D8_COV_H_
// This file is defining functions to handle coverage which are needed for
// fuzzilli fuzzer It communicates coverage bitmap with fuzzilli through shared
// memory
// https://clang.llvm.org/docs/SanitizerCoverage.html
void __sanitizer_cov_reset_edgeguards();
#endif // V8_D8_COV_H_

View File

@ -54,6 +54,10 @@
#include "src/utils/utils.h"
#include "src/wasm/wasm-engine.h"
#ifdef V8_FUZZILLI
#include "src/d8/cov.h"
#endif // V8_FUZZILLI
#ifdef V8_USE_PERFETTO
#include "perfetto/tracing.h"
#endif // V8_USE_PERFETTO
@ -91,6 +95,19 @@ namespace {
const int kMB = 1024 * 1024;
#ifdef V8_FUZZILLI
// REPRL = read-eval-print-loop
// These file descriptors are being opened when Fuzzilli uses fork & execve to
// run V8.
#define REPRL_CRFD 100 // Control read file decriptor
#define REPRL_CWFD 101 // Control write file decriptor
#define REPRL_DRFD 102 // Data read file decriptor
#define REPRL_DWFD 103 // Data write file decriptor
bool fuzzilli_reprl = true;
#else
bool fuzzilli_reprl = false;
#endif // V8_FUZZILLI
const int kMaxSerializerMemoryUsage =
1 * kMB; // Arbitrary maximum for testing.
@ -1762,6 +1779,57 @@ void Shell::Version(const v8::FunctionCallbackInfo<v8::Value>& args) {
.ToLocalChecked());
}
#ifdef V8_FUZZILLI
// We have to assume that the fuzzer will be able to call this function e.g. by
// enumerating the properties of the global object and eval'ing them. As such
// this function is implemented in a way that requires passing some magic value
// as first argument (with the idea being that the fuzzer won't be able to
// generate this value) which then also acts as a selector for the operation
// to perform.
void Shell::Fuzzilli(const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope handle_scope(args.GetIsolate());
String::Utf8Value operation(args.GetIsolate(), args[0]);
if (*operation == nullptr) {
return;
}
if (strcmp(*operation, "FUZZILLI_CRASH") == 0) {
auto arg = args[1]
->Int32Value(args.GetIsolate()->GetCurrentContext())
.FromMaybe(0);
switch (arg) {
case 0:
V8_IMMEDIATE_CRASH();
break;
case 1:
CHECK(0);
break;
default:
DCHECK(false);
break;
}
} else if (strcmp(*operation, "FUZZILLI_PRINT") == 0) {
static FILE* fzliout = fdopen(REPRL_DWFD, "w");
if (!fzliout) {
fprintf(
stderr,
"Fuzzer output channel not available, printing to stdout instead\n");
fzliout = stdout;
}
String::Utf8Value string(args.GetIsolate(), args[1]);
if (*string == nullptr) {
return;
}
fprintf(fzliout, "%s\n", *string);
fflush(fzliout);
}
}
#endif // V8_FUZZILLI
void Shell::ReportException(Isolate* isolate, Local<v8::Message> message,
Local<v8::Value> exception_obj) {
HandleScope handle_scope(isolate);
@ -2026,6 +2094,13 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
AddOSMethods(isolate, os_templ);
global_template->Set(isolate, "os", os_templ);
#ifdef V8_FUZZILLI
global_template->Set(
String::NewFromUtf8(isolate, "fuzzilli", NewStringType::kNormal)
.ToLocalChecked(),
FunctionTemplate::New(isolate, Fuzzilli), PropertyAttribute::DontEnum);
#endif // V8_FUZZILLI
if (i::FLAG_expose_async_hooks) {
Local<ObjectTemplate> async_hooks_templ = ObjectTemplate::New(isolate);
async_hooks_templ->Set(
@ -2097,6 +2172,19 @@ void Shell::Initialize(Isolate* isolate, D8Console* console,
isolate->SetHostInitializeImportMetaObjectCallback(
Shell::HostInitializeImportMetaObject);
#ifdef V8_FUZZILLI
// Let the parent process (Fuzzilli) know we are ready.
char helo[] = "HELO";
if (write(REPRL_CWFD, helo, 4) != 4 || read(REPRL_CRFD, helo, 4) != 4) {
fuzzilli_reprl = false;
}
if (memcmp(helo, "HELO", 4) != 0) {
fprintf(stderr, "Invalid response from parent\n");
_exit(-1);
}
#endif // V8_FUZZILLI
debug::SetConsoleDelegate(isolate, console);
}
@ -2581,6 +2669,36 @@ bool ends_with(const char* input, const char* suffix) {
bool SourceGroup::Execute(Isolate* isolate) {
bool success = true;
#ifdef V8_FUZZILLI
HandleScope handle_scope(isolate);
Local<String> file_name =
String::NewFromUtf8(isolate, "fuzzcode.js", NewStringType::kNormal)
.ToLocalChecked();
size_t script_size;
CHECK_EQ(read(REPRL_CRFD, &script_size, 8), 8);
char* buffer = new char[script_size + 1];
char* ptr = buffer;
size_t remaining = script_size;
while (remaining > 0) {
ssize_t rv = read(REPRL_DRFD, ptr, remaining);
CHECK_GE(rv, 0);
remaining -= rv;
ptr += rv;
}
buffer[script_size] = 0;
Local<String> source =
String::NewFromUtf8(isolate, buffer, NewStringType::kNormal)
.ToLocalChecked();
delete[] buffer;
Shell::set_script_executed();
if (!Shell::ExecuteString(isolate, source, file_name, Shell::kNoPrintResult,
Shell::kReportExceptions,
Shell::kNoProcessMessageQueue)) {
return false;
}
#endif // V8_FUZZILLI
for (int i = begin_offset_; i < end_offset_; ++i) {
const char* arg = argv_[i];
if (strcmp(arg, "-e") == 0 && i + 1 < end_offset_) {
@ -3706,112 +3824,140 @@ int Shell::Main(int argc, char* argv[]) {
Initialize(isolate, &console);
PerIsolateData data(isolate);
if (options.trace_enabled) {
platform::tracing::TraceConfig* trace_config;
if (options.trace_config) {
int size = 0;
char* trace_config_json_str = ReadChars(options.trace_config, &size);
trace_config =
tracing::CreateTraceConfigFromJSON(isolate, trace_config_json_str);
delete[] trace_config_json_str;
// Fuzzilli REPRL = read-eval-print-loop
do {
#ifdef V8_FUZZILLI
if (fuzzilli_reprl) {
unsigned action = 0;
ssize_t nread = read(REPRL_CRFD, &action, 4);
if (nread != 4 || action != 'cexe') {
fprintf(stderr, "Unknown action: %u\n", action);
_exit(-1);
}
}
#endif // V8_FUZZILLI
result = 0;
if (options.trace_enabled) {
platform::tracing::TraceConfig* trace_config;
if (options.trace_config) {
int size = 0;
char* trace_config_json_str = ReadChars(options.trace_config, &size);
trace_config = tracing::CreateTraceConfigFromJSON(
isolate, trace_config_json_str);
delete[] trace_config_json_str;
} else {
trace_config =
platform::tracing::TraceConfig::CreateDefaultTraceConfig();
}
tracing_controller->StartTracing(trace_config);
}
CpuProfiler* cpu_profiler;
if (options.cpu_profiler) {
cpu_profiler = CpuProfiler::New(isolate);
CpuProfilingOptions profile_options;
cpu_profiler->StartProfiling(String::Empty(isolate), profile_options);
}
if (options.stress_opt) {
options.stress_runs = D8Testing::GetStressRuns();
for (int i = 0; i < options.stress_runs && result == 0; i++) {
printf("============ Stress %d/%d ============\n", i + 1,
options.stress_runs);
D8Testing::PrepareStressRun(i);
bool last_run = i == options.stress_runs - 1;
result = RunMain(isolate, last_run);
}
printf("======== Full Deoptimization =======\n");
D8Testing::DeoptimizeAll(isolate);
} else if (i::FLAG_stress_runs > 0) {
options.stress_runs = i::FLAG_stress_runs;
for (int i = 0; i < options.stress_runs && result == 0; i++) {
printf("============ Run %d/%d ============\n", i + 1,
options.stress_runs);
bool last_run = i == options.stress_runs - 1;
result = RunMain(isolate, last_run);
}
} else if (options.code_cache_options !=
ShellOptions::CodeCacheOptions::kNoProduceCache) {
printf("============ Run: Produce code cache ============\n");
// First run to produce the cache
Isolate::CreateParams create_params;
create_params.array_buffer_allocator = Shell::array_buffer_allocator;
i::FLAG_hash_seed ^= 1337; // Use a different hash seed.
Isolate* isolate2 = Isolate::New(create_params);
i::FLAG_hash_seed ^= 1337; // Restore old hash seed.
{
D8Console console(isolate2);
Initialize(isolate2, &console);
PerIsolateData data(isolate2);
Isolate::Scope isolate_scope(isolate2);
result = RunMain(isolate2, false);
}
isolate2->Dispose();
// Change the options to consume cache
DCHECK(options.compile_options == v8::ScriptCompiler::kEagerCompile ||
options.compile_options ==
v8::ScriptCompiler::kNoCompileOptions);
options.compile_options = v8::ScriptCompiler::kConsumeCodeCache;
options.code_cache_options =
ShellOptions::CodeCacheOptions::kNoProduceCache;
printf("============ Run: Consume code cache ============\n");
// Second run to consume the cache in current isolate
result = RunMain(isolate, true);
options.compile_options = v8::ScriptCompiler::kNoCompileOptions;
} else {
trace_config =
platform::tracing::TraceConfig::CreateDefaultTraceConfig();
}
tracing_controller->StartTracing(trace_config);
}
CpuProfiler* cpu_profiler;
if (options.cpu_profiler) {
cpu_profiler = CpuProfiler::New(isolate);
CpuProfilingOptions profile_options;
cpu_profiler->StartProfiling(String::Empty(isolate), profile_options);
}
if (options.stress_opt) {
options.stress_runs = D8Testing::GetStressRuns();
for (int i = 0; i < options.stress_runs && result == 0; i++) {
printf("============ Stress %d/%d ============\n", i + 1,
options.stress_runs);
D8Testing::PrepareStressRun(i);
bool last_run = i == options.stress_runs - 1;
bool last_run = true;
result = RunMain(isolate, last_run);
}
printf("======== Full Deoptimization =======\n");
D8Testing::DeoptimizeAll(isolate);
} else if (i::FLAG_stress_runs > 0) {
options.stress_runs = i::FLAG_stress_runs;
for (int i = 0; i < options.stress_runs && result == 0; i++) {
printf("============ Run %d/%d ============\n", i + 1,
options.stress_runs);
bool last_run = i == options.stress_runs - 1;
result = RunMain(isolate, last_run);
// Run interactive shell if explicitly requested or if no script has been
// executed, but never on --test
if (use_interactive_shell()) {
RunShell(isolate);
}
} else if (options.code_cache_options !=
ShellOptions::CodeCacheOptions::kNoProduceCache) {
printf("============ Run: Produce code cache ============\n");
// First run to produce the cache
Isolate::CreateParams create_params;
create_params.array_buffer_allocator = Shell::array_buffer_allocator;
i::FLAG_hash_seed ^= 1337; // Use a different hash seed.
Isolate* isolate2 = Isolate::New(create_params);
i::FLAG_hash_seed ^= 1337; // Restore old hash seed.
{
D8Console console(isolate2);
Initialize(isolate2, &console);
PerIsolateData data(isolate2);
Isolate::Scope isolate_scope(isolate2);
result = RunMain(isolate2, false);
if (i::FLAG_trace_ignition_dispatches &&
i::FLAG_trace_ignition_dispatches_output_file != nullptr) {
WriteIgnitionDispatchCountersFile(isolate);
}
isolate2->Dispose();
// Change the options to consume cache
DCHECK(options.compile_options == v8::ScriptCompiler::kEagerCompile ||
options.compile_options == v8::ScriptCompiler::kNoCompileOptions);
options.compile_options = v8::ScriptCompiler::kConsumeCodeCache;
options.code_cache_options =
ShellOptions::CodeCacheOptions::kNoProduceCache;
printf("============ Run: Consume code cache ============\n");
// Second run to consume the cache in current isolate
result = RunMain(isolate, true);
options.compile_options = v8::ScriptCompiler::kNoCompileOptions;
} else {
bool last_run = true;
result = RunMain(isolate, last_run);
}
// Run interactive shell if explicitly requested or if no script has been
// executed, but never on --test
if (use_interactive_shell()) {
RunShell(isolate);
}
if (i::FLAG_trace_ignition_dispatches &&
i::FLAG_trace_ignition_dispatches_output_file != nullptr) {
WriteIgnitionDispatchCountersFile(isolate);
}
if (options.cpu_profiler) {
CpuProfile* profile = cpu_profiler->StopProfiling(String::Empty(isolate));
if (options.cpu_profiler_print) {
const internal::ProfileNode* root =
reinterpret_cast<const internal::ProfileNode*>(
profile->GetTopDownRoot());
root->Print(0);
if (options.cpu_profiler) {
CpuProfile* profile =
cpu_profiler->StopProfiling(String::Empty(isolate));
if (options.cpu_profiler_print) {
const internal::ProfileNode* root =
reinterpret_cast<const internal::ProfileNode*>(
profile->GetTopDownRoot());
root->Print(0);
}
profile->Delete();
cpu_profiler->Dispose();
}
profile->Delete();
cpu_profiler->Dispose();
}
// Shut down contexts and collect garbage.
cached_code_map_.clear();
evaluation_context_.Reset();
stringify_function_.Reset();
CollectGarbage(isolate);
// Shut down contexts and collect garbage.
cached_code_map_.clear();
evaluation_context_.Reset();
stringify_function_.Reset();
CollectGarbage(isolate);
#ifdef V8_FUZZILLI
// Send result to parent (fuzzilli) and reset edge guards.
if (fuzzilli_reprl) {
int status = result << 8;
CHECK_EQ(write(REPRL_CWFD, &status, 4), 4);
__sanitizer_cov_reset_edgeguards();
}
#endif // V8_FUZZILLI
} while (fuzzilli_reprl);
}
OnExit(isolate);
V8::Dispose();
V8::ShutdownPlatform();

View File

@ -438,6 +438,10 @@ class Shell : public i::AllStatic {
Local<Module> module,
Local<Object> meta);
#ifdef V8_FUZZILLI
static void Fuzzilli(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // V8_FUZZILLI
// Data is of type DynamicImportData*. We use void* here to be able
// to conform with MicrotaskCallback interface and enqueue this
// function in the microtask queue.

View File

@ -9,8 +9,8 @@ group("gn_all") {
data_deps = [
"benchmarks:v8_benchmarks",
"intl:v8_intl",
"fuzzer:v8_fuzzer",
"intl:v8_intl",
"message:v8_message",
"mjsunit:v8_mjsunit",
"mozilla:v8_mozilla",
@ -26,6 +26,10 @@ group("gn_all") {
"wasm-api-tests:wasm_api_tests",
]
if (v8_fuzzilli) {
deps += [ "fuzzilli:v8_fuzzilli_test" ]
}
if (host_os != "mac" || !is_android) {
# These items don't compile for Android on Mac.
deps += [
@ -44,8 +48,8 @@ group("v8_perf") {
testonly = true
data_deps = [
"..:v8_python_base",
"..:d8",
"..:v8_python_base",
"../tools:v8_android_test_runner_deps",
"../tools:v8_testrunner",
]

21
test/fuzzilli/BUILD.gn Normal file
View File

@ -0,0 +1,21 @@
# 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.
import("../../gni/v8.gni")
v8_executable("v8_fuzzilli_test") {
testonly = true
sources = [
"libreprl.c",
"libreprl.h",
"main.cc",
]
configs = []
deps = []
data = []
}

16
test/fuzzilli/README.md Normal file
View File

@ -0,0 +1,16 @@
# Communication model of fuzzilli with V8
## Source code
On low level fuzzilli communicates with v8 through Swift C API library in `Sources/libreprl/libreprl.c`
`reprl_spawn_child` fucntions spawns child process. It does that by creating pipes, forking itself, then setting filedescriptors, and then transforming itself using `execve` into v8 process. Afterwords it checks for receiving 4 byte string and it sends the exact same string back.
`fetch_output` fetches the output from the child and returns its size and pointer to data.
`execute script`
writes `exec`, and size of script, into the command write pipe and sends script through data write pipe
## Coverage
Coverage information are being monitored through shared memory. On the side of v8 it is monitored through SanitizerCoverage module of Clang compiler ( https://clang.llvm.org/docs/SanitizerCoverage.html ) Through shared memory information about edges are shared with fuzzilli which implements counter for error and covered branches of the V8 code in Sources/libcoverage/coverage.c

View File

@ -0,0 +1,5 @@
# 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.
[]

219
test/fuzzilli/libreprl.c Normal file
View File

@ -0,0 +1,219 @@
// 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;
}

54
test/fuzzilli/libreprl.h Normal file
View File

@ -0,0 +1,54 @@
// 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.
#ifndef __LIBREPRL_H__
#define __LIBREPRL_H__
#include <sys/types.h>
struct reprl_child_process {
// Read file descriptor of the control pipe.
int crfd;
// Write file descriptor of the control pipe.
int cwfd;
// Read file descriptor of the data pipe.
int drfd;
// Write file descriptor of the data pipe.
int dwfd;
// PID of the child process.
int pid;
};
struct reprl_result {
int child_died;
int status;
unsigned long exec_time;
char* output;
size_t output_size;
};
// Spawn a child process implementing the REPRL protocol.
int reprl_spawn_child(char** argv, char** envp,
struct reprl_child_process* child);
// Execute the provided script in the child process, 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);
#endif

18
test/fuzzilli/main.cc Normal file
View File

@ -0,0 +1,18 @@
// 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.
extern "C" {
#include "libreprl.h"
}
int main() {
struct reprl_child_process child;
char* env[] = {nullptr};
char prog[] = "./out.gn/x64.debug/d8";
char*(argv[]) = {prog, nullptr};
if (reprl_spawn_child(argv, env, &child) == -1) return -1;
// struct reprl_result res;
// reprl_execute_script(child.pid, child.crfd, child.cwfd, child.drfd,
// child.dwfd, 1, ,,&res);
return 0;
}

0
test/fuzzilli/test/test Normal file
View File

58
test/fuzzilli/testcfg.py Normal file
View File

@ -0,0 +1,58 @@
# Copyright 2016 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.
import os
from testrunner.local import testsuite
from testrunner.objects import testcase
SUB_TESTS = [
'test',
]
class VariantsGenerator(testsuite.VariantsGenerator):
def _get_variants(self, test):
return self._standard_variant
class TestLoader(testsuite.GenericTestLoader):
@property
def test_dirs(self):
return SUB_TESTS
def _to_relpath(self, abspath, _):
return os.path.relpath(abspath, self.suite.root)
class TestSuite(testsuite.TestSuite):
def _test_loader_class(self):
return TestLoader
def _test_class(self):
return TestCase
def _variants_gen_class(self):
return VariantsGenerator
class TestCase(testcase.TestCase):
def _get_files_params(self):
suite, name = self.path.split(os.path.sep)
return [os.path.join(self.suite.root, suite, name)]
def _get_variant_flags(self):
return []
def _get_statusfile_flags(self):
return []
def _get_mode_flags(self):
return []
def get_shell(self):
return 'v8_fuzzilli_test'
def GetSuite(*args, **kwargs):
return TestSuite(*args, **kwargs)