blob: 478ee45aee5af764c0288efb58f462afca1a777d [file] [log] [blame]
Kaido Kertf309f9a2021-04-30 12:09:15 -07001// Copyright 2018 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
5#include "include/v8config.h"
6
7#if V8_OS_LINUX || V8_OS_FREEBSD
8#include <signal.h>
9#include <ucontext.h>
10#elif V8_OS_MACOSX
11#include <signal.h>
12#include <sys/ucontext.h>
13#elif V8_OS_WIN
14#include <windows.h>
15#endif
16
17#include "testing/gtest/include/gtest/gtest.h"
18
19#if V8_OS_POSIX
20#include "include/v8-wasm-trap-handler-posix.h"
21#elif V8_OS_WIN
22#include "include/v8-wasm-trap-handler-win.h"
23#endif
24#include "src/base/page-allocator.h"
25#include "src/codegen/assembler-inl.h"
26#include "src/codegen/macro-assembler-inl.h"
27#include "src/execution/simulator.h"
28#include "src/objects/backing-store.h"
29#include "src/trap-handler/trap-handler.h"
30#include "src/utils/allocation.h"
31#include "src/utils/vector.h"
32#include "src/wasm/wasm-engine.h"
33
34#include "test/common/assembler-tester.h"
35#include "test/unittests/test-utils.h"
36
37namespace v8 {
38namespace internal {
39namespace wasm {
40
41namespace {
42constexpr Register scratch = r10;
43bool g_test_handler_executed = false;
44#if V8_OS_LINUX || V8_OS_MACOSX || V8_OS_FREEBSD
45struct sigaction g_old_segv_action;
46struct sigaction g_old_fpe_action;
47struct sigaction g_old_bus_action; // We get SIGBUS on Mac sometimes.
48#elif V8_OS_WIN
49void* g_registered_handler = nullptr;
50#endif
51
52// The recovery address allows us to recover from an intentional crash.
53Address g_recovery_address;
54// Flag to indicate if the test handler should call the trap handler as a first
55// chance handler.
56bool g_use_as_first_chance_handler = false;
57} // namespace
58
59#define __ masm.
60
61enum TrapHandlerStyle : int {
62 // The test uses the default trap handler of V8.
63 kDefault = 0,
64 // The test installs the trap handler callback in its own test handler.
65 kCallback = 1
66};
67
68std::string PrintTrapHandlerTestParam(
69 ::testing::TestParamInfo<TrapHandlerStyle> info) {
70 switch (info.param) {
71 case kDefault:
72 return "DefaultTrapHandler";
73 case kCallback:
74 return "Callback";
75 }
76 UNREACHABLE();
77}
78
79class TrapHandlerTest : public TestWithIsolate,
80 public ::testing::WithParamInterface<TrapHandlerStyle> {
81 protected:
82 void SetUp() override {
83 InstallFallbackHandler();
84 SetupTrapHandler(GetParam());
85 backing_store_ = BackingStore::AllocateWasmMemory(i_isolate(), 1, 1,
86 SharedFlag::kNotShared);
87 CHECK(backing_store_);
88 CHECK(backing_store_->has_guard_regions());
89 // The allocated backing store ends with a guard page.
90 crash_address_ = reinterpret_cast<Address>(backing_store_->buffer_start()) +
91 backing_store_->byte_length() + 32;
92 // Allocate a buffer for the generated code.
93 buffer_ = AllocateAssemblerBuffer(AssemblerBase::kDefaultBufferSize,
94 GetRandomMmapAddr());
95
96 InitRecoveryCode();
97 }
98
99 void InstallFallbackHandler() {
100#if V8_OS_LINUX || V8_OS_MACOSX || V8_OS_FREEBSD
101 // Set up a signal handler to recover from the expected crash.
102 struct sigaction action;
103 action.sa_sigaction = SignalHandler;
104 sigemptyset(&action.sa_mask);
105 action.sa_flags = SA_SIGINFO;
106 // SIGSEGV happens for wasm oob memory accesses on Linux.
107 CHECK_EQ(0, sigaction(SIGSEGV, &action, &g_old_segv_action));
108 // SIGBUS happens for wasm oob memory accesses on macOS.
109 CHECK_EQ(0, sigaction(SIGBUS, &action, &g_old_bus_action));
110 // SIGFPE to simulate crashes which are not handled by the trap handler.
111 CHECK_EQ(0, sigaction(SIGFPE, &action, &g_old_fpe_action));
112#elif V8_OS_WIN
113 g_registered_handler =
114 AddVectoredExceptionHandler(/*first=*/0, TestHandler);
115#endif
116 }
117
118 void TearDown() override {
119 // We should always have left wasm code.
120 CHECK(!GetThreadInWasmFlag());
121 buffer_.reset();
122 recovery_buffer_.reset();
123 backing_store_.reset();
124
125 // Clean up the trap handler
126 trap_handler::RemoveTrapHandler();
127 if (!g_test_handler_executed) {
128#if V8_OS_LINUX || V8_OS_MACOSX || V8_OS_FREEBSD
129 // The test handler cleans up the signal handler setup in the test. If the
130 // test handler was not called, we have to do the cleanup ourselves.
131 CHECK_EQ(0, sigaction(SIGSEGV, &g_old_segv_action, nullptr));
132 CHECK_EQ(0, sigaction(SIGFPE, &g_old_fpe_action, nullptr));
133 CHECK_EQ(0, sigaction(SIGBUS, &g_old_bus_action, nullptr));
134#elif V8_OS_WIN
135 RemoveVectoredExceptionHandler(g_registered_handler);
136 g_registered_handler = nullptr;
137#endif
138 }
139 }
140
141 void InitRecoveryCode() {
142 // Create a code snippet where we can jump to to recover from a signal or
143 // exception. The code snippet only consists of a return statement.
144 recovery_buffer_ = AllocateAssemblerBuffer(
145 AssemblerBase::kDefaultBufferSize, GetRandomMmapAddr());
146
147 MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
148 recovery_buffer_->CreateView());
149 int recovery_offset = __ pc_offset();
150 __ Pop(scratch);
151 __ Ret();
152 CodeDesc desc;
153 masm.GetCode(nullptr, &desc);
154 recovery_buffer_->MakeExecutable();
155 g_recovery_address =
156 reinterpret_cast<Address>(desc.buffer + recovery_offset);
157 }
158
159#if V8_OS_LINUX || V8_OS_MACOSX || V8_OS_FREEBSD
160 static void SignalHandler(int signal, siginfo_t* info, void* context) {
161 if (g_use_as_first_chance_handler) {
162 if (v8::TryHandleWebAssemblyTrapPosix(signal, info, context)) {
163 return;
164 }
165 }
166
167 // Reset the signal handler, to avoid that this signal handler is called
168 // repeatedly.
169 sigaction(SIGSEGV, &g_old_segv_action, nullptr);
170 sigaction(SIGFPE, &g_old_fpe_action, nullptr);
171 sigaction(SIGBUS, &g_old_bus_action, nullptr);
172
173 g_test_handler_executed = true;
174 // Set the $rip to the recovery code.
175 ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
176#if V8_OS_LINUX
177 uc->uc_mcontext.gregs[REG_RIP] = g_recovery_address;
178#elif V8_OS_MACOSX
179 uc->uc_mcontext->__ss.__rip = g_recovery_address;
180#elif V8_OS_FREEBSD
181 uc->uc_mcontext.mc_rip = g_recovery_address;
182#else
183#error Unsupported platform
184#endif
185 }
186#endif
187
188#if V8_OS_WIN
189 static LONG WINAPI TestHandler(EXCEPTION_POINTERS* exception) {
190 if (g_use_as_first_chance_handler) {
191 if (v8::TryHandleWebAssemblyTrapWindows(exception)) {
192 return EXCEPTION_CONTINUE_EXECUTION;
193 }
194 }
195 RemoveVectoredExceptionHandler(g_registered_handler);
196 g_registered_handler = nullptr;
197 g_test_handler_executed = true;
198 exception->ContextRecord->Rip = g_recovery_address;
199 return EXCEPTION_CONTINUE_EXECUTION;
200 }
201#endif
202
203 void SetupTrapHandler(TrapHandlerStyle style) {
204 bool use_default_handler = style == kDefault;
205 g_use_as_first_chance_handler = !use_default_handler;
206 CHECK(v8::V8::EnableWebAssemblyTrapHandler(use_default_handler));
207 }
208
209 public:
210 void GenerateSetThreadInWasmFlagCode(MacroAssembler* masm) {
211 masm->Move(scratch,
212 i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
213 RelocInfo::NONE);
214 masm->movl(MemOperand(scratch, 0), Immediate(1));
215 }
216
217 void GenerateResetThreadInWasmFlagCode(MacroAssembler* masm) {
218 masm->Move(scratch,
219 i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
220 RelocInfo::NONE);
221 masm->movl(MemOperand(scratch, 0), Immediate(0));
222 }
223
224 bool GetThreadInWasmFlag() {
225 return *reinterpret_cast<int*>(
226 trap_handler::GetThreadInWasmThreadLocalAddress());
227 }
228
229 // Execute the code in buffer.
230 void ExecuteBuffer() {
231 buffer_->MakeExecutable();
232 GeneratedCode<void>::FromAddress(
233 i_isolate(), reinterpret_cast<Address>(buffer_->start()))
234 .Call();
235 CHECK(!g_test_handler_executed);
236 }
237
238 // Execute the code in buffer. We expect a crash which we recover from in the
239 // test handler.
240 void ExecuteExpectCrash(TestingAssemblerBuffer* buffer,
241 bool check_wasm_flag = true) {
242 CHECK(!g_test_handler_executed);
243 buffer->MakeExecutable();
244 GeneratedCode<void>::FromAddress(i_isolate(),
245 reinterpret_cast<Address>(buffer->start()))
246 .Call();
247 CHECK(g_test_handler_executed);
248 g_test_handler_executed = false;
249 if (check_wasm_flag) CHECK(!GetThreadInWasmFlag());
250 }
251
252 bool test_handler_executed() { return g_test_handler_executed; }
253
254 // The backing store used for testing the trap handler.
255 std::unique_ptr<BackingStore> backing_store_;
256
257 // Address within the guard region of the wasm memory. Accessing this memory
258 // address causes a signal or exception.
259 Address crash_address_;
260
261 // Buffer for generated code.
262 std::unique_ptr<TestingAssemblerBuffer> buffer_;
263 // Buffer for the code for the landing pad of the test handler.
264 std::unique_ptr<TestingAssemblerBuffer> recovery_buffer_;
265};
266
267// TODO(almuthanna): These tests were skipped because they cause a crash when
268// they are ran on Fuchsia. This issue should be solved later on
269// Ticket: https://crbug.com/1028617
270#if !defined(V8_TARGET_OS_FUCHSIA)
271TEST_P(TrapHandlerTest, TestTrapHandlerRecovery) {
272 // Test that the wasm trap handler can recover a memory access violation in
273 // wasm code (we fake the wasm code and the access violation).
274 MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
275 buffer_->CreateView());
276 __ Push(scratch);
277 GenerateSetThreadInWasmFlagCode(&masm);
278 __ Move(scratch, crash_address_, RelocInfo::NONE);
279 int crash_offset = __ pc_offset();
280 __ testl(MemOperand(scratch, 0), Immediate(1));
281 int recovery_offset = __ pc_offset();
282 GenerateResetThreadInWasmFlagCode(&masm);
283 __ Pop(scratch);
284 __ Ret();
285 CodeDesc desc;
286 masm.GetCode(nullptr, &desc);
287
288 trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
289 recovery_offset};
290 trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
291 desc.instr_size, 1, &protected_instruction);
292
293 ExecuteBuffer();
294}
295
296TEST_P(TrapHandlerTest, TestReleaseHandlerData) {
297 // Test that after we release handler data in the trap handler, it cannot
298 // recover from the specific memory access violation anymore.
299 MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
300 buffer_->CreateView());
301 __ Push(scratch);
302 GenerateSetThreadInWasmFlagCode(&masm);
303 __ Move(scratch, crash_address_, RelocInfo::NONE);
304 int crash_offset = __ pc_offset();
305 __ testl(MemOperand(scratch, 0), Immediate(1));
306 int recovery_offset = __ pc_offset();
307 GenerateResetThreadInWasmFlagCode(&masm);
308 __ Pop(scratch);
309 __ Ret();
310 CodeDesc desc;
311 masm.GetCode(nullptr, &desc);
312
313 trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
314 recovery_offset};
315 int handler_id = trap_handler::RegisterHandlerData(
316 reinterpret_cast<Address>(desc.buffer), desc.instr_size, 1,
317 &protected_instruction);
318
319 ExecuteBuffer();
320
321 // Deregister from the trap handler. The trap handler should not do the
322 // recovery now.
323 trap_handler::ReleaseHandlerData(handler_id);
324
325 ExecuteExpectCrash(buffer_.get());
326}
327
328TEST_P(TrapHandlerTest, TestNoThreadInWasmFlag) {
329 // That that if the thread_in_wasm flag is not set, the trap handler does not
330 // get active.
331 MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
332 buffer_->CreateView());
333 __ Push(scratch);
334 __ Move(scratch, crash_address_, RelocInfo::NONE);
335 int crash_offset = __ pc_offset();
336 __ testl(MemOperand(scratch, 0), Immediate(1));
337 int recovery_offset = __ pc_offset();
338 __ Pop(scratch);
339 __ Ret();
340 CodeDesc desc;
341 masm.GetCode(nullptr, &desc);
342
343 trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
344 recovery_offset};
345 trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
346 desc.instr_size, 1, &protected_instruction);
347
348 ExecuteExpectCrash(buffer_.get());
349}
350
351TEST_P(TrapHandlerTest, TestCrashInWasmNoProtectedInstruction) {
352 // Test that if the crash in wasm happened at an instruction which is not
353 // protected, then the trap handler does not handle it.
354 MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
355 buffer_->CreateView());
356 __ Push(scratch);
357 GenerateSetThreadInWasmFlagCode(&masm);
358 int no_crash_offset = __ pc_offset();
359 __ Move(scratch, crash_address_, RelocInfo::NONE);
360 __ testl(MemOperand(scratch, 0), Immediate(1));
361 // Offset where the crash is not happening.
362 int recovery_offset = __ pc_offset();
363 GenerateResetThreadInWasmFlagCode(&masm);
364 __ Pop(scratch);
365 __ Ret();
366 CodeDesc desc;
367 masm.GetCode(nullptr, &desc);
368
369 trap_handler::ProtectedInstructionData protected_instruction{no_crash_offset,
370 recovery_offset};
371 trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
372 desc.instr_size, 1, &protected_instruction);
373
374 ExecuteExpectCrash(buffer_.get());
375}
376
377TEST_P(TrapHandlerTest, TestCrashInWasmWrongCrashType) {
378 // Test that if the crash reason is not a memory access violation, then the
379 // wasm trap handler does not handle it.
380 MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
381 buffer_->CreateView());
382 __ Push(scratch);
383 GenerateSetThreadInWasmFlagCode(&masm);
384 __ xorq(scratch, scratch);
385 int crash_offset = __ pc_offset();
386 __ divq(scratch);
387 // Offset where the crash is not happening.
388 int recovery_offset = __ pc_offset();
389 GenerateResetThreadInWasmFlagCode(&masm);
390 __ Pop(scratch);
391 __ Ret();
392 CodeDesc desc;
393 masm.GetCode(nullptr, &desc);
394
395 trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
396 recovery_offset};
397 trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
398 desc.instr_size, 1, &protected_instruction);
399
400#if V8_OS_POSIX
401 // On Posix, the V8 default trap handler does not register for SIGFPE,
402 // therefore the thread-in-wasm flag is never reset in this test. We
403 // therefore do not check the value of this flag.
404 bool check_wasm_flag = GetParam() != kDefault;
405#elif V8_OS_WIN
406 // On Windows, the trap handler returns immediately if not an exception of
407 // interest.
408 bool check_wasm_flag = false;
409#else
410 bool check_wasm_flag = true;
411#endif
412 ExecuteExpectCrash(buffer_.get(), check_wasm_flag);
413 if (!check_wasm_flag) {
414 // Reset the thread-in-wasm flag because it was probably not reset in the
415 // trap handler.
416 *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
417 }
418}
419#endif
420
421class CodeRunner : public v8::base::Thread {
422 public:
423 CodeRunner(TrapHandlerTest* test, TestingAssemblerBuffer* buffer)
424 : Thread(Options("CodeRunner")), test_(test), buffer_(buffer) {}
425
426 void Run() override { test_->ExecuteExpectCrash(buffer_); }
427
428 private:
429 TrapHandlerTest* test_;
430 TestingAssemblerBuffer* buffer_;
431};
432
433// TODO(almuthanna): This test was skipped because it causes a crash when it is
434// ran on Fuchsia. This issue should be solved later on
435// Ticket: https://crbug.com/1028617
436#if !defined(V8_TARGET_OS_FUCHSIA)
437TEST_P(TrapHandlerTest, TestCrashInOtherThread) {
438 // Test setup:
439 // The current thread enters wasm land (sets the thread_in_wasm flag)
440 // A second thread crashes at a protected instruction without having the flag
441 // set.
442 MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
443 buffer_->CreateView());
444 __ Push(scratch);
445 __ Move(scratch, crash_address_, RelocInfo::NONE);
446 int crash_offset = __ pc_offset();
447 __ testl(MemOperand(scratch, 0), Immediate(1));
448 int recovery_offset = __ pc_offset();
449 __ Pop(scratch);
450 __ Ret();
451 CodeDesc desc;
452 masm.GetCode(nullptr, &desc);
453
454 trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
455 recovery_offset};
456 trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
457 desc.instr_size, 1, &protected_instruction);
458
459 CodeRunner runner(this, buffer_.get());
460 CHECK(!GetThreadInWasmFlag());
461 // Set the thread-in-wasm flag manually in this thread.
462 *trap_handler::GetThreadInWasmThreadLocalAddress() = 1;
463 CHECK(runner.Start());
464 runner.Join();
465 CHECK(GetThreadInWasmFlag());
466 // Reset the thread-in-wasm flag.
467 *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
468}
469#endif
470
471INSTANTIATE_TEST_SUITE_P(Traps, TrapHandlerTest,
472 ::testing::Values(kDefault, kCallback),
473 PrintTrapHandlerTestParam);
474
475#undef __
476} // namespace wasm
477} // namespace internal
478} // namespace v8