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