// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <chrono>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>
#include <tuple>
#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "v8/include/libplatform/libplatform.h"
#include "v8/include/v8.h"
using v8::MaybeLocal;
using std::ref;
using std::lock_guard;
using std::mutex;
using std::chrono::time_point;
using std::chrono::steady_clock;
using std::chrono::seconds;
using std::chrono::duration_cast;
static const seconds kSleepSeconds(1);
// Because of the sleep we do, the actual max will be:
// kSleepSeconds + kMaxExecutionSeconds.
// TODO(metzman): Determine if having such a short timeout causes too much
// indeterminism.
static const seconds kMaxExecutionSeconds(7);
// Inspired by/copied from d8 code, this allocator will return nullptr when
// an allocation request is made that puts currently_allocated_ over
// kAllocationLimit (1 GB). Should handle the current allocations done by V8.
class MockArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
std::unique_ptr<Allocator> allocator_ =
std::unique_ptr<Allocator>(NewDefaultAllocator());
const size_t kAllocationLimit = 1000 * 1024 * 1024;
// TODO(metzman): Determine if this approach where we keep track of state
// between runs is a good idea. Maybe we should simply prevent allocations
// over a certain size regardless of previous allocations.
size_t currently_allocated_;
mutex mtx_;
public:
MockArrayBufferAllocator()
: v8::ArrayBuffer::Allocator(), currently_allocated_(0) {}
void* Allocate(size_t length) override {
lock_guard<mutex> mtx_locker(mtx_);
if (length + currently_allocated_ > kAllocationLimit) {
return nullptr;
}
currently_allocated_ += length;
return allocator_->Allocate(length);
}
void* AllocateUninitialized(size_t length) override {
lock_guard<mutex> mtx_locker(mtx_);
if (length + currently_allocated_ > kAllocationLimit) {
return nullptr;
}
currently_allocated_ += length;
return allocator_->AllocateUninitialized(length);
}
void Free(void* ptr, size_t length) override {
lock_guard<mutex> mtx_locker(mtx_);
currently_allocated_ -= length;
return allocator_->Free(ptr, length);
}
};
void terminate_execution(v8::Isolate* isolate,
mutex& mtx,
bool& is_running,
time_point<steady_clock>& start_time) {
while (true) {
std::this_thread::sleep_for(kSleepSeconds);
lock_guard<mutex> mtx_locker(mtx);
if (is_running) {
if (duration_cast<seconds>(steady_clock::now() - start_time) >
kMaxExecutionSeconds) {
isolate->TerminateExecution();
is_running = false;
std::cout << "Terminated" << std::endl;
fflush(0);
}
}
}
}
struct Environment {
Environment() {
platform_ = v8::platform::NewDefaultPlatform(
0, v8::platform::IdleTaskSupport::kDisabled,
v8::platform::InProcessStackDumping::kDisabled, nullptr);
v8::V8::InitializePlatform(platform_.get());
v8::V8::Initialize();
v8::Isolate::CreateParams create_params;
mock_arraybuffer_allocator = std::make_unique<MockArrayBufferAllocator>();
create_params.array_buffer_allocator = mock_arraybuffer_allocator.get();
isolate = v8::Isolate::New(create_params);
terminator_thread = std::thread(terminate_execution, isolate, ref(mtx),
ref(is_running), ref(start_time));
}
std::unique_ptr<MockArrayBufferAllocator> mock_arraybuffer_allocator;
mutex mtx;
std::thread terminator_thread;
raw_ptr<v8::Isolate> isolate;
std::unique_ptr<v8::Platform> platform_;
time_point<steady_clock> start_time;
bool is_running = true;
};
// Explicitly specify some attributes to avoid issues with the linker dead-
// stripping the following function on macOS, as it is not called directly
// by fuzz target. LibFuzzer runtime uses dlsym() to resolve that function.
extern "C" __attribute__((used)) __attribute__((visibility("default"))) int
LLVMFuzzerInitialize(int* argc, char*** argv) {
v8::V8::InitializeICUDefaultLocation((*argv)[0]);
v8::V8::InitializeExternalStartupData((*argv)[0]);
v8::V8::SetFlagsFromCommandLine(argc, *argv, true);
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
static Environment* env = new Environment();
if (size < 1)
return 0;
v8::Isolate::Scope isolate_scope(env->isolate);
v8::HandleScope handle_scope(env->isolate);
v8::Local<v8::Context> context = v8::Context::New(env->isolate);
v8::Context::Scope context_scope(context);
std::string source_string =
std::string(reinterpret_cast<const char*>(data), size);
MaybeLocal<v8::String> source_v8_string = v8::String::NewFromUtf8(
env->isolate, source_string.c_str(), v8::NewStringType::kNormal);
if (source_v8_string.IsEmpty())
return 0;
v8::TryCatch try_catch(env->isolate);
MaybeLocal<v8::Script> script =
v8::Script::Compile(context, source_v8_string.ToLocalChecked());
if (script.IsEmpty())
return 0;
auto local_script = script.ToLocalChecked();
env->mtx.lock();
env->start_time = steady_clock::now();
env->mtx.unlock();
std::ignore = local_script->Run(context);
lock_guard<mutex> mtx_locker(env->mtx);
env->is_running = false;
return 0;
}