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