// 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 #include #include #include #include #include #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_ = 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 { lock_guard 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 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 mtx_locker(mtx_); currently_allocated_ -= length; return allocator_->Free(ptr, length); } }; void terminate_execution(v8::Isolate* isolate, mutex& mtx, bool& is_running, time_point& start_time) { while (true) { std::this_thread::sleep_for(kSleepSeconds); lock_guard mtx_locker(mtx); if (is_running) { if (duration_cast(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(); 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 mock_arraybuffer_allocator; mutex mtx; std::thread terminator_thread; raw_ptr isolate; std::unique_ptr platform_; time_point 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 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->mtx.unlock(); std::ignore = local_script->Run(context); lock_guard mtx_locker(env->mtx); env->is_running = false; return 0; }