|  | // 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 <chrono> | 
|  | #include <functional> | 
|  | #include <iostream> | 
|  | #include <mutex> | 
|  | #include <thread> | 
|  |  | 
|  | #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> 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 { | 
|  | void* data = AllocateUninitialized(length); | 
|  | return data == nullptr ? data : memset(data, 0, length); | 
|  | } | 
|  |  | 
|  | void* AllocateUninitialized(size_t length) override { | 
|  | lock_guard<mutex> 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<mutex> 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<steady_clock>& start_time) { | 
|  | while (true) { | 
|  | std::this_thread::sleep_for(kSleepSeconds); | 
|  | mtx.lock(); | 
|  | 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); | 
|  | } | 
|  | } | 
|  | 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<steady_clock> 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<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->is_running = true; | 
|  | env->mtx.unlock(); | 
|  |  | 
|  | ALLOW_UNUSED_LOCAL(local_script->Run(context)); | 
|  |  | 
|  | lock_guard<mutex> mtx_locker(env->mtx); | 
|  | env->is_running = false; | 
|  | return 0; | 
|  | } |