| // 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; |
| } |