v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
handler-inside-posix.cc
Go to the documentation of this file.
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// PLEASE READ BEFORE CHANGING THIS FILE!
6//
7// This file implements the out of bounds signal handler for
8// WebAssembly. Signal handlers are notoriously difficult to get
9// right, and getting it wrong can lead to security
10// vulnerabilities. In order to minimize this risk, here are some
11// rules to follow.
12//
13// 1. Do not introduce any new external dependencies. This file needs
14// to be self contained so it is easy to audit everything that a
15// signal handler might do.
16//
17// 2. Any changes must be reviewed by someone from the crash reporting
18// or security team. See OWNERS for suggested reviewers.
19//
20// For more information, see https://goo.gl/yMeyUY.
21//
22// This file contains most of the code that actually runs in a signal handler
23// context. Some additional code is used both inside and outside the signal
24// handler. This code can be found in handler-shared.cc.
25
27
28#include <signal.h>
29
30#if defined(V8_OS_LINUX) || defined(V8_OS_FREEBSD)
31#include <ucontext.h>
32#elif V8_OS_DARWIN
33#include <sys/ucontext.h>
34#endif
35
36#include <stddef.h>
37#include <stdlib.h>
38
41
42#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
44#endif
45
46namespace v8 {
47namespace internal {
48namespace trap_handler {
49
50#if V8_TRAP_HANDLER_SUPPORTED
51
52#if V8_OS_LINUX && V8_HOST_ARCH_ARM64
53#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.regs[REG]
54#elif V8_OS_LINUX && (V8_HOST_ARCH_LOONG64 || V8_HOST_ARCH_RISCV64)
55#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.__gregs[REG]
56#elif V8_OS_LINUX
57#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.gregs[REG_##REG]
58#elif V8_OS_DARWIN && V8_HOST_ARCH_ARM64
59#define CONTEXT_REG(reg, REG) &uc->uc_mcontext->__ss.__x[REG]
60#elif V8_OS_DARWIN
61#define CONTEXT_REG(reg, REG) &uc->uc_mcontext->__ss.__##reg
62#elif V8_OS_FREEBSD
63#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.mc_##reg
64#else
65#error "Unsupported platform."
66#endif
67
68#if V8_OS_LINUX && V8_HOST_ARCH_ARM64
69#define CONTEXT_PC() &uc->uc_mcontext.pc
70#elif V8_OS_DARWIN && V8_HOST_ARCH_ARM64
71#define CONTEXT_PC() &uc->uc_mcontext->__ss.__pc
72#elif V8_OS_LINUX && V8_HOST_ARCH_LOONG64
73#define CONTEXT_PC() &uc->uc_mcontext.__pc
74#elif V8_OS_LINUX && V8_HOST_ARCH_RISCV64
75#define CONTEXT_PC() &uc->uc_mcontext.__gregs[REG_PC]
76#endif
77
78bool IsKernelGeneratedSignal(siginfo_t* info) {
79 // On macOS, only `info->si_code > 0` is relevant, because macOS leaves
80 // si_code at its default of 0 for signals that don’t originate in hardware.
81 // The other conditions are only relevant for Linux.
82 return info->si_code > 0 && info->si_code != SI_USER &&
83 info->si_code != SI_QUEUE && info->si_code != SI_TIMER &&
84 info->si_code != SI_ASYNCIO && info->si_code != SI_MESGQ;
85}
86
87class UnmaskOobSignalScope {
88 public:
89 UnmaskOobSignalScope() {
90 sigset_t sigs;
91 // Fortunately, sigemptyset and sigaddset are async-signal-safe according to
92 // the POSIX standard.
93 sigemptyset(&sigs);
94 sigaddset(&sigs, kOobSignal);
95 pthread_sigmask(SIG_UNBLOCK, &sigs, &old_mask_);
96 }
97
98 UnmaskOobSignalScope(const UnmaskOobSignalScope&) = delete;
99 void operator=(const UnmaskOobSignalScope&) = delete;
100
101 ~UnmaskOobSignalScope() { pthread_sigmask(SIG_SETMASK, &old_mask_, nullptr); }
102
103 private:
104 sigset_t old_mask_;
105};
106
107#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
108// This is the address where we continue on a failed "ProbeMemory". It's defined
109// in "handler-outside-simulator.cc".
110extern char probe_memory_continuation[]
111#if V8_OS_DARWIN
112 asm("_v8_simulator_probe_memory_continuation");
113#else
114 asm("v8_simulator_probe_memory_continuation");
115#endif
116#endif // V8_TRAP_HANDLER_VIA_SIMULATOR
117
118bool TryHandleSignal(int signum, siginfo_t* info, void* context) {
119 // Ensure the faulting thread was actually running Wasm code. This should be
120 // the first check in the trap handler to guarantee that the
121 // g_thread_in_wasm_code flag is only set in wasm code. Otherwise a later
122 // signal handler is executed with the flag set.
123 if (!g_thread_in_wasm_code) return false;
124
125 // Clear g_thread_in_wasm_code, primarily to protect against nested faults.
126 // The only path that resets the flag to true is if we find a landing pad (in
127 // which case this function returns true). Otherwise we leave the flag unset
128 // since we do not return to wasm code.
129 g_thread_in_wasm_code = false;
130
131 // Bail out early in case we got called for the wrong kind of signal.
132 if (signum != kOobSignal) return false;
133
134 // Make sure the signal was generated by the kernel and not some other source.
135 if (!IsKernelGeneratedSignal(info)) return false;
136
137 // Check whether the fault should be handled based on the accessed address.
138 // A fault caused by an access to an address that cannot belong to a Wasm
139 // memory object should not be handled.
140 uintptr_t access_addr = reinterpret_cast<uintptr_t>(info->si_addr);
141 if (!IsAccessedMemoryCovered(access_addr)) return false;
142
143 // Unmask the oob signal, which is automatically masked during the execution
144 // of this handler. This ensures that crashes generated in this function will
145 // be handled by the crash reporter. Otherwise, the process might be killed
146 // with the crash going unreported. The scope object makes sure to restore the
147 // signal mask on return from this function. We put the scope object in a
148 // separate block to ensure that we restore the signal mask before we restore
149 // the g_thread_in_wasm_code flag.
150 {
151 UnmaskOobSignalScope unmask_oob_signal;
152
153 ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
154#if V8_HOST_ARCH_X64
155 auto* context_ip = CONTEXT_REG(rip, RIP);
156#elif V8_HOST_ARCH_ARM64
157 auto* context_ip = CONTEXT_PC();
158#elif V8_HOST_ARCH_LOONG64
159 auto* context_ip = CONTEXT_PC();
160#elif V8_HOST_ARCH_RISCV64
161 auto* context_ip = CONTEXT_PC();
162#else
163#error "Unsupported architecture."
164#endif
165
166 uintptr_t fault_addr = *context_ip;
167#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
168 // Only handle signals triggered by the load in {ProbeMemory}.
169 if (fault_addr != reinterpret_cast<uintptr_t>(&ProbeMemory)) {
170 return false;
171 }
172
173 // The simulated ip will be in the second parameter register (%rsi).
174 auto* simulated_ip_reg = CONTEXT_REG(rsi, RSI);
175 if (!IsFaultAddressCovered(*simulated_ip_reg)) return false;
177
178 auto* return_reg = CONTEXT_REG(rax, RAX);
179 *return_reg = gLandingPad;
180 // The fault_address that is set in non-simulator builds here is set in the
181 // simulator directly.
182 // Continue at the memory probing continuation.
183 *context_ip = reinterpret_cast<uintptr_t>(&probe_memory_continuation);
184#else
185 if (!IsFaultAddressCovered(fault_addr)) return false;
187 // Tell the caller to return to the landing pad.
188 *context_ip = gLandingPad;
189
190#if V8_HOST_ARCH_X64
191 auto* fault_address_reg = CONTEXT_REG(r10, R10);
192#elif V8_HOST_ARCH_ARM64
193 auto* fault_address_reg = CONTEXT_REG(x16, 16);
194#elif V8_HOST_ARCH_LOONG64
195 auto* fault_address_reg = CONTEXT_REG(t6, 18);
196#elif V8_HOST_ARCH_RISCV64
197 auto* fault_address_reg = CONTEXT_REG(t6, 31);
198#else
199#error "Unsupported architecture."
200#endif
201 *fault_address_reg = fault_addr;
202#endif
203 }
204 // We will return to wasm code, so restore the g_thread_in_wasm_code flag.
205 // This should only be done once the signal is blocked again (outside the
206 // {UnmaskOobSignalScope}) to ensure that we do not catch a signal we raise
207 // inside of the handler.
209 return true;
210}
211
212void HandleSignal(int signum, siginfo_t* info, void* context) {
213 if (!TryHandleSignal(signum, info, context)) {
214 // Since V8 didn't handle this signal, we want to re-raise the same signal.
215 // For kernel-generated signals, we do this by restoring the original
216 // handler and then returning. The fault will happen again and the usual
217 // signal handling will happen.
218 //
219 // We handle user-generated signals by calling raise() instead. This is for
220 // completeness. We should never actually see one of these, but just in
221 // case, we do the right thing.
223 if (!IsKernelGeneratedSignal(info)) {
224 raise(signum);
225 }
226 }
227 // TryHandleSignal modifies context to change where we return to.
228}
229
230#endif
231
232} // namespace trap_handler
233} // namespace internal
234} // namespace v8
TNode< Context > context
bool IsFaultAddressCovered(uintptr_t fault_addr)
bool TryHandleSignal(int signum, siginfo_t *info, void *context)
void HandleSignal(int signum, siginfo_t *info, void *context)
thread_local int g_thread_in_wasm_code
std::atomic< uintptr_t > gLandingPad
bool IsAccessedMemoryCovered(uintptr_t accessed_addr)
#define TH_DCHECK(condition)