summaryrefslogtreecommitdiff
path: root/deps/v8/src/trap-handler/handler-inside-posix.cc
blob: 17af3d75dca6b592b56b1329c80114aa5e9099b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// PLEASE READ BEFORE CHANGING THIS FILE!
//
// This file implements the out of bounds signal handler for
// WebAssembly. Signal handlers are notoriously difficult to get
// right, and getting it wrong can lead to security
// vulnerabilities. In order to minimize this risk, here are some
// rules to follow.
//
// 1. Do not introduce any new external dependencies. This file needs
//    to be self contained so it is easy to audit everything that a
//    signal handler might do.
//
// 2. Any changes must be reviewed by someone from the crash reporting
//    or security team. See OWNERS for suggested reviewers.
//
// For more information, see https://goo.gl/yMeyUY.
//
// This file contains most of the code that actually runs in a signal handler
// context. Some additional code is used both inside and outside the signal
// handler. This code can be found in handler-shared.cc.

#include "src/trap-handler/handler-inside-posix.h"

#include <signal.h>

#if defined(V8_OS_LINUX) || defined(V8_OS_FREEBSD)
#include <ucontext.h>
#elif V8_OS_DARWIN
#include <sys/ucontext.h>
#endif

#include <stddef.h>
#include <stdlib.h>

#include "src/trap-handler/trap-handler-internal.h"
#include "src/trap-handler/trap-handler.h"

#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
#include "src/trap-handler/trap-handler-simulator.h"
#endif

namespace v8 {
namespace internal {
namespace trap_handler {

#if V8_TRAP_HANDLER_SUPPORTED

#if V8_OS_LINUX
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.gregs[REG_##REG]
#elif V8_OS_DARWIN
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext->__ss.__##reg
#elif V8_OS_FREEBSD
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.mc_##reg
#else
#error "Unsupported platform."
#endif

bool IsKernelGeneratedSignal(siginfo_t* info) {
  // On macOS, only `info->si_code > 0` is relevant, because macOS leaves
  // si_code at its default of 0 for signals that don’t originate in hardware.
  // The other conditions are only relevant for Linux.
  return info->si_code > 0 && info->si_code != SI_USER &&
         info->si_code != SI_QUEUE && info->si_code != SI_TIMER &&
         info->si_code != SI_ASYNCIO && info->si_code != SI_MESGQ;
}

class UnmaskOobSignalScope {
 public:
  UnmaskOobSignalScope() {
    sigset_t sigs;
    // Fortunately, sigemptyset and sigaddset are async-signal-safe according to
    // the POSIX standard.
    sigemptyset(&sigs);
    sigaddset(&sigs, kOobSignal);
    pthread_sigmask(SIG_UNBLOCK, &sigs, &old_mask_);
  }

  UnmaskOobSignalScope(const UnmaskOobSignalScope&) = delete;
  void operator=(const UnmaskOobSignalScope&) = delete;

  ~UnmaskOobSignalScope() { pthread_sigmask(SIG_SETMASK, &old_mask_, nullptr); }

 private:
  sigset_t old_mask_;
};

#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
// This is the address where we continue on a failed "ProbeMemory". It's defined
// in "handler-outside-simulator.cc".
extern "C" char v8_probe_memory_continuation[];
#endif  // V8_TRAP_HANDLER_VIA_SIMULATOR

bool TryHandleSignal(int signum, siginfo_t* info, void* context) {
  // Ensure the faulting thread was actually running Wasm code. This should be
  // the first check in the trap handler to guarantee that the
  // g_thread_in_wasm_code flag is only set in wasm code. Otherwise a later
  // signal handler is executed with the flag set.
  if (!g_thread_in_wasm_code) return false;

  // Clear g_thread_in_wasm_code, primarily to protect against nested faults.
  // The only path that resets the flag to true is if we find a landing pad (in
  // which case this function returns true). Otherwise we leave the flag unset
  // since we do not return to wasm code.
  g_thread_in_wasm_code = false;

  // Bail out early in case we got called for the wrong kind of signal.
  if (signum != kOobSignal) return false;

  // Make sure the signal was generated by the kernel and not some other source.
  if (!IsKernelGeneratedSignal(info)) return false;

  // Unmask the oob signal, which is automatically masked during the execution
  // of this handler. This ensures that crashes generated in this function will
  // be handled by the crash reporter. Otherwise, the process might be killed
  // with the crash going unreported. The scope object makes sure to restore the
  // signal mask on return from this function. We put the scope object in a
  // separate block to ensure that we restore the signal mask before we restore
  // the g_thread_in_wasm_code flag.
  {
    UnmaskOobSignalScope unmask_oob_signal;

    ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
#if V8_HOST_ARCH_X64
    auto* context_ip = CONTEXT_REG(rip, RIP);
#elif V8_HOST_ARCH_ARM64
    auto* context_ip = CONTEXT_REG(pc, PC);
#else
#error "Unsupported architecture."
#endif

    uintptr_t fault_addr = *context_ip;
    uintptr_t landing_pad = 0;

#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
    // Only handle signals triggered by the load in {ProbeMemory}.
    if (fault_addr != reinterpret_cast<uintptr_t>(&ProbeMemory)) {
      return false;
    }

    // The simulated ip will be in the second parameter register (%rsi).
    auto* simulated_ip_reg = CONTEXT_REG(rsi, RSI);
    if (!TryFindLandingPad(*simulated_ip_reg, &landing_pad)) return false;
    TH_DCHECK(landing_pad != 0);

    auto* return_reg = CONTEXT_REG(rax, RAX);
    *return_reg = landing_pad;
    // Continue at the memory probing continuation.
    *context_ip = reinterpret_cast<uintptr_t>(&v8_probe_memory_continuation);
#else
    if (!TryFindLandingPad(fault_addr, &landing_pad)) return false;

    // Tell the caller to return to the landing pad.
    *context_ip = landing_pad;
#endif
  }
  // We will return to wasm code, so restore the g_thread_in_wasm_code flag.
  // This should only be done once the signal is blocked again (outside the
  // {UnmaskOobSignalScope}) to ensure that we do not catch a signal we raise
  // inside of the handler.
  g_thread_in_wasm_code = true;
  return true;
}

void HandleSignal(int signum, siginfo_t* info, void* context) {
  if (!TryHandleSignal(signum, info, context)) {
    // Since V8 didn't handle this signal, we want to re-raise the same signal.
    // For kernel-generated signals, we do this by restoring the original
    // handler and then returning. The fault will happen again and the usual
    // signal handling will happen.
    //
    // We handle user-generated signals by calling raise() instead. This is for
    // completeness. We should never actually see one of these, but just in
    // case, we do the right thing.
    RemoveTrapHandler();
    if (!IsKernelGeneratedSignal(info)) {
      raise(signum);
    }
  }
  // TryHandleSignal modifies context to change where we return to.
}

#endif

}  // namespace trap_handler
}  // namespace internal
}  // namespace v8