1 : /*
2 : * Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 : * Use of this source code is governed by a BSD-style license that can be
4 : * found in the LICENSE file.
5 : */
6 : #include "native_client/src/trusted/service_runtime/linux/android_compat.h"
7 :
8 : #include <errno.h>
9 : #include <linux/futex.h>
10 : #include <signal.h>
11 : #include <sys/syscall.h>
12 :
13 : #include "native_client/src/include/concurrency_ops.h"
14 : #include "native_client/src/shared/platform/nacl_check.h"
15 : #include "native_client/src/shared/platform/nacl_exit.h"
16 : #include "native_client/src/shared/platform/nacl_sync_checked.h"
17 : #include "native_client/src/trusted/service_runtime/nacl_app_thread.h"
18 : #include "native_client/src/trusted/service_runtime/nacl_globals.h"
19 : #include "native_client/src/trusted/service_runtime/nacl_tls.h"
20 : #include "native_client/src/trusted/service_runtime/sel_ldr.h"
21 : #include "native_client/src/trusted/service_runtime/thread_suspension.h"
22 :
23 :
24 : struct NaClAppThreadSuspendedRegisters {
25 : struct NaClSignalContext context;
26 : };
27 :
28 : /*
29 : * If |*addr| still contains |value|, this waits to be woken up by a
30 : * FutexWake(addr,...) call from another thread; otherwise, it returns
31 : * immediately.
32 : *
33 : * Note that if this is interrupted by a signal, the system call will
34 : * get restarted, but it will recheck whether |*addr| still contains
35 : * |value|.
36 : *
37 : * We use the *_PRIVATE variant to use process-local futexes which are
38 : * slightly faster than shared futexes. (Private futexes are
39 : * well-known but are not covered by the Linux man page for futex(),
40 : * which is very out-of-date.)
41 : */
42 22271 : static void FutexWait(Atomic32 *addr, Atomic32 value) {
43 22271 : if (syscall(SYS_futex, addr, FUTEX_WAIT_PRIVATE, value, 0, 0, 0) != 0) {
44 : /*
45 : * We get EWOULDBLOCK if *addr != value (EAGAIN is a synonym).
46 : * We get EINTR if interrupted by a signal.
47 : */
48 558 : if (errno != EINTR && errno != EWOULDBLOCK) {
49 0 : NaClLog(LOG_FATAL, "FutexWait: futex() failed with error %d\n", errno);
50 : }
51 : }
52 22254 : }
53 :
54 : /*
55 : * This wakes up threads that are waiting on |addr| using FutexWait().
56 : * |waiters| is the maximum number of threads that will be woken up.
57 : */
58 22800 : static void FutexWake(Atomic32 *addr, int waiters) {
59 22800 : if (syscall(SYS_futex, addr, FUTEX_WAKE_PRIVATE, waiters, 0, 0, 0) < 0) {
60 0 : NaClLog(LOG_FATAL, "FutexWake: futex() failed with error %d\n", errno);
61 : }
62 22794 : }
63 :
64 5435971 : void NaClAppThreadSetSuspendState(struct NaClAppThread *natp,
65 : enum NaClSuspendState old_state,
66 : enum NaClSuspendState new_state) {
67 : while (1) {
68 5435971 : Atomic32 state = CompareAndSwap(&natp->suspend_state, old_state, new_state);
69 5435995 : if (NACL_LIKELY(state == (Atomic32) old_state)) {
70 5434936 : break;
71 : }
72 1059 : if ((state & NACL_APP_THREAD_SUSPENDING) != 0) {
73 : /* We have been asked to suspend, so wait. */
74 1059 : FutexWait(&natp->suspend_state, state);
75 : } else {
76 0 : NaClLog(LOG_FATAL, "NaClAppThreadSetSuspendState: Unexpected state: %i\n",
77 : state);
78 : }
79 1059 : }
80 5434936 : }
81 :
82 10614 : static void HandleSuspendSignal(struct NaClSignalContext *regs) {
83 10614 : struct NaClAppThread *natp = NaClTlsGetCurrentThread();
84 10614 : struct NaClSignalContext *suspended_registers =
85 10614 : &natp->suspended_registers->context;
86 :
87 : /* Sanity check. */
88 10614 : if (natp->suspend_state != (NACL_APP_THREAD_UNTRUSTED |
89 : NACL_APP_THREAD_SUSPENDING)) {
90 0 : NaClSignalErrorMessage("HandleSuspendSignal: "
91 : "Unexpected suspend_state\n");
92 0 : NaClAbort();
93 : }
94 :
95 10614 : if (suspended_registers != NULL) {
96 10490 : *suspended_registers = *regs;
97 : /*
98 : * Ensure that the change to natp->suspend_state does not become
99 : * visible before the change to natp->suspended_registers.
100 : */
101 10490 : NaClWriteMemoryBarrier();
102 : }
103 :
104 : /*
105 : * Indicate that we have suspended by setting
106 : * NACL_APP_THREAD_SUSPENDED. We should not need an atomic
107 : * operation for this since the calling thread will not be trying to
108 : * change suspend_state.
109 : */
110 10614 : natp->suspend_state |= NACL_APP_THREAD_SUSPENDED;
111 10614 : FutexWake(&natp->suspend_state, 1);
112 :
113 : /* Wait until we are asked to resume. */
114 : while (1) {
115 21197 : Atomic32 state = natp->suspend_state;
116 21197 : if ((state & NACL_APP_THREAD_SUSPENDED) != 0) {
117 10600 : FutexWait(&natp->suspend_state, state);
118 10583 : continue; /* Retry */
119 : }
120 10597 : break;
121 10583 : }
122 : /*
123 : * Apply register modifications. We need to use a snapshot of
124 : * natp->suspended_registers because, since we were asked to resume,
125 : * we could have been asked to suspend again, and
126 : * suspended_registers could have been allocated in the new
127 : * suspension request but not in the original suspension request.
128 : */
129 10597 : if (suspended_registers != NULL) {
130 10473 : *regs = *suspended_registers;
131 : }
132 10597 : }
133 :
134 91 : static void FireDebugStubEvent(int pipe_fd) {
135 91 : char buf = 0;
136 91 : if (write(pipe_fd, &buf, sizeof(buf)) != sizeof(buf)) {
137 0 : NaClSignalErrorMessage("FireDebugStubEvent: Can't send debug stub event\n");
138 0 : NaClAbort();
139 : }
140 91 : }
141 :
142 91 : static void HandleUntrustedFault(int signal,
143 : struct NaClSignalContext *regs,
144 : struct NaClAppThread *natp) {
145 : /* Sanity check. */
146 91 : if ((natp->suspend_state & NACL_APP_THREAD_UNTRUSTED) == 0) {
147 0 : NaClSignalErrorMessage("HandleUntrustedFault: Unexpected suspend_state\n");
148 0 : NaClAbort();
149 : }
150 :
151 : /* Notify the debug stub by marking this thread as faulted. */
152 91 : natp->fault_signal = signal;
153 91 : AtomicIncrement(&natp->nap->faulted_thread_count, 1);
154 91 : FireDebugStubEvent(natp->nap->faulted_thread_fd_write);
155 :
156 : /*
157 : * We now expect the debug stub to suspend this thread via the
158 : * thread suspension API. This will allow the debug stub to get the
159 : * register state at the point the fault happened. The debug stub
160 : * will be able to modify the register state before unblocking the
161 : * thread using NaClAppThreadUnblockIfFaulted().
162 : */
163 : do {
164 : int new_signal;
165 : sigset_t sigset;
166 94 : sigemptyset(&sigset);
167 94 : sigaddset(&sigset, NACL_THREAD_SUSPEND_SIGNAL);
168 94 : if (sigwait(&sigset, &new_signal) != 0) {
169 0 : NaClSignalErrorMessage("HandleUntrustedFault: sigwait() failed\n");
170 0 : NaClAbort();
171 : }
172 94 : if (new_signal != NACL_THREAD_SUSPEND_SIGNAL) {
173 0 : NaClSignalErrorMessage("HandleUntrustedFault: "
174 : "sigwait() returned unexpected result\n");
175 0 : NaClAbort();
176 : }
177 94 : HandleSuspendSignal(regs);
178 78 : } while (natp->fault_signal != 0);
179 75 : }
180 :
181 140375 : int NaClThreadSuspensionSignalHandler(int signal,
182 : struct NaClSignalContext *regs,
183 : int is_untrusted,
184 : struct NaClAppThread *natp) {
185 : /*
186 : * We handle NACL_THREAD_SUSPEND_SIGNAL even if is_untrusted is
187 : * false, because the thread suspension signal can occur in trusted
188 : * code while switching from or to untrusted code while
189 : * suspend_state contains NACL_APP_THREAD_UNTRUSTED.
190 : */
191 140375 : if (signal == NACL_THREAD_SUSPEND_SIGNAL) {
192 10520 : HandleSuspendSignal(regs);
193 10519 : return 1;
194 : }
195 129855 : if (is_untrusted && natp->nap->enable_faulted_thread_queue) {
196 91 : HandleUntrustedFault(signal, regs, natp);
197 75 : return 1;
198 : }
199 : /* We did not handle this signal. */
200 129764 : return 0;
201 : }
202 :
203 : /* Wait for the thread to indicate that it has suspended. */
204 10614 : static void WaitForUntrustedThreadToSuspend(struct NaClAppThread *natp) {
205 10614 : const Atomic32 kBaseState = (NACL_APP_THREAD_UNTRUSTED |
206 : NACL_APP_THREAD_SUSPENDING);
207 : while (1) {
208 21226 : Atomic32 state = natp->suspend_state;
209 21226 : if (state == kBaseState) {
210 10612 : FutexWait(&natp->suspend_state, state);
211 10612 : continue; /* Retry */
212 : }
213 10614 : if (state != (kBaseState | NACL_APP_THREAD_SUSPENDED)) {
214 0 : NaClLog(LOG_FATAL, "WaitForUntrustedThreadToSuspend: "
215 : "Unexpected state: %d\n", state);
216 : }
217 10614 : break;
218 10612 : }
219 10614 : }
220 :
221 12567 : void NaClUntrustedThreadSuspend(struct NaClAppThread *natp,
222 : int save_registers) {
223 : Atomic32 old_state;
224 : Atomic32 suspending_state;
225 :
226 : /*
227 : * We do not want the thread to enter a NaCl syscall and start
228 : * taking locks when pthread_kill() takes effect, so we ask the
229 : * thread to suspend even if it is currently running untrusted code.
230 : */
231 : while (1) {
232 12567 : old_state = natp->suspend_state;
233 12567 : DCHECK((old_state & NACL_APP_THREAD_SUSPENDING) == 0);
234 12567 : suspending_state = old_state | NACL_APP_THREAD_SUSPENDING;
235 12567 : if (CompareAndSwap(&natp->suspend_state, old_state, suspending_state)
236 : != old_state) {
237 362 : continue; /* Retry */
238 : }
239 12205 : break;
240 362 : }
241 : /*
242 : * Once the thread has NACL_APP_THREAD_SUSPENDING set, it may not
243 : * change state itself, so there should be no race condition in this
244 : * check.
245 : */
246 12205 : DCHECK(natp->suspend_state == suspending_state);
247 :
248 12205 : if (old_state == NACL_APP_THREAD_UNTRUSTED) {
249 : /*
250 : * Allocate register state struct if needed. This is race-free
251 : * when we are called by NaClUntrustedThreadsSuspendAll(), since
252 : * that claims nap->threads_mu.
253 : */
254 10614 : if (save_registers && natp->suspended_registers == NULL) {
255 27 : natp->suspended_registers = malloc(sizeof(*natp->suspended_registers));
256 27 : if (natp->suspended_registers == NULL) {
257 0 : NaClLog(LOG_FATAL, "NaClUntrustedThreadSuspend: malloc() failed\n");
258 : }
259 : }
260 10614 : CHECK(natp->host_thread_is_defined);
261 10614 : if (pthread_kill(natp->host_thread.tid, NACL_THREAD_SUSPEND_SIGNAL) != 0) {
262 0 : NaClLog(LOG_FATAL, "NaClUntrustedThreadSuspend: "
263 : "pthread_kill() call failed\n");
264 : }
265 10614 : WaitForUntrustedThreadToSuspend(natp);
266 : }
267 12205 : }
268 :
269 12186 : void NaClUntrustedThreadResume(struct NaClAppThread *natp) {
270 : Atomic32 old_state;
271 : Atomic32 new_state;
272 : while (1) {
273 12186 : old_state = natp->suspend_state;
274 12186 : new_state = old_state & ~(NACL_APP_THREAD_SUSPENDING |
275 : NACL_APP_THREAD_SUSPENDED);
276 12186 : DCHECK((old_state & NACL_APP_THREAD_SUSPENDING) != 0);
277 12186 : if (CompareAndSwap(&natp->suspend_state, old_state, new_state)
278 : != old_state) {
279 0 : continue; /* Retry */
280 : }
281 12186 : break;
282 0 : }
283 :
284 : /*
285 : * TODO(mseaborn): A refinement would be to wake up the thread only
286 : * if it actually suspended during the context switch.
287 : */
288 12186 : FutexWake(&natp->suspend_state, 1);
289 12186 : }
290 :
291 20962 : void NaClAppThreadGetSuspendedRegistersInternal(
292 : struct NaClAppThread *natp, struct NaClSignalContext *regs) {
293 20962 : *regs = natp->suspended_registers->context;
294 20962 : }
295 :
296 77 : void NaClAppThreadSetSuspendedRegistersInternal(
297 : struct NaClAppThread *natp, const struct NaClSignalContext *regs) {
298 77 : natp->suspended_registers->context = *regs;
299 77 : }
300 :
301 92 : int NaClAppThreadUnblockIfFaulted(struct NaClAppThread *natp, int *signal) {
302 : /* This function may only be called on a thread that is suspended. */
303 92 : DCHECK(natp->suspend_state == (NACL_APP_THREAD_UNTRUSTED |
304 : NACL_APP_THREAD_SUSPENDING |
305 : NACL_APP_THREAD_SUSPENDED) ||
306 : natp->suspend_state == (NACL_APP_THREAD_TRUSTED |
307 : NACL_APP_THREAD_SUSPENDING));
308 :
309 92 : if (natp->fault_signal == 0) {
310 1 : return 0;
311 : }
312 91 : *signal = natp->fault_signal;
313 91 : natp->fault_signal = 0;
314 91 : AtomicIncrement(&natp->nap->faulted_thread_count, -1);
315 91 : return 1;
316 : }
|