v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
testing.cc
Go to the documentation of this file.
1// Copyright 2022 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include "src/api/api-inl.h"
11#include "src/heap/factory.h"
15#include "src/sandbox/sandbox.h"
16
17#ifdef V8_OS_LINUX
18#include <signal.h>
19#include <sys/mman.h>
20#include <unistd.h>
21#endif // V8_OS_LINUX
22
23#ifdef V8_USE_ADDRESS_SANITIZER
24#include <sanitizer/asan_interface.h>
25#endif // V8_USE_ADDRESS_SANITIZER
26
27namespace v8 {
28namespace internal {
29
30#ifdef V8_ENABLE_SANDBOX
31
32SandboxTesting::Mode SandboxTesting::mode_ = SandboxTesting::Mode::kDisabled;
33
34#ifdef V8_ENABLE_MEMORY_CORRUPTION_API
35
36namespace {
37
38// Sandbox.base
39void SandboxGetBase(const v8::FunctionCallbackInfo<v8::Value>& info) {
41 v8::Isolate* isolate = info.GetIsolate();
42 double sandbox_base = Sandbox::current()->base();
43 info.GetReturnValue().Set(v8::Number::New(isolate, sandbox_base));
44}
45// Sandbox.byteLength
46void SandboxGetByteLength(const v8::FunctionCallbackInfo<v8::Value>& info) {
48 v8::Isolate* isolate = info.GetIsolate();
49 double sandbox_size = Sandbox::current()->size();
50 info.GetReturnValue().Set(v8::Number::New(isolate, sandbox_size));
51}
52
53// new Sandbox.MemoryView(info) -> Sandbox.MemoryView
54void SandboxMemoryView(const v8::FunctionCallbackInfo<v8::Value>& info) {
56 v8::Isolate* isolate = info.GetIsolate();
57 Local<v8::Context> context = isolate->GetCurrentContext();
58
59 if (!info.IsConstructCall()) {
60 isolate->ThrowError("Sandbox.MemoryView must be invoked with 'new'");
61 return;
62 }
63
64 Local<v8::Integer> arg1, arg2;
65 if (!info[0]->ToInteger(context).ToLocal(&arg1) ||
66 !info[1]->ToInteger(context).ToLocal(&arg2)) {
67 isolate->ThrowError("Expects two number arguments (start offset and size)");
68 return;
69 }
70
71 Sandbox* sandbox = Sandbox::current();
72 CHECK_LE(sandbox->size(), kMaxSafeIntegerUint64);
73
74 uint64_t offset = arg1->Value();
75 uint64_t size = arg2->Value();
76 if (offset > sandbox->size() || size > sandbox->size() ||
77 (offset + size) > sandbox->size()) {
78 isolate->ThrowError(
79 "The MemoryView must be entirely contained within the sandbox");
80 return;
81 }
82
83 Factory* factory = reinterpret_cast<Isolate*>(isolate)->factory();
84 std::unique_ptr<BackingStore> memory = BackingStore::WrapAllocation(
85 reinterpret_cast<void*>(sandbox->base() + offset), size,
87 if (!memory) {
88 isolate->ThrowError("Out of memory: MemoryView backing store");
89 return;
90 }
91 Handle<JSArrayBuffer> buffer = factory->NewJSArrayBuffer(std::move(memory));
92 info.GetReturnValue().Set(Utils::ToLocal(buffer));
93}
94
95// The methods below either take a HeapObject or the address of a HeapObject as
96// argument. These helper functions can be used to extract the argument object
97// in both cases.
98using ArgumentObjectExtractorFunction = std::function<bool(
100
101static bool GetArgumentObjectPassedAsReference(
103 v8::Isolate* isolate = info.GetIsolate();
104
105 if (info.Length() == 0) {
106 isolate->ThrowError("First argument must be provided");
107 return false;
108 }
109
110 Handle<Object> arg = Utils::OpenHandle(*info[0]);
111 if (!IsHeapObject(*arg)) {
112 isolate->ThrowError("First argument must be a HeapObject");
113 return false;
114 }
115
116 *out = Cast<HeapObject>(*arg);
117 return true;
118}
119
120static bool GetArgumentObjectPassedAsAddress(
122 Sandbox* sandbox = Sandbox::current();
123 v8::Isolate* isolate = info.GetIsolate();
124 Local<v8::Context> context = isolate->GetCurrentContext();
125
126 if (info.Length() == 0) {
127 isolate->ThrowError("First argument must be provided");
128 return false;
129 }
130
131 Local<v8::Uint32> arg1;
132 if (!info[0]->ToUint32(context).ToLocal(&arg1)) {
133 isolate->ThrowError("First argument must be the address of a HeapObject");
134 return false;
135 }
136
137 uint32_t address = arg1->Value();
138 // Allow tagged addresses by removing the kHeapObjectTag and
139 // kWeakHeapObjectTag. This allows clients to just read tagged pointers from
140 // the heap and use them for these APIs.
141 address &= ~kHeapObjectTagMask;
142 *out = HeapObject::FromAddress(sandbox->base() + address);
143 return true;
144}
145
146// Sandbox.getAddressOf(Object) -> Number
147void SandboxGetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
149 v8::Isolate* isolate = info.GetIsolate();
150
152 if (!GetArgumentObjectPassedAsReference(info, &obj)) {
153 return;
154 }
155
156 // HeapObjects must be allocated inside the pointer compression cage so their
157 // address relative to the start of the sandbox can be obtained simply by
158 // taking the lowest 32 bits of the absolute address.
159 uint32_t address = static_cast<uint32_t>(obj->address());
160 info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address));
161}
162
163// Sandbox.getObjectAt(Number) -> Object
164void SandboxGetObjectAt(const v8::FunctionCallbackInfo<v8::Value>& info) {
166 v8::Isolate* isolate = info.GetIsolate();
167
169 if (!GetArgumentObjectPassedAsAddress(info, &obj)) {
170 return;
171 }
172
173 Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
174 Handle<Object> handle(obj, i_isolate);
175 info.GetReturnValue().Set(ToApiHandle<v8::Value>(handle));
176}
177
178// Sandbox.isValidObjectAt(Address) -> Bool
179void SandboxIsValidObjectAt(const v8::FunctionCallbackInfo<v8::Value>& info) {
181 v8::Isolate* isolate = info.GetIsolate();
182 Sandbox* sandbox = Sandbox::current();
183 Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
184 auto IsLocatedInMappedMemory = [&](Address address) {
185 if (heap->memory_allocator()->LookupChunkContainingAddress(address) !=
186 nullptr) {
187 return true;
188 }
189 return heap->read_only_space()->ContainsSlow(address);
190 };
191
193 if (!GetArgumentObjectPassedAsAddress(info, &obj)) {
194 return;
195 }
196
197 // Simple heuristic: follow the Map chain three times until we find a MetaMap
198 // (where the map pointer points to itself), or give up.
199 info.GetReturnValue().Set(false);
200 Address current = obj.address();
201 for (int i = 0; i < 3; i++) {
202 if (!IsLocatedInMappedMemory(current)) {
203 return;
204 }
205 uint32_t map_word = *reinterpret_cast<uint32_t*>(current);
206 if ((map_word & kHeapObjectTag) != kHeapObjectTag) {
207 return;
208 }
209 Address map_address = sandbox->base() + map_word - kHeapObjectTag;
210 if (map_address == current) {
211 info.GetReturnValue().Set(true);
212 return;
213 }
214 current = map_address;
215 }
216}
217
218static void SandboxIsWritableImpl(
220 ArgumentObjectExtractorFunction getArgumentObject) {
222
224 if (!getArgumentObject(info, &obj)) {
225 return;
226 }
227
228 MemoryChunkMetadata* chunk = MemoryChunkMetadata::FromHeapObject(obj);
229 bool is_writable = chunk->IsWritable();
230 info.GetReturnValue().Set(is_writable);
231}
232
233// Sandbox.isWritable(Object) -> Bool
234void SandboxIsWritable(const v8::FunctionCallbackInfo<v8::Value>& info) {
235 SandboxIsWritableImpl(info, &GetArgumentObjectPassedAsReference);
236}
237
238// Sandbox.isWritableObjectAt(Number) -> Bool
239void SandboxIsWritableObjectAt(
241 SandboxIsWritableImpl(info, &GetArgumentObjectPassedAsAddress);
242}
243
244static void SandboxGetSizeOfImpl(
246 ArgumentObjectExtractorFunction getArgumentObject) {
248
250 if (!getArgumentObject(info, &obj)) {
251 return;
252 }
253
254 int size = obj->Size();
255 info.GetReturnValue().Set(size);
256}
257
258// Sandbox.getSizeOf(Object) -> Number
259void SandboxGetSizeOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
260 SandboxGetSizeOfImpl(info, &GetArgumentObjectPassedAsReference);
261}
262
263// Sandbox.getSizeOfObjectAt(Number) -> Number
264void SandboxGetSizeOfObjectAt(const v8::FunctionCallbackInfo<v8::Value>& info) {
265 SandboxGetSizeOfImpl(info, &GetArgumentObjectPassedAsAddress);
266}
267
268static void SandboxGetInstanceTypeOfImpl(
270 ArgumentObjectExtractorFunction getArgumentObject) {
272 v8::Isolate* isolate = info.GetIsolate();
273
275 if (!getArgumentObject(info, &obj)) {
276 return;
277 }
278
279 InstanceType type = obj->map()->instance_type();
280 std::stringstream out;
281 out << type;
282 MaybeLocal<v8::String> result =
283 v8::String::NewFromUtf8(isolate, out.str().c_str());
284 info.GetReturnValue().Set(result.ToLocalChecked());
285}
286
287// Sandbox.getInstanceTypeOf(Object) -> String
288void SandboxGetInstanceTypeOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
289 SandboxGetInstanceTypeOfImpl(info, &GetArgumentObjectPassedAsReference);
290}
291
292// Sandbox.getInstanceTypeOfObjectAt(Number) -> String
293void SandboxGetInstanceTypeOfObjectAt(
295 SandboxGetInstanceTypeOfImpl(info, &GetArgumentObjectPassedAsAddress);
296}
297
298static void SandboxGetInstanceTypeIdOfImpl(
300 ArgumentObjectExtractorFunction getArgumentObject) {
302
304 if (!getArgumentObject(info, &obj)) {
305 return;
306 }
307
308 InstanceType type = obj->map()->instance_type();
309 static_assert(std::is_same_v<std::underlying_type_t<InstanceType>, uint16_t>);
310 if (type > LAST_TYPE) {
311 // This can happen with corrupted objects. Canonicalize to a special
312 // "unknown" instance type to indicate that this is an unknown type.
313 const uint16_t kUnknownInstanceType = std::numeric_limits<uint16_t>::max();
314 type = static_cast<InstanceType>(kUnknownInstanceType);
315 }
316
317 info.GetReturnValue().Set(type);
318}
319
320// Sandbox.getInstanceTypeIdOf(Object) -> Number
321void SandboxGetInstanceTypeIdOf(
323 SandboxGetInstanceTypeIdOfImpl(info, &GetArgumentObjectPassedAsReference);
324}
325
326// Sandbox.getInstanceTypeIdOfObjectAt(Number) -> Number
327void SandboxGetInstanceTypeIdOfObjectAt(
329 SandboxGetInstanceTypeIdOfImpl(info, &GetArgumentObjectPassedAsAddress);
330}
331
332// Sandbox.getInstanceTypeIdFor(String) -> Number
333void SandboxGetInstanceTypeIdFor(
336 v8::Isolate* isolate = info.GetIsolate();
337
338 v8::String::Utf8Value type_name(isolate, info[0]);
339 if (!*type_name) {
340 isolate->ThrowError("First argument must be a string");
341 return;
342 }
343
344 auto& all_types = SandboxTesting::GetInstanceTypeMap();
345 if (all_types.find(*type_name) == all_types.end()) {
346 isolate->ThrowError(
347 "Unknown type name. If needed, add it in "
348 "SandboxTesting::GetInstanceTypeMap");
349 return;
350 }
351
352 InstanceType type_id = all_types[*type_name];
353 info.GetReturnValue().Set(type_id);
354}
355
356// Obtain the offset of a field in an object.
357//
358// This can be used to obtain the offsets of internal object fields in order to
359// avoid hardcoding offsets into testcases. It basically makes the various
360// Foo::kBarOffset constants accessible from JavaScript. The main benefit of
361// that is that testcases continue to work if the field offset changes.
362// Additionally, if a field is removed, testcases that use it will fail and can
363// then be deleted if they are no longer useful.
364//
365// TODO(saelo): instead of this, consider adding an API like
366// `Sandbox.getTypeDescriptor(Number|String) -> Object` which, given an
367// instance type id or name, returns an object containing the offset constants
368// as properties as well as potentially other information such as the types of
369// the object's fields.
370//
371// Sandbox.getFieldOffset(Number, String) -> Number
372void SandboxGetFieldOffset(const v8::FunctionCallbackInfo<v8::Value>& info) {
374 v8::Isolate* isolate = info.GetIsolate();
375 Local<v8::Context> context = isolate->GetCurrentContext();
376
377 if (!info[0]->IsInt32()) {
378 isolate->ThrowError("Second argument must be an integer");
379 return;
380 }
381
382 int raw_type = info[0]->Int32Value(context).FromMaybe(-1);
383 if (raw_type < FIRST_TYPE || raw_type > LAST_TYPE) {
384 isolate->ThrowError("Invalid instance type");
385 return;
386 }
387 InstanceType instance_type = static_cast<InstanceType>(raw_type);
388
389 v8::String::Utf8Value field_name(isolate, info[1]);
390 if (!*field_name) {
391 isolate->ThrowError("Second argument must be a string");
392 return;
393 }
394
395 auto& all_fields = SandboxTesting::GetFieldOffsetMap();
396 if (all_fields.find(instance_type) == all_fields.end()) {
397 isolate->ThrowError(
398 "Unknown object type. If needed, add it in "
399 "SandboxTesting::GetFieldOffsetMap");
400 return;
401 }
402
403 auto& obj_fields = all_fields[instance_type];
404 if (obj_fields.find(*field_name) == obj_fields.end()) {
405 isolate->ThrowError(
406 "Unknown field. If needed, add it in "
407 "SandboxTesting::GetFieldOffsetMap");
408 return;
409 }
410
411 int offset = obj_fields[*field_name];
412 info.GetReturnValue().Set(offset);
413}
414
416 Isolate* isolate, FunctionCallback func,
417 ConstructorBehavior constructor_behavior) {
418 // Use the API functions here as they are more convenient to use.
419 v8::Isolate* api_isolate = reinterpret_cast<v8::Isolate*>(isolate);
420 Local<FunctionTemplate> function_template =
421 FunctionTemplate::New(api_isolate, func, {}, {}, 0, constructor_behavior,
423 return v8::Utils::OpenHandle(*function_template);
424}
425
426Handle<JSFunction> CreateFunc(Isolate* isolate, FunctionCallback func,
427 Handle<String> name, bool is_constructor) {
428 ConstructorBehavior constructor_behavior = is_constructor
431 Handle<FunctionTemplateInfo> function_template =
432 NewFunctionTemplate(isolate, func, constructor_behavior);
433 return ApiNatives::InstantiateFunction(isolate, function_template, name)
434 .ToHandleChecked();
435}
436
437void InstallFunc(Isolate* isolate, Handle<JSObject> holder,
438 FunctionCallback func, const char* name, int num_parameters,
439 bool is_constructor) {
440 Factory* factory = isolate->factory();
441 Handle<String> function_name = factory->NewStringFromAsciiChecked(name);
442 Handle<JSFunction> function =
443 CreateFunc(isolate, func, function_name, is_constructor);
444 function->shared()->set_length(num_parameters);
445 JSObject::AddProperty(isolate, holder, function_name, function, NONE);
446}
447
448void InstallGetter(Isolate* isolate, Handle<JSObject> object,
449 FunctionCallback func, const char* name) {
450 Factory* factory = isolate->factory();
451 Handle<String> property_name = factory->NewStringFromAsciiChecked(name);
452 Handle<JSFunction> getter = CreateFunc(isolate, func, property_name, false);
453 Handle<Object> setter = factory->null_value();
455 setter, FROZEN);
456}
457
458void InstallFunction(Isolate* isolate, Handle<JSObject> holder,
459 FunctionCallback func, const char* name,
460 int num_parameters) {
461 InstallFunc(isolate, holder, func, name, num_parameters, false);
462}
463
464void InstallConstructor(Isolate* isolate, Handle<JSObject> holder,
465 FunctionCallback func, const char* name,
466 int num_parameters) {
467 InstallFunc(isolate, holder, func, name, num_parameters, true);
468}
469} // namespace
470
471void SandboxTesting::InstallMemoryCorruptionApi(Isolate* isolate) {
472#ifndef V8_ENABLE_MEMORY_CORRUPTION_API
473#error "This function should not be available in any shipping build " \
474 "where it could potentially be abused to facilitate exploitation."
475#endif
476
477 CHECK(Sandbox::current()->is_initialized());
478
479 // Create the special Sandbox object that provides read/write access to the
480 // sandbox address space alongside other miscellaneous functionality.
481 Handle<JSObject> sandbox = isolate->factory()->NewJSObject(
482 isolate->object_function(), AllocationType::kOld);
483
484 InstallGetter(isolate, sandbox, SandboxGetBase, "base");
485 InstallGetter(isolate, sandbox, SandboxGetByteLength, "byteLength");
486 InstallConstructor(isolate, sandbox, SandboxMemoryView, "MemoryView", 2);
487 InstallFunction(isolate, sandbox, SandboxGetAddressOf, "getAddressOf", 1);
488 InstallFunction(isolate, sandbox, SandboxGetObjectAt, "getObjectAt", 1);
489 InstallFunction(isolate, sandbox, SandboxIsValidObjectAt, "isValidObjectAt",
490 1);
491 InstallFunction(isolate, sandbox, SandboxIsWritable, "isWritable", 1);
492 InstallFunction(isolate, sandbox, SandboxIsWritableObjectAt,
493 "isWritableObjectAt", 1);
494 InstallFunction(isolate, sandbox, SandboxGetSizeOf, "getSizeOf", 1);
495 InstallFunction(isolate, sandbox, SandboxGetSizeOfObjectAt,
496 "getSizeOfObjectAt", 1);
497 InstallFunction(isolate, sandbox, SandboxGetInstanceTypeOf,
498 "getInstanceTypeOf", 1);
499 InstallFunction(isolate, sandbox, SandboxGetInstanceTypeOfObjectAt,
500 "getInstanceTypeOfObjectAt", 1);
501 InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdOf,
502 "getInstanceTypeIdOf", 1);
503 InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdOfObjectAt,
504 "getInstanceTypeIdOfObjectAt", 1);
505 InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdFor,
506 "getInstanceTypeIdFor", 1);
507 InstallFunction(isolate, sandbox, SandboxGetFieldOffset, "getFieldOffset", 2);
508
509 // Install the Sandbox object as property on the global object.
510 Handle<JSGlobalObject> global = isolate->global_object();
511 Handle<String> name =
512 isolate->factory()->NewStringFromAsciiChecked("Sandbox");
513 JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM);
514}
515
516#endif // V8_ENABLE_MEMORY_CORRUPTION_API
517
518namespace {
519#ifdef V8_OS_LINUX
520
521void PrintToStderr(const char* output) {
522 // NOTE: This code MUST be async-signal safe.
523 // NO malloc or stdio is allowed here.
524 ssize_t return_val = write(STDERR_FILENO, output, strlen(output));
525 USE(return_val);
526}
527
528[[noreturn]] void FilterCrash(const char* reason) {
529 // NOTE: This code MUST be async-signal safe.
530 // NO malloc or stdio is allowed here.
531 PrintToStderr(reason);
532 // In sandbox fuzzing mode, we want to exit with a non-zero status to
533 // indicate to the fuzzer that the sample "failed" (ran into an unrecoverable
534 // error) and should probably not be mutated further. Otherwise, we exit with
535 // zero, which is for example needed for regression tests to make them "pass"
536 // when no sandbox violation is detected.
537 int status =
538 SandboxTesting::mode() == SandboxTesting::Mode::kForFuzzing ? -1 : 0;
539 _exit(status);
540}
541
542// Signal handler checking whether a memory access violation happened inside or
543// outside of the sandbox address space. If inside, the signal is ignored and
544// the process terminated normally, in the latter case the original signal
545// handler is restored and the signal delivered again.
546struct sigaction g_old_sigabrt_handler, g_old_sigtrap_handler,
547 g_old_sigbus_handler, g_old_sigsegv_handler;
548
549void UninstallCrashFilter() {
550 // NOTE: This code MUST be async-signal safe.
551 // NO malloc or stdio is allowed here.
552
553 // It's important that we always restore all signal handlers. For example, if
554 // we forward a SIGSEGV to Asan's signal handler, that signal handler may
555 // terminate the process with SIGABRT, which we must then *not* ignore.
556 //
557 // Should any of the sigaction calls below ever fail, the default signal
558 // handler will be invoked (due to SA_RESETHAND) and will terminate the
559 // process, so there's no need to attempt to handle that condition.
560 sigaction(SIGABRT, &g_old_sigabrt_handler, nullptr);
561 sigaction(SIGTRAP, &g_old_sigtrap_handler, nullptr);
562 sigaction(SIGBUS, &g_old_sigbus_handler, nullptr);
563 sigaction(SIGSEGV, &g_old_sigsegv_handler, nullptr);
564
565 // We should also uninstall the sanitizer death callback as our crash filter
566 // may hand a crash over to ASan, which should then not enter our crash
567 // filtering logic a second time.
568#ifdef V8_USE_ADDRESS_SANITIZER
569 __sanitizer_set_death_callback(nullptr);
570#endif
571}
572
573void CrashFilter(int signal, siginfo_t* info, void* void_context) {
574 // NOTE: This code MUST be async-signal safe.
575 // NO malloc or stdio is allowed here.
576
577 if (signal == SIGABRT) {
578 // SIGABRT typically indicates a failed CHECK or similar, which is harmless.
579 FilterCrash("Caught harmless signal (SIGABRT). Exiting process...\n");
580 }
581
582 if (signal == SIGTRAP) {
583 // Similarly, SIGTRAP may for example indicate UNREACHABLE code.
584 FilterCrash("Caught harmless signal (SIGTRAP). Exiting process...\n");
585 }
586
587 Address faultaddr = reinterpret_cast<Address>(info->si_addr);
588
589 if (Sandbox::current()->Contains(faultaddr)) {
590 FilterCrash(
591 "Caught harmless memory access violation (inside sandbox address "
592 "space). Exiting process...\n");
593 }
594
595 if (info->si_code == SI_KERNEL && faultaddr == 0) {
596 // This combination appears to indicate a crash at a non-canonical address
597 // on Linux. Crashes at non-canonical addresses are for example caused by
598 // failed external pointer type checks. Memory accesses that _always_ land
599 // at a non-canonical address are not exploitable and so these are filtered
600 // out here. However, testcases need to be written with this in mind and
601 // must cause crashes at valid addresses.
602 FilterCrash(
603 "Caught harmless memory access violation (non-canonical address). "
604 "Exiting process...\n");
605 }
606
607 if (faultaddr >= 0x8000'0000'0000'0000ULL) {
608 // On Linux, it appears that the kernel will still report valid (i.e.
609 // canonical) kernel space addresses via the si_addr field, so we need to
610 // handle these separately. We've already filtered out non-canonical
611 // addresses above, so here we can just test if the most-significant bit of
612 // the address is set, and if so assume that it's a kernel address.
613 FilterCrash(
614 "Caught harmless memory access violation (kernel space address). "
615 "Exiting process...\n");
616 }
617
618 if (faultaddr < 0x1000) {
619 // Nullptr dereferences are harmless as nothing can be mapped there. We use
620 // the typical page size (which is also the default value of mmap_min_addr
621 // on Linux) to determine what counts as a nullptr dereference here.
622 FilterCrash(
623 "Caught harmless memory access violation (nullptr dereference). "
624 "Exiting process...\n");
625 }
626
627 if (faultaddr < 4ULL * GB) {
628 // Currently we also ignore access violations in the first 4GB of the
629 // virtual address space. See crbug.com/1470641 for more details.
630 FilterCrash(
631 "Caught harmless memory access violation (first 4GB of virtual address "
632 "space). Exiting process...\n");
633 }
634
635 // Stack overflow detection.
636 //
637 // On Linux, we generally have two types of stacks:
638 // 1. The main thread's stack, allocated by the kernel, and
639 // 2. The stacks of any other thread, allocated by the application
640 //
641 // These stacks differ in some ways, and that affects the way stack overflows
642 // (caused e.g. by unbounded recursion) materialize: for (1) the kernel will
643 // use a "gap" region below the stack segment, i.e. an unmapped area into
644 // which the kernel itself will not place any mappings and into which the
645 // stack cannot grow. A stack overflow therefore crashes with a SEGV_MAPERR.
646 // On the other hand, for (2) the application is responsible for allocating
647 // the stack and therefore also for allocating any guard regions around it.
648 // As these guard regions must be regular mappings (with PROT_NONE), a stack
649 // overflow will crash with a SEGV_ACCERR.
650 //
651 // It's relatively hard to reliably and accurately detect stack overflow, so
652 // here we use a simple heuristic: did we crash on any kind of access
653 // violation on an address just below the current thread's stack region. This
654 // may cause both false positives (e.g. an access not through the stack
655 // pointer register that happens to also land just below the stack) and false
656 // negatives (e.g. a stack overflow on the main thread that "jumps over" the
657 // first page of the gap region), but is probably good enough in practice.
658 pthread_attr_t attr;
659 int pthread_error = pthread_getattr_np(pthread_self(), &attr);
660 if (!pthread_error) {
661 uintptr_t stack_base;
662 size_t stack_size;
663 pthread_error = pthread_attr_getstack(
664 &attr, reinterpret_cast<void**>(&stack_base), &stack_size);
665 // The main thread's stack on Linux typically has a fairly large gap region
666 // (1MB by default), but other thread's stacks usually have smaller guard
667 // regions so here we're conservative and assume that the guard region
668 // consists only of a single page.
669 const size_t kMinStackGuardRegionSize = sysconf(_SC_PAGESIZE);
670 uintptr_t stack_guard_region_start = stack_base - kMinStackGuardRegionSize;
671 uintptr_t stack_guard_region_end = stack_base;
672 if (!pthread_error && stack_guard_region_start <= faultaddr &&
673 faultaddr < stack_guard_region_end) {
674 FilterCrash("Caught harmless stack overflow. Exiting process...\n");
675 }
676 }
677
678 if (info->si_code == SEGV_ACCERR) {
679 // This indicates an access to a valid mapping but with insufficient
680 // permissions, for example accessing a region mapped with PROT_NONE, or
681 // writing to a read-only mapping.
682 //
683 // The sandbox relies on such accesses crashing in a safe way in some
684 // cases. For example, the accesses into the various pointer tables are not
685 // bounds checked, but instead it is guaranteed that an out-of-bounds
686 // access will hit a PROT_NONE mapping.
687 //
688 // Memory accesses that _always_ cause such a permission violation are not
689 // exploitable and the crashes are therefore filtered out here. However,
690 // testcases need to be written with this behavior in mind and should
691 // typically try to access non-existing memory to demonstrate the ability
692 // to escape from the sandbox.
693 FilterCrash(
694 "Caught harmless memory access violation (memory permission "
695 "violation). Exiting process...\n");
696 }
697
698 // Otherwise it's a sandbox violation, so restore the original signal
699 // handlers, then return from this handler. The faulting instruction will be
700 // re-executed and will again trigger the access violation, but now the
701 // signal will be handled by the original signal handler.
702 UninstallCrashFilter();
703
704 PrintToStderr("\n## V8 sandbox violation detected!\n\n");
705}
706
707#ifdef V8_USE_ADDRESS_SANITIZER
708void AsanFaultHandler() {
709 Address faultaddr = reinterpret_cast<Address>(__asan_get_report_address());
710
711 if (faultaddr == kNullAddress) {
712 FilterCrash(
713 "Caught ASan fault without a fault address. Ignoring it as we cannot "
714 "check if it is a sandbox violation. Exiting process...\n");
715 }
716
717 if (Sandbox::current()->Contains(faultaddr)) {
718 FilterCrash(
719 "Caught harmless ASan fault (inside sandbox address space). Exiting "
720 "process...\n");
721 }
722
723 // Asan may report the failure via abort(), so we should also restore the
724 // original signal handlers here.
725 UninstallCrashFilter();
726
727 PrintToStderr("\n## V8 sandbox violation detected!\n\n");
728}
729#endif // V8_USE_ADDRESS_SANITIZER
730
731void InstallCrashFilter() {
732 // Register an alternate stack for signal delivery so that signal handlers
733 // can run properly even if for example the stack pointer has been corrupted
734 // or the stack has overflowed.
735 // Note that the alternate stack is currently only registered for the main
736 // thread. Stack pointer corruption or stack overflows on background threads
737 // may therefore still cause the signal handler to crash.
738 VirtualAddressSpace* vas = GetPlatformVirtualAddressSpace();
739 Address alternate_stack =
740 vas->AllocatePages(VirtualAddressSpace::kNoHint, SIGSTKSZ,
741 vas->page_size(), PagePermissions::kReadWrite);
742 CHECK_NE(alternate_stack, kNullAddress);
743 stack_t signalstack = {
744 .ss_sp = reinterpret_cast<void*>(alternate_stack),
745 .ss_flags = 0,
746 .ss_size = static_cast<size_t>(SIGSTKSZ),
747 };
748 CHECK_EQ(sigaltstack(&signalstack, nullptr), 0);
749
750 struct sigaction action;
751 memset(&action, 0, sizeof(action));
752 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
753 action.sa_sigaction = &CrashFilter;
754 sigemptyset(&action.sa_mask);
755
756 bool success = true;
757 success &= (sigaction(SIGABRT, &action, &g_old_sigabrt_handler) == 0);
758 success &= (sigaction(SIGTRAP, &action, &g_old_sigtrap_handler) == 0);
759 success &= (sigaction(SIGBUS, &action, &g_old_sigbus_handler) == 0);
760 success &= (sigaction(SIGSEGV, &action, &g_old_sigsegv_handler) == 0);
761 CHECK(success);
762
763#if defined(V8_USE_ADDRESS_SANITIZER)
764 __sanitizer_set_death_callback(&AsanFaultHandler);
765#elif defined(V8_USE_MEMORY_SANITIZER) || \
766 defined(V8_USE_UNDEFINED_BEHAVIOR_SANITIZER)
767 // TODO(saelo): can we also test for the other sanitizers here somehow?
768 FATAL("The sandbox crash filter currently only supports AddressSanitizer");
769#endif
770}
771
772#endif // V8_OS_LINUX
773} // namespace
774
775void SandboxTesting::Enable(Mode mode) {
776 CHECK_EQ(mode_, Mode::kDisabled);
777 CHECK_NE(mode, Mode::kDisabled);
778 CHECK(Sandbox::current()->is_initialized());
779
780 mode_ = mode;
781
782 fprintf(stderr,
783 "Sandbox testing mode is enabled. Only sandbox violations will be "
784 "reported, all other crashes will be ignored.\n");
785
786#ifdef V8_OS_LINUX
787 InstallCrashFilter();
788#else
789 FATAL("The sandbox crash filter is currently only available on Linux");
790#endif // V8_OS_LINUX
791}
792
793SandboxTesting::InstanceTypeMap& SandboxTesting::GetInstanceTypeMap() {
794 // This mechanism is currently very crude and needs to be manually maintained
795 // and extended (e.g. when adding a js test for the sandbox). In the future,
796 // it would be nice to somehow automatically generate this map from the
797 // object definitions and also support the class inheritance hierarchy.
798 static base::LeakyObject<InstanceTypeMap> g_known_instance_types;
799 auto& types = *g_known_instance_types.get();
800 bool is_initialized = types.size() != 0;
801 if (!is_initialized) {
802 types["JS_OBJECT_TYPE"] = JS_OBJECT_TYPE;
803 types["JS_FUNCTION_TYPE"] = JS_FUNCTION_TYPE;
804 types["JS_ARRAY_TYPE"] = JS_ARRAY_TYPE;
805 types["JS_ARRAY_BUFFER_TYPE"] = JS_ARRAY_BUFFER_TYPE;
806 types["JS_TYPED_ARRAY_TYPE"] = JS_TYPED_ARRAY_TYPE;
807 types["SEQ_ONE_BYTE_STRING_TYPE"] = SEQ_ONE_BYTE_STRING_TYPE;
808 types["SEQ_TWO_BYTE_STRING_TYPE"] = SEQ_TWO_BYTE_STRING_TYPE;
809 types["INTERNALIZED_ONE_BYTE_STRING_TYPE"] =
811 types["SLICED_ONE_BYTE_STRING_TYPE"] = SLICED_ONE_BYTE_STRING_TYPE;
812 types["CONS_ONE_BYTE_STRING_TYPE"] = CONS_ONE_BYTE_STRING_TYPE;
813 types["SHARED_FUNCTION_INFO_TYPE"] = SHARED_FUNCTION_INFO_TYPE;
814 types["SCRIPT_TYPE"] = SCRIPT_TYPE;
815#ifdef V8_ENABLE_WEBASSEMBLY
816 types["WASM_MODULE_OBJECT_TYPE"] = WASM_MODULE_OBJECT_TYPE;
817 types["WASM_INSTANCE_OBJECT_TYPE"] = WASM_INSTANCE_OBJECT_TYPE;
818 types["WASM_FUNC_REF_TYPE"] = WASM_FUNC_REF_TYPE;
819 types["WASM_TABLE_OBJECT_TYPE"] = WASM_TABLE_OBJECT_TYPE;
820#endif // V8_ENABLE_WEBASSEMBLY
821 }
822 return types;
823}
824
825SandboxTesting::FieldOffsetMap& SandboxTesting::GetFieldOffsetMap() {
826 // This mechanism is currently very crude and needs to be manually maintained
827 // and extended (e.g. when adding a js test for the sandbox). In the future,
828 // it would be nice to somehow automatically generate this map from the
829 // object definitions and also support the class inheritance hierarchy.
830 static base::LeakyObject<FieldOffsetMap> g_known_fields;
831 auto& fields = *g_known_fields.get();
832 bool is_initialized = fields.size() != 0;
833 if (!is_initialized) {
834#ifdef V8_ENABLE_LEAPTIERING
835 fields[JS_FUNCTION_TYPE]["dispatch_handle"] =
836 JSFunction::kDispatchHandleOffset;
837#endif // V8_ENABLE_LEAPTIERING
838 fields[JS_FUNCTION_TYPE]["shared_function_info"] =
839 JSFunction::kSharedFunctionInfoOffset;
840 fields[JS_ARRAY_TYPE]["elements"] = JSArray::kElementsOffset;
841 fields[JS_ARRAY_TYPE]["length"] = JSArray::kLengthOffset;
842 fields[JS_TYPED_ARRAY_TYPE]["length"] = JSTypedArray::kRawLengthOffset;
843 fields[JS_TYPED_ARRAY_TYPE]["byte_length"] =
844 JSTypedArray::kRawByteLengthOffset;
845 fields[JS_TYPED_ARRAY_TYPE]["byte_offset"] =
846 JSTypedArray::kRawByteOffsetOffset;
847 fields[JS_TYPED_ARRAY_TYPE]["external_pointer"] =
848 JSTypedArray::kExternalPointerOffset;
849 fields[JS_TYPED_ARRAY_TYPE]["base_pointer"] =
850 JSTypedArray::kBasePointerOffset;
851 fields[SEQ_ONE_BYTE_STRING_TYPE]["length"] =
852 offsetof(SeqOneByteString, length_);
853 fields[SEQ_TWO_BYTE_STRING_TYPE]["hash"] =
854 offsetof(SeqTwoByteString, raw_hash_field_);
855 fields[SEQ_TWO_BYTE_STRING_TYPE]["length"] =
856 offsetof(SeqTwoByteString, length_);
857 fields[INTERNALIZED_ONE_BYTE_STRING_TYPE]["length"] =
858 offsetof(InternalizedString, length_);
859 fields[SLICED_ONE_BYTE_STRING_TYPE]["parent"] =
860 offsetof(SlicedString, parent_);
861 fields[CONS_ONE_BYTE_STRING_TYPE]["length"] = offsetof(ConsString, length_);
862 fields[CONS_ONE_BYTE_STRING_TYPE]["first"] = offsetof(ConsString, first_);
863 fields[CONS_ONE_BYTE_STRING_TYPE]["second"] = offsetof(ConsString, second_);
864 fields[SHARED_FUNCTION_INFO_TYPE]["trusted_function_data"] =
865 SharedFunctionInfo::kTrustedFunctionDataOffset;
866 fields[SHARED_FUNCTION_INFO_TYPE]["length"] =
867 SharedFunctionInfo::kLengthOffset;
868 fields[SHARED_FUNCTION_INFO_TYPE]["formal_parameter_count"] =
869 SharedFunctionInfo::kFormalParameterCountOffset;
870 fields[SCRIPT_TYPE]["wasm_managed_native_module"] =
871 Script::kEvalFromPositionOffset;
872#ifdef V8_ENABLE_WEBASSEMBLY
873 fields[WASM_MODULE_OBJECT_TYPE]["managed_native_module"] =
874 WasmModuleObject::kManagedNativeModuleOffset;
875 fields[WASM_MODULE_OBJECT_TYPE]["script"] = WasmModuleObject::kScriptOffset;
876 fields[WASM_INSTANCE_OBJECT_TYPE]["module_object"] =
877 WasmInstanceObject::kModuleObjectOffset;
878 fields[WASM_FUNC_REF_TYPE]["trusted_internal"] =
879 WasmFuncRef::kTrustedInternalOffset;
880 fields[WASM_TABLE_OBJECT_TYPE]["entries"] = WasmTableObject::kEntriesOffset;
881 fields[WASM_TABLE_OBJECT_TYPE]["current_length"] =
882 WasmTableObject::kCurrentLengthOffset;
883 fields[WASM_TABLE_OBJECT_TYPE]["maximum_length"] =
884 WasmTableObject::kMaximumLengthOffset;
885 fields[WASM_TABLE_OBJECT_TYPE]["raw_type"] =
886 WasmTableObject::kRawTypeOffset;
887#endif // V8_ENABLE_WEBASSEMBLY
888 }
889 return fields;
890}
891
892#endif // V8_ENABLE_SANDBOX
893
894} // namespace internal
895} // namespace v8
PropertyT * getter
static void EmptyDeleter(void *data, size_t length, void *deleter_data)
Definition api.cc:4023
static Local< FunctionTemplate > New(Isolate *isolate, FunctionCallback callback=nullptr, Local< Value > data=Local< Value >(), Local< Signature > signature=Local< Signature >(), int length=0, ConstructorBehavior behavior=ConstructorBehavior::kAllow, SideEffectType side_effect_type=SideEffectType::kHasSideEffect, const CFunction *c_function=nullptr, uint16_t instance_type=0, uint16_t allowed_receiver_instance_type_range_start=0, uint16_t allowed_receiver_instance_type_range_end=0)
Definition api.cc:1101
static Local< Integer > NewFromUnsigned(Isolate *isolate, uint32_t value)
Definition api.cc:9579
Local< Context > GetCurrentContext()
Definition api.cc:9756
static Local< Number > New(Isolate *isolate, double value)
Definition api.cc:9557
static V8_WARN_UNUSED_RESULT MaybeLocal< String > NewFromUtf8(Isolate *isolate, const char *data, NewStringType type=NewStringType::kNormal, int length=-1)
Definition api.cc:7593
static v8::internal::Handle< To > OpenHandle(v8::Local< From > handle)
Definition api.h:274
static constexpr Address kNoHint
static V8_WARN_UNUSED_RESULT MaybeHandle< JSFunction > InstantiateFunction(Isolate *isolate, DirectHandle< NativeContext > native_context, DirectHandle< FunctionTemplateInfo > data, MaybeDirectHandle< Name > maybe_name={})
static std::unique_ptr< BackingStore > WrapAllocation(void *allocation_base, size_t allocation_length, v8::BackingStore::DeleterCallback deleter, void *deleter_data, SharedFlag shared)
static Tagged< HeapObject > FromAddress(Address address)
static V8_EXPORT_PRIVATE void AddProperty(Isolate *isolate, DirectHandle< JSObject > object, DirectHandle< Name > name, DirectHandle< Object > value, PropertyAttributes attributes)
static V8_EXPORT_PRIVATE MaybeDirectHandle< Object > DefineOwnAccessorIgnoreAttributes(DirectHandle< JSObject > object, DirectHandle< Name > name, DirectHandle< Object > getter, DirectHandle< Object > setter, PropertyAttributes attributes)
static V8_INLINE MemoryChunkMetadata * FromHeapObject(Tagged< HeapObject > o)
RecordWriteMode const mode_
LineAndColumn current
Isolate * isolate
int32_t offset
ZoneVector< RpoNumber > & result
const int length_
Definition mul-fft.cc:473
unsigned short uint16_t
Definition unicode.cc:39
V8_INLINE IndirectHandle< T > handle(Tagged< T > object, Isolate *isolate)
Definition handles-inl.h:72
kInterpreterTrampolineOffset Tagged< HeapObject >
constexpr uint64_t kMaxSafeIntegerUint64
Definition globals.h:1983
too high values may cause the compiler to set high thresholds for inlining to as much as possible avoid inlined allocation of objects that cannot escape trace load stores from virtual maglev objects use TurboFan fast string builder analyze liveness of environment slots and zap dead values trace TurboFan load elimination emit data about basic block usage in builtins to this enable builtin reordering when run mksnapshot flag for emit warnings when applying builtin profile data verify register allocation in TurboFan randomly schedule instructions to stress dependency tracking enable store store elimination in TurboFan rewrite far to near simulate GC compiler thread race related to allow float parameters to be passed in simulator mode JS Wasm Run additional turbo_optimize_inlined_js_wasm_wrappers enable experimental feedback collection in generic lowering enable Turboshaft s WasmLoadElimination enable Turboshaft s low level load elimination for JS enable Turboshaft s escape analysis for string concatenation use enable Turbolev features that we want to ship in the not too far future trace individual Turboshaft reduction steps trace intermediate Turboshaft reduction steps invocation count threshold for early optimization Enables optimizations which favor memory size over execution speed Enables sampling allocation profiler with X as a sample interval min size of a semi the new space consists of two semi spaces max size of the Collect garbage after Collect garbage after keeps maps alive for< n > old space garbage collections print one detailed trace line in allocation gc speed threshold for starting incremental marking via a task in percent of available threshold for starting incremental marking immediately in percent of available Use a single schedule for determining a marking schedule between JS and C objects schedules the minor GC task with kUserVisible priority max worker number of concurrent for NumberOfWorkerThreads start background threads that allocate memory concurrent_array_buffer_sweeping use parallel threads to clear weak refs in the atomic pause trace progress of the incremental marking trace object counts and memory usage report a tick only when allocated zone memory changes by this amount TracingFlags::gc_stats TracingFlags::gc_stats track native contexts that are expected to be garbage collected verify heap pointers before and after GC memory reducer runs GC with ReduceMemoryFootprint flag Maximum number of memory reducer GCs scheduled Old gen GC speed is computed directly from gc tracer counters Perform compaction on full GCs based on V8 s default heuristics Perform compaction on every full GC Perform code space compaction when finalizing a full GC with stack Stress GC compaction to flush out bugs with moving objects flush of baseline code when it has not been executed recently Use time base code flushing instead of age Use a progress bar to scan large objects in increments when incremental marking is active force incremental marking for small heaps and run it more often force marking at random points between and force scavenge at random points between and reclaim otherwise unreachable unmodified wrapper objects when possible less compaction in non memory reducing mode use high priority threads for concurrent Marking Test mode only flag It allows an unit test to select evacuation candidates use incremental marking for CppHeap cppheap_concurrent_marking c value for membalancer A special constant to balance between memory and space tradeoff The smaller the more memory it uses enable use of SSE4 instructions if available enable use of AVX VNNI instructions if available enable use of POPCNT instruction if available force all emitted branches to be in long mode(MIPS/PPC only)") DEFINE_BOOL(partial_constant_pool
bool V8_EXPORT ValidateCallbackInfo(const FunctionCallbackInfo< void > &info)
Definition api.cc:12301
v8::VirtualAddressSpace * GetPlatformVirtualAddressSpace()
Definition allocation.cc:71
@ SLICED_ONE_BYTE_STRING_TYPE
@ INTERNALIZED_ONE_BYTE_STRING_TYPE
const int kHeapObjectTag
Definition v8-internal.h:72
V8_INLINE constexpr bool IsHeapObject(TaggedImpl< kRefType, StorageType > obj)
Definition objects.h:669
static constexpr Address kNullAddress
Definition v8-internal.h:53
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
Local< T > Handle
bool ToLocal(v8::internal::MaybeDirectHandle< v8::internal::Object > maybe, Local< T > *local)
Definition api.h:303
ConstructorBehavior
void(*)(const FunctionCallbackInfo< Value > &info) FunctionCallback
v8::Local< T > ToApiHandle(v8::internal::DirectHandle< v8::internal::Object > obj)
Definition api.h:297
static i::DirectHandle< i::FunctionTemplateInfo > NewFunctionTemplate(i::Isolate *i_isolate, FunctionCallback func, bool has_prototype, SideEffectType side_effect_type=SideEffectType::kHasSideEffect)
Definition wasm-js.cc:3223
BytecodeSequenceNode * parent_
#define FATAL(...)
Definition logging.h:47
#define CHECK(condition)
Definition logging.h:124
#define CHECK_LE(lhs, rhs)
#define CHECK_NE(lhs, rhs)
#define CHECK_EQ(lhs, rhs)
#define DCHECK(condition)
Definition logging.h:482
#define USE(...)
Definition macros.h:293
wasm::ValueType type