lucet_runtime_internals/instance/
signals.rs

1use crate::context::Context;
2use crate::error::Error;
3use crate::instance::{
4    siginfo_ext::SiginfoExt, FaultDetails, Instance, State, TerminationDetails, CURRENT_INSTANCE,
5    HOST_CTX,
6};
7use crate::sysdeps::UContextPtr;
8use lazy_static::lazy_static;
9use libc::{c_int, c_void, siginfo_t, SIGBUS, SIGSEGV};
10use lucet_module::TrapCode;
11use nix::sys::signal::{
12    pthread_sigmask, raise, sigaction, SaFlags, SigAction, SigHandler, SigSet, SigmaskHow, Signal,
13};
14use std::mem::MaybeUninit;
15use std::panic;
16use std::sync::{Arc, Mutex};
17
18lazy_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.
26pub 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
35pub type SignalHandler = dyn Fn(
36    &Instance,
37    &Option<TrapCode>,
38    libc::c_int,
39    *const siginfo_t,
40    *const c_void,
41) -> SignalBehavior;
42
43pub 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
53impl Instance {
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).
126extern "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        || signal == Signal::SIGALRM)
133    {
134        panic!("unexpected signal in guest signal handler: {:?}", signal);
135    }
136    assert!(!siginfo_ptr.is_null(), "siginfo must not be null");
137
138    // Safety: when using a SA_SIGINFO sigaction, the third argument can be cast to a `ucontext_t`
139    // pointer per the manpage
140    assert!(!ucontext_ptr.is_null(), "ucontext_ptr must not be null");
141    let ctx = UContextPtr::new(ucontext_ptr);
142    let rip = ctx.get_ip();
143
144    let switch_to_host = CURRENT_INSTANCE.with(|current_instance| {
145        let mut current_instance = current_instance.borrow_mut();
146
147        if current_instance.is_none() {
148            // If there is no current instance, we've caught a signal raised by a thread that's not
149            // running a lucet instance. Restore the host signal handler and reraise the signal,
150            // then return if the host handler returns
151            unsafe {
152                reraise_host_signal_in_handler(signal, signum, siginfo_ptr, ucontext_ptr);
153            }
154            // don't try context-switching
155            return false;
156        }
157
158        // Safety: the memory pointed to by CURRENT_INSTANCE should be a valid instance. This is not
159        // a trivial property, but relies on the compiler not emitting guest programs that can
160        // overwrite the instance.
161        let inst = unsafe {
162            current_instance
163                .as_mut()
164                .expect("current instance exists")
165                .as_mut()
166        };
167
168        if signal == Signal::SIGALRM {
169            // if have gotten a SIGALRM, the killswitch that sent this signal must have also
170            // disabled the `terminable` flag. If this assert fails, the SIGALRM came from some
171            // other source, or we have a bug that allows SIGALRM to be sent when they should not.
172            //
173            // TODO: once we have a notion of logging in `lucet-runtime`, this should be a logged
174            // error.
175            debug_assert!(!inst.kill_state.is_terminable());
176
177            inst.state = State::Terminating {
178                details: TerminationDetails::Remote,
179            };
180            return true;
181        }
182
183        let trapcode = inst.module.lookup_trapcode(rip);
184
185        let behavior = (inst.signal_handler)(inst, &trapcode, signum, siginfo_ptr, ucontext_ptr);
186        let switch_to_host = match behavior {
187            SignalBehavior::Continue => {
188                // return to the guest context without making any modifications to the instance
189                false
190            }
191            SignalBehavior::Terminate => {
192                // set the state before jumping back to the host context
193                inst.state = State::Terminating {
194                    details: TerminationDetails::Signal,
195                };
196
197                true
198            }
199            SignalBehavior::Default => {
200                /*
201                 * /!\ WARNING: LOAD-BEARING THUNK /!\
202                 *
203                 * This thunk, in debug builds, introduces multiple copies of UContext in the local
204                 * stack frame. This also includes a local `State`, which is quite large as well.
205                 * In total, this thunk accounts for roughly 5kb of stack use, where default signal
206                 * stack sizes are typically 8kb total.
207                 *
208                 * In code paths that do not pass through this (such as immediately reraising as a
209                 * host signal), the code in this thunk would force an exhaustion of more than half
210                 * the stack, significantly increasing the likelihood the Lucet signal handler may
211                 * overflow some other thread with a minimal stack size.
212                 */
213                let mut thunk = || {
214                    // safety: pointer is checked for null at the top of the function, and the
215                    // manpage guarantees that a siginfo_t will be passed as the second argument
216                    let siginfo = unsafe { *siginfo_ptr };
217                    let rip_addr = rip as usize;
218                    // If the trap table lookup returned unknown, it is a fatal error
219                    let unknown_fault = trapcode.is_none();
220
221                    // If the trap was a segv or bus fault and the addressed memory was in the
222                    // signal stack guard page or outside the alloc entirely, the fault is fatal
223                    let outside_guard = (siginfo.si_signo == SIGSEGV || siginfo.si_signo == SIGBUS)
224                        && inst
225                            .alloc
226                            .addr_location(siginfo.si_addr_ext())
227                            .is_fault_fatal();
228
229                    // record the fault and jump back to the host context
230                    inst.state = State::Faulted {
231                        details: FaultDetails {
232                            fatal: unknown_fault || outside_guard,
233                            trapcode: trapcode,
234                            rip_addr,
235                            // Details set to `None` here: have to wait until `verify_trap_safety` to
236                            // fill in these details, because access may not be signal safe.
237                            rip_addr_details: None,
238                        },
239                        siginfo,
240                        context: ctx.into(),
241                    };
242                };
243
244                thunk();
245                true
246            }
247        };
248
249        if switch_to_host {
250            // we must disable termination so no KillSwitch may fire in host code.
251            inst.kill_state.disable_termination();
252        }
253
254        switch_to_host
255    });
256
257    if switch_to_host {
258        HOST_CTX.with(|host_ctx| unsafe {
259            Context::set_from_signal(&*host_ctx.get())
260                .expect("can successfully switch back to the host context");
261        });
262        unreachable!()
263    }
264}
265
266struct SignalState {
267    counter: usize,
268    saved_sigbus: SigAction,
269    saved_sigfpe: SigAction,
270    saved_sigill: SigAction,
271    saved_sigsegv: SigAction,
272    saved_sigalrm: SigAction,
273    saved_panic_hook: Option<Arc<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>>>,
274}
275
276// raw pointers in the saved types
277unsafe impl Send for SignalState {}
278
279unsafe fn setup_guest_signal_state(ostate: &mut Option<SignalState>) {
280    let mut masked_signals = SigSet::empty();
281    masked_signals.add(Signal::SIGBUS);
282    masked_signals.add(Signal::SIGFPE);
283    masked_signals.add(Signal::SIGILL);
284    masked_signals.add(Signal::SIGSEGV);
285    masked_signals.add(Signal::SIGALRM);
286
287    // setup signal handlers
288    let sa = SigAction::new(
289        SigHandler::SigAction(handle_signal),
290        SaFlags::SA_RESTART | SaFlags::SA_SIGINFO | SaFlags::SA_ONSTACK,
291        masked_signals,
292    );
293    let saved_sigbus = sigaction(Signal::SIGBUS, &sa).expect("sigaction succeeds");
294    let saved_sigfpe = sigaction(Signal::SIGFPE, &sa).expect("sigaction succeeds");
295    let saved_sigill = sigaction(Signal::SIGILL, &sa).expect("sigaction succeeds");
296    let saved_sigsegv = sigaction(Signal::SIGSEGV, &sa).expect("sigaction succeeds");
297    let saved_sigalrm = sigaction(Signal::SIGALRM, &sa).expect("sigaction succeeds");
298
299    let saved_panic_hook = Some(setup_guest_panic_hook());
300
301    *ostate = Some(SignalState {
302        counter: 1,
303        saved_sigbus,
304        saved_sigfpe,
305        saved_sigill,
306        saved_sigsegv,
307        saved_sigalrm,
308        saved_panic_hook,
309    });
310}
311
312fn setup_guest_panic_hook() -> Arc<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> {
313    let saved_panic_hook = Arc::new(panic::take_hook());
314    let closure_saved_panic_hook = saved_panic_hook.clone();
315    std::panic::set_hook(Box::new(move |panic_info| {
316        if panic_info
317            .payload()
318            .downcast_ref::<TerminationDetails>()
319            .is_none()
320        {
321            closure_saved_panic_hook(panic_info);
322        } else {
323            // this is a panic used to implement instance termination (such as
324            // `lucet_hostcall_terminate!`), so we don't want to print a backtrace; instead, we do
325            // nothing
326        }
327    }));
328    saved_panic_hook
329}
330
331unsafe fn restore_host_signal_state(state: &mut SignalState) {
332    // restore signal handlers
333    sigaction(Signal::SIGBUS, &state.saved_sigbus).expect("sigaction succeeds");
334    sigaction(Signal::SIGFPE, &state.saved_sigfpe).expect("sigaction succeeds");
335    sigaction(Signal::SIGILL, &state.saved_sigill).expect("sigaction succeeds");
336    sigaction(Signal::SIGSEGV, &state.saved_sigsegv).expect("sigaction succeeds");
337    sigaction(Signal::SIGALRM, &state.saved_sigalrm).expect("sigaction succeeds");
338
339    // restore panic hook
340    drop(panic::take_hook());
341    state
342        .saved_panic_hook
343        .take()
344        .map(|hook| Arc::try_unwrap(hook).map(|hook| panic::set_hook(hook)));
345}
346
347unsafe fn reraise_host_signal_in_handler(
348    sig: Signal,
349    signum: libc::c_int,
350    siginfo_ptr: *mut libc::siginfo_t,
351    ucontext_ptr: *mut c_void,
352) {
353    let saved_handler = {
354        // TODO: avoid taking a mutex here, probably by having some static muts just for this
355        // function
356        if let Some(ref state) = *LUCET_SIGNAL_STATE.lock().unwrap() {
357            match sig {
358                Signal::SIGBUS => state.saved_sigbus.clone(),
359                Signal::SIGFPE => state.saved_sigfpe.clone(),
360                Signal::SIGILL => state.saved_sigill.clone(),
361                Signal::SIGSEGV => state.saved_sigsegv.clone(),
362                Signal::SIGALRM => state.saved_sigalrm.clone(),
363                sig => panic!(
364                    "unexpected signal in reraise_host_signal_in_handler: {:?}",
365                    sig
366                ),
367            }
368        } else {
369            // this case is very fishy; it can arise when the last lucet instance spins down and
370            // uninstalls the lucet handlers while a signal handler is running on this thread, but
371            // before taking the mutex above. The theory is that if this has happened, the host
372            // handler has been reinstalled, so we shouldn't end up back here if we reraise
373
374            // unmask the signal to reraise; we don't have to restore it because the handler will return
375            // after this. If it signals again between here and now, that's a double fault and the
376            // process is going to die anyway
377            let mut unmask = SigSet::empty();
378            unmask.add(sig);
379            pthread_sigmask(SigmaskHow::SIG_UNBLOCK, Some(&unmask), None)
380                .expect("pthread_sigmask succeeds");
381            // if there's no current signal state, just re-raise and hope for the best
382            raise(sig).expect("raise succeeds");
383            return;
384        }
385    };
386
387    match saved_handler.handler() {
388        SigHandler::SigDfl => {
389            // reinstall default signal handler and reraise the signal; this should terminate the
390            // program
391            sigaction(sig, &saved_handler).expect("sigaction succeeds");
392            let mut unmask = SigSet::empty();
393            unmask.add(sig);
394            pthread_sigmask(SigmaskHow::SIG_UNBLOCK, Some(&unmask), None)
395                .expect("pthread_sigmask succeeds");
396            raise(sig).expect("raise succeeds");
397        }
398        SigHandler::SigIgn => {
399            // don't do anything; if we hit this case, whatever program is hosting us is almost
400            // certainly doing something wrong, because our set of signals requires intervention to
401            // proceed
402            return;
403        }
404        SigHandler::Handler(f) => {
405            // call the saved handler directly so there is no altstack confusion
406            f(signum)
407        }
408        SigHandler::SigAction(f) => {
409            // call the saved handler directly so there is no altstack confusion
410            f(signum, siginfo_ptr, ucontext_ptr)
411        }
412    }
413}
414
415////////////////////////////////////////////////////////////////////////////////////////////////////
416// A collection of wrappers that will be upstreamed to the `nix` crate eventually.
417////////////////////////////////////////////////////////////////////////////////////////////////////
418
419use bitflags::bitflags;
420
421#[derive(Copy, Clone)]
422pub struct SigStack {
423    stack: libc::stack_t,
424}
425
426impl SigStack {
427    pub fn new(sp: *mut libc::c_void, flags: SigStackFlags, size: libc::size_t) -> SigStack {
428        let stack = libc::stack_t {
429            ss_sp: sp,
430            ss_flags: flags.bits(),
431            ss_size: size,
432        };
433        SigStack { stack }
434    }
435
436    pub fn disabled() -> SigStack {
437        let stack = libc::stack_t {
438            ss_sp: std::ptr::null_mut(),
439            ss_flags: SigStackFlags::SS_DISABLE.bits(),
440            ss_size: libc::SIGSTKSZ,
441        };
442        SigStack { stack }
443    }
444
445    pub fn flags(&self) -> SigStackFlags {
446        SigStackFlags::from_bits_truncate(self.stack.ss_flags)
447    }
448}
449
450impl AsRef<libc::stack_t> for SigStack {
451    fn as_ref(&self) -> &libc::stack_t {
452        &self.stack
453    }
454}
455
456impl AsMut<libc::stack_t> for SigStack {
457    fn as_mut(&mut self) -> &mut libc::stack_t {
458        &mut self.stack
459    }
460}
461
462bitflags! {
463    pub struct SigStackFlags: libc::c_int {
464        const SS_ONSTACK = libc::SS_ONSTACK;
465        const SS_DISABLE = libc::SS_DISABLE;
466    }
467}
468
469pub unsafe fn sigaltstack(new_sigstack: Option<SigStack>) -> nix::Result<Option<SigStack>> {
470    let mut previous_stack = MaybeUninit::<libc::stack_t>::uninit();
471    let disabled_sigstack = SigStack::disabled();
472    let new_stack = match new_sigstack {
473        None => &disabled_sigstack.stack,
474        Some(ref new_stack) => &new_stack.stack,
475    };
476    let res = libc::sigaltstack(
477        new_stack as *const libc::stack_t,
478        previous_stack.as_mut_ptr(),
479    );
480    nix::errno::Errno::result(res).map(|_| {
481        let sigstack = SigStack {
482            stack: previous_stack.assume_init(),
483        };
484        if sigstack.flags().contains(SigStackFlags::SS_DISABLE) {
485            None
486        } else {
487            Some(sigstack)
488        }
489    })
490}
491
492pub unsafe fn altstack_flags() -> nix::Result<SigStackFlags> {
493    let mut current_stack = MaybeUninit::<libc::stack_t>::uninit();
494    let res = libc::sigaltstack(std::ptr::null_mut(), current_stack.as_mut_ptr());
495    nix::errno::Errno::result(res)
496        .map(|_| SigStackFlags::from_bits_truncate(current_stack.assume_init().ss_flags))
497}