1 use crate::context::Context;
2 use crate::error::Error;
3 use crate::instance::{
4 siginfo_ext::SiginfoExt, FaultDetails, Instance, State, TerminationDetails, CURRENT_INSTANCE,
5 HOST_CTX,
6 };
7 use crate::sysdeps::UContextPtr;
8 use lazy_static::lazy_static;
9 use libc::{c_int, c_void, siginfo_t, SIGBUS, SIGSEGV};
10 use lucet_module::TrapCode;
11 use nix::sys::signal::{
12 pthread_sigmask, raise, sigaction, SaFlags, SigAction, SigHandler, SigSet, SigmaskHow, Signal,
13 };
14 use std::mem::MaybeUninit;
15 use std::panic;
16 use std::sync::{Arc, Mutex};
17
18 lazy_static! {
19 // TODO: work out an alternative to this that is signal-safe for `reraise_host_signal_in_handler`
20 static ref LUCET_SIGNAL_STATE: Mutex<Option<SignalState>> = Mutex::new(None);
21 }
22
23 /// The value returned by
24 /// [`Instance.signal_handler`](struct.Instance.html#structfield.signal_handler) to determine the
25 /// outcome of a handled signal.
26 pub enum SignalBehavior {
27 /// Use default behavior, which switches back to the host with `State::Fault` populated.
28 Default,
29 /// Override default behavior and cause the instance to continue.
30 Continue,
31 /// Override default behavior and cause the instance to terminate.
32 Terminate,
33 }
34
35 pub type SignalHandler = dyn Fn(
36 &Instance,
37 &Option<TrapCode>,
38 libc::c_int,
39 *const siginfo_t,
40 *const c_void,
41 ) -> SignalBehavior;
42
signal_handler_none( _inst: &Instance, _trapcode: &Option<TrapCode>, _signum: libc::c_int, _siginfo_ptr: *const siginfo_t, _ucontext_ptr: *const c_void, ) -> SignalBehavior43 pub fn signal_handler_none(
44 _inst: &Instance,
45 _trapcode: &Option<TrapCode>,
46 _signum: libc::c_int,
47 _siginfo_ptr: *const siginfo_t,
48 _ucontext_ptr: *const c_void,
49 ) -> SignalBehavior {
50 SignalBehavior::Default
51 }
52
53 impl Instance {
with_signals_on<F, R>(&mut self, f: F) -> Result<R, Error> where F: FnOnce(&mut Instance) -> Result<R, Error>,54 pub(crate) fn with_signals_on<F, R>(&mut self, f: F) -> Result<R, Error>
55 where
56 F: FnOnce(&mut Instance) -> Result<R, Error>,
57 {
58 // Set up the signal stack for this thread. Note that because signal stacks are per-thread,
59 // rather than per-process, we do this for every run, while the signal handler is installed
60 // only once per process.
61 let guest_sigstack = SigStack::new(
62 self.alloc.slot().sigstack,
63 SigStackFlags::empty(),
64 self.alloc.slot().limits.signal_stack_size,
65 );
66 let previous_sigstack = unsafe { sigaltstack(Some(guest_sigstack)) }
67 .expect("enabling or changing the signal stack succeeds");
68 if let Some(previous_sigstack) = previous_sigstack {
69 assert!(
70 !previous_sigstack
71 .flags()
72 .contains(SigStackFlags::SS_ONSTACK),
73 "an instance was created with a signal stack"
74 );
75 }
76 let mut ostate = LUCET_SIGNAL_STATE.lock().unwrap();
77 if let Some(ref mut state) = *ostate {
78 state.counter += 1;
79 } else {
80 unsafe {
81 setup_guest_signal_state(&mut ostate);
82 }
83 }
84 drop(ostate);
85
86 // run the body
87 let res = f(self);
88
89 let mut ostate = LUCET_SIGNAL_STATE.lock().unwrap();
90 let counter_zero = if let Some(ref mut state) = *ostate {
91 state.counter -= 1;
92 if state.counter == 0 {
93 unsafe {
94 restore_host_signal_state(state);
95 }
96 true
97 } else {
98 false
99 }
100 } else {
101 panic!("signal handlers weren't installed at instance exit");
102 };
103 if counter_zero {
104 *ostate = None;
105 }
106
107 unsafe {
108 // restore the host signal stack for this thread
109 if !altstack_flags()
110 .expect("the current stack flags could be retrieved")
111 .contains(SigStackFlags::SS_ONSTACK)
112 {
113 sigaltstack(previous_sigstack).expect("sigaltstack restoration succeeds");
114 }
115 }
116
117 res
118 }
119 }
120
121 /// Signal handler installed during instance execution.
122 ///
123 /// This function is only designed to handle signals that are the direct result of execution of a
124 /// hardware instruction from the faulting WASM thread. It thus safely assumes the signal is
125 /// directed specifically at this thread (i.e. not a different thread or the process as a whole).
handle_signal(signum: c_int, siginfo_ptr: *mut siginfo_t, ucontext_ptr: *mut c_void)126 extern "C" fn handle_signal(signum: c_int, siginfo_ptr: *mut siginfo_t, ucontext_ptr: *mut c_void) {
127 let signal = Signal::from_c_int(signum).expect("signum is a valid signal");
128 if !(signal == Signal::SIGBUS
129 || signal == Signal::SIGSEGV
130 || signal == Signal::SIGILL
131 || signal == Signal::SIGFPE)
132 {
133 panic!("unexpected signal in guest signal handler: {:?}", signal);
134 }
135 assert!(!siginfo_ptr.is_null(), "siginfo must not be null");
136
137 // Safety: when using a SA_SIGINFO sigaction, the third argument can be cast to a `ucontext_t`
138 // pointer per the manpage
139 assert!(!ucontext_ptr.is_null(), "ucontext_ptr must not be null");
140 let ctx = UContextPtr::new(ucontext_ptr);
141 let rip = ctx.get_ip();
142
143 let switch_to_host = CURRENT_INSTANCE.with(|current_instance| {
144 let mut current_instance = current_instance.borrow_mut();
145
146 if current_instance.is_none() {
147 // If there is no current instance, we've caught a signal raised by a thread that's not
148 // running a lucet instance. Restore the host signal handler and reraise the signal,
149 // then return if the host handler returns
150 unsafe {
151 reraise_host_signal_in_handler(signal, signum, siginfo_ptr, ucontext_ptr);
152 }
153 // don't try context-switching
154 return false;
155 }
156
157 // Safety: the memory pointed to by CURRENT_INSTANCE should be a valid instance. This is not
158 // a trivial property, but relies on the compiler not emitting guest programs that can
159 // overwrite the instance.
160 let inst = unsafe {
161 current_instance
162 .as_mut()
163 .expect("current instance exists")
164 .as_mut()
165 };
166
167 let trapcode = inst.module.lookup_trapcode(rip);
168
169 let behavior = (inst.signal_handler)(inst, &trapcode, signum, siginfo_ptr, ucontext_ptr);
170 match behavior {
171 SignalBehavior::Continue => {
172 // return to the guest context without making any modifications to the instance
173 false
174 }
175 SignalBehavior::Terminate => {
176 // set the state before jumping back to the host context
177 inst.state = State::Terminating {
178 details: TerminationDetails::Signal,
179 };
180 true
181 }
182 SignalBehavior::Default => {
183 // safety: pointer is checked for null at the top of the function, and the
184 // manpage guarantees that a siginfo_t will be passed as the second argument
185 let siginfo = unsafe { *siginfo_ptr };
186 let rip_addr = rip as usize;
187 // If the trap table lookup returned unknown, it is a fatal error
188 let unknown_fault = trapcode.is_none();
189
190 // If the trap was a segv or bus fault and the addressed memory was outside the
191 // guard pages, it is also a fatal error
192 let outside_guard = (siginfo.si_signo == SIGSEGV || siginfo.si_signo == SIGBUS)
193 && !inst.alloc.addr_in_guard_page(siginfo.si_addr_ext());
194
195 // record the fault and jump back to the host context
196 inst.state = State::Faulted {
197 details: FaultDetails {
198 fatal: unknown_fault || outside_guard,
199 trapcode: trapcode,
200 rip_addr,
201 // Details set to `None` here: have to wait until `verify_trap_safety` to
202 // fill in these details, because access may not be signal safe.
203 rip_addr_details: None,
204 },
205 siginfo,
206 context: ctx.into(),
207 };
208 true
209 }
210 }
211 });
212
213 if switch_to_host {
214 HOST_CTX.with(|host_ctx| unsafe {
215 Context::set_from_signal(&*host_ctx.get())
216 .expect("can successfully switch back to the host context");
217 });
218 unreachable!()
219 }
220 }
221
222 struct SignalState {
223 counter: usize,
224 saved_sigbus: SigAction,
225 saved_sigfpe: SigAction,
226 saved_sigill: SigAction,
227 saved_sigsegv: SigAction,
228 saved_panic_hook: Option<Arc<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>>>,
229 }
230
231 // raw pointers in the saved types
232 unsafe impl Send for SignalState {}
233
setup_guest_signal_state(ostate: &mut Option<SignalState>)234 unsafe fn setup_guest_signal_state(ostate: &mut Option<SignalState>) {
235 let mut masked_signals = SigSet::empty();
236 masked_signals.add(Signal::SIGBUS);
237 masked_signals.add(Signal::SIGFPE);
238 masked_signals.add(Signal::SIGILL);
239 masked_signals.add(Signal::SIGSEGV);
240
241 // setup signal handlers
242 let sa = SigAction::new(
243 SigHandler::SigAction(handle_signal),
244 SaFlags::SA_RESTART | SaFlags::SA_SIGINFO | SaFlags::SA_ONSTACK,
245 masked_signals,
246 );
247 let saved_sigbus = sigaction(Signal::SIGBUS, &sa).expect("sigaction succeeds");
248 let saved_sigfpe = sigaction(Signal::SIGFPE, &sa).expect("sigaction succeeds");
249 let saved_sigill = sigaction(Signal::SIGILL, &sa).expect("sigaction succeeds");
250 let saved_sigsegv = sigaction(Signal::SIGSEGV, &sa).expect("sigaction succeeds");
251
252 let saved_panic_hook = Some(setup_guest_panic_hook());
253
254 *ostate = Some(SignalState {
255 counter: 1,
256 saved_sigbus,
257 saved_sigfpe,
258 saved_sigill,
259 saved_sigsegv,
260 saved_panic_hook,
261 });
262 }
263
setup_guest_panic_hook() -> Arc<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>>264 fn setup_guest_panic_hook() -> Arc<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> {
265 let saved_panic_hook = Arc::new(panic::take_hook());
266 let closure_saved_panic_hook = saved_panic_hook.clone();
267 std::panic::set_hook(Box::new(move |panic_info| {
268 if panic_info
269 .payload()
270 .downcast_ref::<TerminationDetails>()
271 .is_none()
272 {
273 closure_saved_panic_hook(panic_info);
274 } else {
275 // this is a panic used to implement instance termination (such as
276 // `lucet_hostcall_terminate!`), so we don't want to print a backtrace; instead, we do
277 // nothing
278 }
279 }));
280 saved_panic_hook
281 }
282
restore_host_signal_state(state: &mut SignalState)283 unsafe fn restore_host_signal_state(state: &mut SignalState) {
284 // restore signal handlers
285 sigaction(Signal::SIGBUS, &state.saved_sigbus).expect("sigaction succeeds");
286 sigaction(Signal::SIGFPE, &state.saved_sigfpe).expect("sigaction succeeds");
287 sigaction(Signal::SIGILL, &state.saved_sigill).expect("sigaction succeeds");
288 sigaction(Signal::SIGSEGV, &state.saved_sigsegv).expect("sigaction succeeds");
289
290 // restore panic hook
291 drop(panic::take_hook());
292 state
293 .saved_panic_hook
294 .take()
295 .map(|hook| Arc::try_unwrap(hook).map(|hook| panic::set_hook(hook)));
296 }
297
reraise_host_signal_in_handler( sig: Signal, signum: libc::c_int, siginfo_ptr: *mut libc::siginfo_t, ucontext_ptr: *mut c_void, )298 unsafe fn reraise_host_signal_in_handler(
299 sig: Signal,
300 signum: libc::c_int,
301 siginfo_ptr: *mut libc::siginfo_t,
302 ucontext_ptr: *mut c_void,
303 ) {
304 let saved_handler = {
305 // TODO: avoid taking a mutex here, probably by having some static muts just for this
306 // function
307 if let Some(ref state) = *LUCET_SIGNAL_STATE.lock().unwrap() {
308 match sig {
309 Signal::SIGBUS => state.saved_sigbus.clone(),
310 Signal::SIGFPE => state.saved_sigfpe.clone(),
311 Signal::SIGILL => state.saved_sigill.clone(),
312 Signal::SIGSEGV => state.saved_sigsegv.clone(),
313 sig => panic!(
314 "unexpected signal in reraise_host_signal_in_handler: {:?}",
315 sig
316 ),
317 }
318 } else {
319 // this case is very fishy; it can arise when the last lucet instance spins down and
320 // uninstalls the lucet handlers while a signal handler is running on this thread, but
321 // before taking the mutex above. The theory is that if this has happened, the host
322 // handler has been reinstalled, so we shouldn't end up back here if we reraise
323
324 // unmask the signal to reraise; we don't have to restore it because the handler will return
325 // after this. If it signals again between here and now, that's a double fault and the
326 // process is going to die anyway
327 let mut unmask = SigSet::empty();
328 unmask.add(sig);
329 pthread_sigmask(SigmaskHow::SIG_UNBLOCK, Some(&unmask), None)
330 .expect("pthread_sigmask succeeds");
331 // if there's no current signal state, just re-raise and hope for the best
332 raise(sig).expect("raise succeeds");
333 return;
334 }
335 };
336
337 match saved_handler.handler() {
338 SigHandler::SigDfl => {
339 // reinstall default signal handler and reraise the signal; this should terminate the
340 // program
341 sigaction(sig, &saved_handler).expect("sigaction succeeds");
342 let mut unmask = SigSet::empty();
343 unmask.add(sig);
344 pthread_sigmask(SigmaskHow::SIG_UNBLOCK, Some(&unmask), None)
345 .expect("pthread_sigmask succeeds");
346 raise(sig).expect("raise succeeds");
347 }
348 SigHandler::SigIgn => {
349 // don't do anything; if we hit this case, whatever program is hosting us is almost
350 // certainly doing something wrong, because our set of signals requires intervention to
351 // proceed
352 return;
353 }
354 SigHandler::Handler(f) => {
355 // call the saved handler directly so there is no altstack confusion
356 f(signum)
357 }
358 SigHandler::SigAction(f) => {
359 // call the saved handler directly so there is no altstack confusion
360 f(signum, siginfo_ptr, ucontext_ptr)
361 }
362 }
363 }
364
365 ////////////////////////////////////////////////////////////////////////////////////////////////////
366 // A collection of wrappers that will be upstreamed to the `nix` crate eventually.
367 ////////////////////////////////////////////////////////////////////////////////////////////////////
368
369 use bitflags::bitflags;
370
371 #[derive(Copy, Clone)]
372 pub struct SigStack {
373 stack: libc::stack_t,
374 }
375
376 impl SigStack {
new(sp: *mut libc::c_void, flags: SigStackFlags, size: libc::size_t) -> SigStack377 pub fn new(sp: *mut libc::c_void, flags: SigStackFlags, size: libc::size_t) -> SigStack {
378 let stack = libc::stack_t {
379 ss_sp: sp,
380 ss_flags: flags.bits(),
381 ss_size: size,
382 };
383 SigStack { stack }
384 }
385
disabled() -> SigStack386 pub fn disabled() -> SigStack {
387 let stack = libc::stack_t {
388 ss_sp: std::ptr::null_mut(),
389 ss_flags: SigStackFlags::SS_DISABLE.bits(),
390 ss_size: libc::SIGSTKSZ,
391 };
392 SigStack { stack }
393 }
394
flags(&self) -> SigStackFlags395 pub fn flags(&self) -> SigStackFlags {
396 SigStackFlags::from_bits_truncate(self.stack.ss_flags)
397 }
398 }
399
400 impl AsRef<libc::stack_t> for SigStack {
as_ref(&self) -> &libc::stack_t401 fn as_ref(&self) -> &libc::stack_t {
402 &self.stack
403 }
404 }
405
406 impl AsMut<libc::stack_t> for SigStack {
as_mut(&mut self) -> &mut libc::stack_t407 fn as_mut(&mut self) -> &mut libc::stack_t {
408 &mut self.stack
409 }
410 }
411
412 bitflags! {
413 pub struct SigStackFlags: libc::c_int {
414 const SS_ONSTACK = libc::SS_ONSTACK;
415 const SS_DISABLE = libc::SS_DISABLE;
416 }
417 }
418
sigaltstack(new_sigstack: Option<SigStack>) -> nix::Result<Option<SigStack>>419 pub unsafe fn sigaltstack(new_sigstack: Option<SigStack>) -> nix::Result<Option<SigStack>> {
420 let mut previous_stack = MaybeUninit::<libc::stack_t>::uninit();
421 let disabled_sigstack = SigStack::disabled();
422 let new_stack = match new_sigstack {
423 None => &disabled_sigstack.stack,
424 Some(ref new_stack) => &new_stack.stack,
425 };
426 let res = libc::sigaltstack(
427 new_stack as *const libc::stack_t,
428 previous_stack.as_mut_ptr(),
429 );
430 nix::errno::Errno::result(res).map(|_| {
431 let sigstack = SigStack {
432 stack: previous_stack.assume_init(),
433 };
434 if sigstack.flags().contains(SigStackFlags::SS_DISABLE) {
435 None
436 } else {
437 Some(sigstack)
438 }
439 })
440 }
441
altstack_flags() -> nix::Result<SigStackFlags>442 pub unsafe fn altstack_flags() -> nix::Result<SigStackFlags> {
443 let mut current_stack = MaybeUninit::<libc::stack_t>::uninit();
444 let res = libc::sigaltstack(std::ptr::null_mut(), current_stack.as_mut_ptr());
445 nix::errno::Errno::result(res)
446 .map(|_| SigStackFlags::from_bits_truncate(current_stack.assume_init().ss_flags))
447 }
448