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 36300 : std::string PrintTrapHandlerTestParam(
69 : ::testing::TestParamInfo<TrapHandlerStyle> info) {
70 36300 : switch (info.param) {
71 : case kDefault:
72 18150 : return "DefaultTrapHandler";
73 : case kCallback:
74 18150 : 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 : ->TryAllocateBackingStoreForTesting(
90 24 : 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 : i_isolate()->wasm_engine()->memory_tracker()->FreeBackingStoreForTesting(
128 24 : 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 60 : recovery_buffer_->CreateView());
154 12 : int recovery_offset = __ pc_offset();
155 12 : __ Pop(scratch);
156 12 : __ Ret();
157 12 : CodeDesc desc;
158 12 : 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 12 : 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 : masm->Move(scratch,
213 : i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
214 8 : RelocInfo::NONE);
215 16 : masm->movl(MemOperand(scratch, 0), Immediate(1));
216 8 : }
217 :
218 8 : void GenerateResetThreadInWasmFlagCode(MacroAssembler* masm) {
219 : masm->Move(scratch,
220 : i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
221 8 : 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 : 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 18156 : 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 10 : buffer_->CreateView());
275 2 : __ Push(scratch);
276 2 : GenerateSetThreadInWasmFlagCode(&masm);
277 2 : __ Move(scratch, crash_address_, RelocInfo::NONE);
278 2 : int crash_offset = __ pc_offset();
279 4 : __ testl(MemOperand(scratch, 0), Immediate(1));
280 2 : int recovery_offset = __ pc_offset();
281 2 : GenerateResetThreadInWasmFlagCode(&masm);
282 2 : __ Pop(scratch);
283 2 : __ Ret();
284 2 : CodeDesc desc;
285 2 : masm.GetCode(nullptr, &desc);
286 :
287 2 : SetupTrapHandler(GetParam());
288 : trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
289 2 : recovery_offset};
290 : trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
291 2 : desc.instr_size, 1, &protected_instruction);
292 :
293 2 : ExecuteBuffer();
294 2 : }
295 :
296 18156 : 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 10 : buffer_->CreateView());
301 2 : __ Push(scratch);
302 2 : GenerateSetThreadInWasmFlagCode(&masm);
303 2 : __ Move(scratch, crash_address_, RelocInfo::NONE);
304 2 : int crash_offset = __ pc_offset();
305 4 : __ testl(MemOperand(scratch, 0), Immediate(1));
306 2 : int recovery_offset = __ pc_offset();
307 2 : GenerateResetThreadInWasmFlagCode(&masm);
308 2 : __ Pop(scratch);
309 2 : __ Ret();
310 2 : CodeDesc desc;
311 2 : masm.GetCode(nullptr, &desc);
312 :
313 : trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
314 2 : recovery_offset};
315 : int handler_id = trap_handler::RegisterHandlerData(
316 : 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 18156 : 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 10 : buffer_->CreateView());
335 2 : __ Push(scratch);
336 2 : __ Move(scratch, crash_address_, RelocInfo::NONE);
337 2 : int crash_offset = __ pc_offset();
338 4 : __ testl(MemOperand(scratch, 0), Immediate(1));
339 2 : int recovery_offset = __ pc_offset();
340 2 : __ Pop(scratch);
341 2 : __ Ret();
342 2 : CodeDesc desc;
343 2 : masm.GetCode(nullptr, &desc);
344 :
345 : trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
346 2 : recovery_offset};
347 : trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
348 2 : desc.instr_size, 1, &protected_instruction);
349 :
350 2 : SetupTrapHandler(GetParam());
351 :
352 2 : ExecuteExpectCrash(buffer_.get());
353 2 : }
354 :
355 18156 : 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 10 : buffer_->CreateView());
360 2 : __ Push(scratch);
361 2 : GenerateSetThreadInWasmFlagCode(&masm);
362 2 : 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 2 : int recovery_offset = __ pc_offset();
367 2 : GenerateResetThreadInWasmFlagCode(&masm);
368 2 : __ Pop(scratch);
369 2 : __ Ret();
370 2 : CodeDesc desc;
371 2 : masm.GetCode(nullptr, &desc);
372 :
373 : trap_handler::ProtectedInstructionData protected_instruction{no_crash_offset,
374 2 : recovery_offset};
375 : trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
376 2 : desc.instr_size, 1, &protected_instruction);
377 :
378 2 : SetupTrapHandler(GetParam());
379 :
380 2 : ExecuteExpectCrash(buffer_.get());
381 2 : }
382 :
383 18156 : 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 10 : buffer_->CreateView());
388 2 : __ Push(scratch);
389 2 : GenerateSetThreadInWasmFlagCode(&masm);
390 : __ xorq(scratch, scratch);
391 2 : int crash_offset = __ pc_offset();
392 : __ divq(scratch);
393 : // Offset where the crash is not happening.
394 2 : int recovery_offset = __ pc_offset();
395 2 : GenerateResetThreadInWasmFlagCode(&masm);
396 2 : __ Pop(scratch);
397 2 : __ Ret();
398 2 : CodeDesc desc;
399 2 : masm.GetCode(nullptr, &desc);
400 :
401 : trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
402 2 : recovery_offset};
403 : trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
404 2 : desc.instr_size, 1, &protected_instruction);
405 :
406 2 : SetupTrapHandler(GetParam());
407 :
408 : #if V8_OS_POSIX
409 : // The V8 default trap handler does not register for SIGFPE, therefore the
410 : // thread-in-wasm flag is never reset in this test. We therefore do not check
411 : // the value of this flag.
412 2 : bool check_wasm_flag = GetParam() != kDefault;
413 : #else
414 : bool check_wasm_flag = true;
415 : #endif
416 4 : ExecuteExpectCrash(buffer_.get(), check_wasm_flag);
417 2 : if (!check_wasm_flag) {
418 : // Reset the thread-in-wasm flag because it was probably not reset in the
419 : // trap handler.
420 1 : *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
421 : }
422 2 : }
423 :
424 2 : class CodeRunner : public v8::base::Thread {
425 : public:
426 : CodeRunner(TrapHandlerTest* test, TestingAssemblerBuffer* buffer)
427 2 : : Thread(Options("CodeRunner")), test_(test), buffer_(buffer) {}
428 :
429 2 : void Run() override { test_->ExecuteExpectCrash(buffer_); }
430 :
431 : private:
432 : TrapHandlerTest* test_;
433 : TestingAssemblerBuffer* buffer_;
434 : };
435 :
436 18156 : TEST_P(TrapHandlerTest, TestCrashInOtherThread) {
437 : // Test setup:
438 : // The current thread enters wasm land (sets the thread_in_wasm flag)
439 : // A second thread crashes at a protected instruction without having the flag
440 : // set.
441 : MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
442 10 : buffer_->CreateView());
443 2 : __ Push(scratch);
444 2 : __ Move(scratch, crash_address_, RelocInfo::NONE);
445 2 : int crash_offset = __ pc_offset();
446 4 : __ testl(MemOperand(scratch, 0), Immediate(1));
447 2 : int recovery_offset = __ pc_offset();
448 2 : __ Pop(scratch);
449 2 : __ Ret();
450 2 : CodeDesc desc;
451 2 : masm.GetCode(nullptr, &desc);
452 :
453 : trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
454 2 : recovery_offset};
455 : trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
456 2 : desc.instr_size, 1, &protected_instruction);
457 :
458 2 : SetupTrapHandler(GetParam());
459 :
460 2 : CodeRunner runner(this, buffer_.get());
461 2 : CHECK(!GetThreadInWasmFlag());
462 : // Set the thread-in-wasm flag manually in this thread.
463 2 : *trap_handler::GetThreadInWasmThreadLocalAddress() = 1;
464 2 : runner.Start();
465 2 : runner.Join();
466 2 : CHECK(GetThreadInWasmFlag());
467 : // Reset the thread-in-wasm flag.
468 2 : *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
469 2 : }
470 :
471 69575 : INSTANTIATE_TEST_CASE_P(/* no prefix */, TrapHandlerTest,
472 : ::testing::Values(kDefault, kCallback),
473 : PrintTrapHandlerTestParam);
474 :
475 : #undef __
476 : } // namespace wasm
477 : } // namespace internal
478 9075 : } // namespace v8
|