1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <chrono>
6 #include <functional>
7 #include <iostream>
8 #include <mutex>
9 #include <thread>
10 #include <tuple>
11
12 #include "base/compiler_specific.h"
13 #include "base/memory/raw_ptr.h"
14 #include "v8/include/libplatform/libplatform.h"
15 #include "v8/include/v8.h"
16
17 using v8::MaybeLocal;
18 using std::ref;
19 using std::lock_guard;
20 using std::mutex;
21 using std::chrono::time_point;
22 using std::chrono::steady_clock;
23 using std::chrono::seconds;
24 using std::chrono::duration_cast;
25
26 static const seconds kSleepSeconds(1);
27
28 // Because of the sleep we do, the actual max will be:
29 // kSleepSeconds + kMaxExecutionSeconds.
30 // TODO(metzman): Determine if having such a short timeout causes too much
31 // indeterminism.
32 static const seconds kMaxExecutionSeconds(7);
33
34 // Inspired by/copied from d8 code, this allocator will return nullptr when
35 // an allocation request is made that puts currently_allocated_ over
36 // kAllocationLimit (1 GB). Should handle the current allocations done by V8.
37 class MockArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
38 std::unique_ptr<Allocator> allocator_ =
39 std::unique_ptr<Allocator>(NewDefaultAllocator());
40
41 const size_t kAllocationLimit = 1000 * 1024 * 1024;
42 // TODO(metzman): Determine if this approach where we keep track of state
43 // between runs is a good idea. Maybe we should simply prevent allocations
44 // over a certain size regardless of previous allocations.
45 size_t currently_allocated_;
46 mutex mtx_;
47
48 public:
MockArrayBufferAllocator()49 MockArrayBufferAllocator()
50 : v8::ArrayBuffer::Allocator(), currently_allocated_(0) {}
51
Allocate(size_t length)52 void* Allocate(size_t length) override {
53 lock_guard<mutex> mtx_locker(mtx_);
54 if (length + currently_allocated_ > kAllocationLimit) {
55 return nullptr;
56 }
57 currently_allocated_ += length;
58 return allocator_->Allocate(length);
59 }
60
AllocateUninitialized(size_t length)61 void* AllocateUninitialized(size_t length) override {
62 lock_guard<mutex> mtx_locker(mtx_);
63 if (length + currently_allocated_ > kAllocationLimit) {
64 return nullptr;
65 }
66 currently_allocated_ += length;
67 return allocator_->AllocateUninitialized(length);
68 }
69
Free(void * ptr,size_t length)70 void Free(void* ptr, size_t length) override {
71 lock_guard<mutex> mtx_locker(mtx_);
72 currently_allocated_ -= length;
73 return allocator_->Free(ptr, length);
74 }
75 };
76
terminate_execution(v8::Isolate * isolate,mutex & mtx,bool & is_running,time_point<steady_clock> & start_time)77 void terminate_execution(v8::Isolate* isolate,
78 mutex& mtx,
79 bool& is_running,
80 time_point<steady_clock>& start_time) {
81 while (true) {
82 std::this_thread::sleep_for(kSleepSeconds);
83 lock_guard<mutex> mtx_locker(mtx);
84 if (is_running) {
85 if (duration_cast<seconds>(steady_clock::now() - start_time) >
86 kMaxExecutionSeconds) {
87 isolate->TerminateExecution();
88 is_running = false;
89 std::cout << "Terminated" << std::endl;
90 fflush(0);
91 }
92 }
93 }
94 }
95
96 struct Environment {
EnvironmentEnvironment97 Environment() {
98 platform_ = v8::platform::NewDefaultPlatform(
99 0, v8::platform::IdleTaskSupport::kDisabled,
100 v8::platform::InProcessStackDumping::kDisabled, nullptr);
101
102 v8::V8::InitializePlatform(platform_.get());
103 v8::V8::Initialize();
104 v8::Isolate::CreateParams create_params;
105
106 mock_arraybuffer_allocator = std::make_unique<MockArrayBufferAllocator>();
107
108 create_params.array_buffer_allocator = mock_arraybuffer_allocator.get();
109 isolate = v8::Isolate::New(create_params);
110 terminator_thread = std::thread(terminate_execution, isolate, ref(mtx),
111 ref(is_running), ref(start_time));
112 }
113 std::unique_ptr<MockArrayBufferAllocator> mock_arraybuffer_allocator;
114 mutex mtx;
115 std::thread terminator_thread;
116 raw_ptr<v8::Isolate> isolate;
117 std::unique_ptr<v8::Platform> platform_;
118 time_point<steady_clock> start_time;
119 bool is_running = true;
120 };
121
122 // Explicitly specify some attributes to avoid issues with the linker dead-
123 // stripping the following function on macOS, as it is not called directly
124 // by fuzz target. LibFuzzer runtime uses dlsym() to resolve that function.
125 extern "C" __attribute__((used)) __attribute__((visibility("default"))) int
LLVMFuzzerInitialize(int * argc,char *** argv)126 LLVMFuzzerInitialize(int* argc, char*** argv) {
127 v8::V8::InitializeICUDefaultLocation((*argv)[0]);
128 v8::V8::InitializeExternalStartupData((*argv)[0]);
129 v8::V8::SetFlagsFromCommandLine(argc, *argv, true);
130 return 0;
131 }
132
LLVMFuzzerTestOneInput(const uint8_t * data,size_t size)133 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
134 static Environment* env = new Environment();
135
136 if (size < 1)
137 return 0;
138
139 v8::Isolate::Scope isolate_scope(env->isolate);
140 v8::HandleScope handle_scope(env->isolate);
141 v8::Local<v8::Context> context = v8::Context::New(env->isolate);
142 v8::Context::Scope context_scope(context);
143
144 std::string source_string =
145 std::string(reinterpret_cast<const char*>(data), size);
146
147 MaybeLocal<v8::String> source_v8_string = v8::String::NewFromUtf8(
148 env->isolate, source_string.c_str(), v8::NewStringType::kNormal);
149
150 if (source_v8_string.IsEmpty())
151 return 0;
152
153 v8::TryCatch try_catch(env->isolate);
154 MaybeLocal<v8::Script> script =
155 v8::Script::Compile(context, source_v8_string.ToLocalChecked());
156
157 if (script.IsEmpty())
158 return 0;
159
160 auto local_script = script.ToLocalChecked();
161 env->mtx.lock();
162 env->start_time = steady_clock::now();
163 env->mtx.unlock();
164
165 std::ignore = local_script->Run(context);
166
167 lock_guard<mutex> mtx_locker(env->mtx);
168 env->is_running = false;
169 return 0;
170 }
171