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:
parent
d9337dc100
commit
70eb08982c
10
BUILD.gn
10
BUILD.gn
@ -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.
|
||||
|
@ -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 == "") {
|
||||
|
@ -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
74
src/d8/cov.cc
Normal 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
15
src/d8/cov.h
Normal 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_
|
336
src/d8/d8.cc
336
src/d8/d8.cc
@ -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();
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
21
test/fuzzilli/BUILD.gn
Normal 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
16
test/fuzzilli/README.md
Normal 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
|
5
test/fuzzilli/fuzzilli.status
Normal file
5
test/fuzzilli/fuzzilli.status
Normal 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
219
test/fuzzilli/libreprl.c
Normal 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
54
test/fuzzilli/libreprl.h
Normal 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
18
test/fuzzilli/main.cc
Normal 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
0
test/fuzzilli/test/test
Normal file
58
test/fuzzilli/testcfg.py
Normal file
58
test/fuzzilli/testcfg.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user