// Copyright 2017 The Chromium 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 #include #include #include #include #include "base/logging.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_ = std::unique_ptr(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 { void* data = AllocateUninitialized(length); return data == nullptr ? data : memset(data, 0, length); } void* AllocateUninitialized(size_t length) override { lock_guard mtx_locker(mtx_); if (length + currently_allocated_ > kAllocationLimit) { return nullptr; } currently_allocated_ += length; return malloc(length); } void Free(void* ptr, size_t length) override { lock_guard mtx_locker(mtx_); currently_allocated_ -= length; // We need to free before we unlock, otherwise currently_allocated_ will // be innacurate. free(ptr); } }; void terminate_execution(v8::Isolate* isolate, mutex& mtx, bool& is_running, time_point& start_time) { while (true) { std::this_thread::sleep_for(kSleepSeconds); mtx.lock(); if (is_running) { if (duration_cast(steady_clock::now() - start_time) > kMaxExecutionSeconds) { isolate->TerminateExecution(); is_running = false; std::cout << "Terminated" << std::endl; fflush(0); } } mtx.unlock(); } } struct Environment { Environment() { v8::Platform* platform = v8::platform::CreateDefaultPlatform( 0, v8::platform::IdleTaskSupport::kDisabled, v8::platform::InProcessStackDumping::kDisabled, nullptr); v8::V8::InitializePlatform(platform); v8::V8::Initialize(); v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = &mock_arraybuffer_allocator; isolate = v8::Isolate::New(create_params); terminator_thread = std::thread(terminate_execution, isolate, ref(mtx), ref(is_running), ref(start_time)); } MockArrayBufferAllocator mock_arraybuffer_allocator; mutex mtx; std::thread terminator_thread; v8::Isolate* isolate; time_point start_time; bool is_running; }; extern "C" 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 context = v8::Context::New(env->isolate); v8::Context::Scope context_scope(context); std::string source_string = std::string(reinterpret_cast(data), size); MaybeLocal 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 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->is_running = true; env->mtx.unlock(); ALLOW_UNUSED_LOCAL(local_script->Run(context)); lock_guard mtx_locker(env->mtx); env->is_running = false; return 0; }