-
Notifications
You must be signed in to change notification settings - Fork 196
/
host_exception.c
258 lines (206 loc) · 7.9 KB
/
host_exception.c
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2014 Stony Brook University
* 2020 Intel Labs
*/
/*
* This file contains APIs to set up signal handlers.
*/
#include <stddef.h> /* needed by <linux/signal.h> for size_t */
#include "sigset.h" /* FIXME: this include can't be sorted, otherwise we get:
* In file included from sgx_exception.c:19:0:
* ../../../include/arch/x86_64/linux/ucontext.h:136:5: error: unknown type name ‘__sigset_t’
* __sigset_t uc_sigmask;
*/
#include <linux/signal.h>
#include <stdbool.h>
#include "api.h"
#include "cpu.h"
#include "debug_map.h"
#include "host_internal.h"
#include "pal_rpc_queue.h"
#include "sigreturn.h"
#include "ucontext.h"
static const int ASYNC_SIGNALS[] = {SIGTERM, SIGCONT};
static int block_signal(int sig, bool block) {
int how = block ? SIG_BLOCK : SIG_UNBLOCK;
__sigset_t mask;
__sigemptyset(&mask);
__sigaddset(&mask, sig);
int ret = DO_SYSCALL(rt_sigprocmask, how, &mask, NULL, sizeof(__sigset_t));
return ret < 0 ? ret : 0;
}
static int set_signal_handler(int sig, void* handler) {
struct sigaction action = {0};
action.sa_handler = handler;
action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTORER;
action.sa_restorer = syscall_rt_sigreturn;
/* disallow nested asynchronous signals during enclave exception handling */
__sigemptyset((__sigset_t*)&action.sa_mask);
for (size_t i = 0; i < ARRAY_SIZE(ASYNC_SIGNALS); i++)
__sigaddset((__sigset_t*)&action.sa_mask, ASYNC_SIGNALS[i]);
int ret = DO_SYSCALL(rt_sigaction, sig, &action, NULL, sizeof(__sigset_t));
if (ret < 0)
return ret;
return block_signal(sig, /*block=*/false);
}
int block_async_signals(bool block) {
for (size_t i = 0; i < ARRAY_SIZE(ASYNC_SIGNALS); i++) {
int ret = block_signal(ASYNC_SIGNALS[i], block);
if (ret < 0)
return ret;
}
return 0;
}
static enum pal_event signal_to_pal_event(int sig) {
switch (sig) {
case SIGFPE:
return PAL_EVENT_ARITHMETIC_ERROR;
case SIGSEGV:
case SIGBUS:
return PAL_EVENT_MEMFAULT;
case SIGILL:
return PAL_EVENT_ILLEGAL;
case SIGTERM:
return PAL_EVENT_QUIT;
case SIGCONT:
return PAL_EVENT_INTERRUPTED;
default:
BUG();
}
}
static bool interrupted_in_enclave(struct ucontext* uc) {
unsigned long rip = ucontext_get_ip(uc);
/* in case of AEX, RIP can point to any instruction in the AEP/ERESUME trampoline code, i.e.,
* RIP can point to anywhere in [async_exit_pointer, async_exit_pointer_end) interval */
return rip >= (unsigned long)async_exit_pointer && rip < (unsigned long)async_exit_pointer_end;
}
static bool interrupted_in_aex_profiling(void) {
return pal_get_host_tcb()->is_in_aex_profiling != 0;
}
static void handle_sync_signal(int signum, siginfo_t* info, struct ucontext* uc) {
enum pal_event event = signal_to_pal_event(signum);
__UNUSED(info);
/* send dummy signal to RPC threads so they interrupt blocked syscalls */
if (g_rpc_queue)
for (size_t i = 0; i < g_rpc_queue->rpc_threads_cnt; i++)
DO_SYSCALL(tkill, g_rpc_queue->rpc_threads[i], SIGUSR2);
if (interrupted_in_enclave(uc)) {
/* exception happened in app/LibOS/trusted PAL code, handle signal inside enclave */
pal_get_host_tcb()->sync_signal_cnt++;
sgx_raise(event);
return;
}
/* exception happened in untrusted PAL code (during syscall handling): fatal in Gramine */
unsigned long rip = ucontext_get_ip(uc);
char buf[LOCATION_BUF_SIZE];
pal_describe_location(rip, buf, sizeof(buf));
const char* event_name;
switch (signum) {
case SIGSEGV:
event_name = "segmentation fault (SIGSEGV)";
break;
case SIGILL:
event_name = "illegal instruction (SIGILL)";
break;
case SIGFPE:
event_name = "arithmetic exception (SIGFPE)";
break;
case SIGBUS:
event_name = "memory mapping exception (SIGBUS)";
break;
default:
event_name = "unknown exception";
break;
}
log_error("Unexpected %s occurred inside untrusted PAL (%s)", event_name, buf);
DO_SYSCALL(exit_group, 1);
die_or_inf_loop();
}
static void handle_async_signal(int signum, siginfo_t* info, struct ucontext* uc) {
enum pal_event event = signal_to_pal_event(signum);
__UNUSED(info);
/* send dummy signal to RPC threads so they interrupt blocked syscalls */
if (g_rpc_queue)
for (size_t i = 0; i < g_rpc_queue->rpc_threads_cnt; i++)
DO_SYSCALL(tkill, g_rpc_queue->rpc_threads[i], SIGUSR2);
if (interrupted_in_enclave(uc) || interrupted_in_aex_profiling()) {
/* signal arrived while in app/LibOS/trusted PAL code or when handling another AEX, handle
* signal inside enclave */
pal_get_host_tcb()->async_signal_cnt++;
sgx_raise(event);
return;
}
assert(event == PAL_EVENT_INTERRUPTED || event == PAL_EVENT_QUIT);
if (pal_get_host_tcb()->last_async_event != PAL_EVENT_QUIT) {
/* Do not overwrite `PAL_EVENT_QUIT`. The only other possible event here is
* `PAL_EVENT_INTERRUPTED`, which is basically a no-op (just makes sure that a thread
* notices any new signals or other state changes, which also happens for other events). */
pal_get_host_tcb()->last_async_event = event;
}
uint64_t rip = ucontext_get_ip(uc);
if (rip == (uint64_t)&do_syscall_intr_after_check1
|| rip == (uint64_t)&do_syscall_intr_after_check2) {
ucontext_set_ip(uc, (uint64_t)&do_syscall_intr_eintr);
}
}
static void handle_dummy_signal(int signum, siginfo_t* info, struct ucontext* uc) {
__UNUSED(signum);
__UNUSED(info);
__UNUSED(uc);
/* we need this handler to interrupt blocking syscalls in RPC threads */
}
int sgx_signal_setup(void) {
int ret;
/* SIGCHLD and SIGPIPE are emulated completely inside LibOS */
ret = set_signal_handler(SIGPIPE, SIG_IGN);
if (ret < 0)
goto err;
ret = set_signal_handler(SIGCHLD, SIG_IGN);
if (ret < 0)
goto err;
/* register synchronous signals (exceptions) in host Linux */
ret = set_signal_handler(SIGFPE, handle_sync_signal);
if (ret < 0)
goto err;
ret = set_signal_handler(SIGSEGV, handle_sync_signal);
if (ret < 0)
goto err;
ret = set_signal_handler(SIGBUS, handle_sync_signal);
if (ret < 0)
goto err;
ret = set_signal_handler(SIGILL, handle_sync_signal);
if (ret < 0)
goto err;
/* register asynchronous signals in host Linux */
ret = set_signal_handler(SIGTERM, handle_async_signal);
if (ret < 0)
goto err;
ret = set_signal_handler(SIGCONT, handle_async_signal);
if (ret < 0)
goto err;
/* SIGUSR2 is reserved for Gramine usage: interrupting blocking syscalls in RPC threads.
* We block SIGUSR2 in enclave threads; it is unblocked by each RPC thread explicitly. */
ret = set_signal_handler(SIGUSR2, handle_dummy_signal);
if (ret < 0)
goto err;
ret = block_signal(SIGUSR2, /*block=*/true);
if (ret < 0)
goto err;
ret = 0;
err:
return ret;
}
/* The below function is used by stack protector's __stack_chk_fail(), _FORTIFY_SOURCE's *_chk()
* functions and by assert.h's assert() defined in the common library. Thus it might be called by
* any PAL execution context, including this untrusted context. */
noreturn void pal_abort(void) {
DO_SYSCALL(exit_group, 1);
die_or_inf_loop();
}
void pal_describe_location(uintptr_t addr, char* buf, size_t buf_size) {
#ifdef DEBUG
if (debug_describe_location(addr, buf, buf_size) == 0)
return;
#endif
default_describe_location(addr, buf, buf_size);
}