shadow_shim/signals.rs
1use core::cell::Cell;
2
3use linux_api::signal::{
4 SigActionFlags, SigAltStackFlags, Signal, SignalHandler, defaultaction, sigaction, siginfo_t,
5 sigset_t, stack_t,
6};
7use linux_api::ucontext::ucontext;
8use log::{trace, warn};
9use shadow_shim_helper_rs::shim_shmem;
10
11use crate::tls::ShimTlsVar;
12use crate::{ExecutionContext, global_host_shmem, tls_process_shmem, tls_thread_shmem};
13
14/// Information passed through to the SIGUSR1 signal handler. Contains the info
15/// needed to call a managed code signal handler.
16struct Sigusr1Info {
17 native_sigaltstack: Option<stack_t>,
18 siginfo: siginfo_t,
19 action: sigaction,
20 // May be NULL, in the case that we didn't get here in the context of an
21 // earlier signal handler (e.g. seccomp).
22
23 // We don't copy by value in case additional fields are added to the stuct
24 // definition, and because we currently accept a libc::ucontext_t in our C
25 // API, which *does* have extra fields at the end.
26 ctx: *mut ucontext,
27}
28static SIGUSR1_SIGINFO: ShimTlsVar<Cell<Option<Sigusr1Info>>> =
29 ShimTlsVar::new(&crate::SHIM_TLS, || Cell::new(None));
30
31extern "C" fn handle_sigusr1(_signo: i32, _info: *mut siginfo_t, ctx: *mut core::ffi::c_void) {
32 debug_assert_eq!(
33 ExecutionContext::current(),
34 ExecutionContext::Shadow,
35 "Native sigusr1 unexpectedly raised from non-shadow code"
36 );
37
38 let mut info = SIGUSR1_SIGINFO.get().take().unwrap();
39 let signo = info.siginfo.signal().unwrap().as_i32();
40 // SAFETY: Should have been initialized correctly in `process_signals`.
41 let handler = unsafe { info.action.handler() };
42
43 if let Some(stack) = &info.native_sigaltstack {
44 // We temporarily switched the sigaltstack so that this handler would
45 // run on the specified stack. Now switch back to the native sigaltstack (i.e.
46 // the one Shadow originally configured for *it's* signal handling).
47 unsafe { linux_api::signal::sigaltstack(Some(stack), None) }.unwrap();
48 }
49
50 // SAFETY: Not particularly. We're calling a handler provided by managed code, which
51 // we don't attempt to analyze or sandbox. A "well behaved" handler should be safe to
52 // call here, but it could do anything including things that are unsound in Rust.
53 match handler {
54 linux_api::signal::SignalHandler::Handler(handler_fn) => {
55 let _prev = ExecutionContext::Application.enter();
56 unsafe { handler_fn(signo) }
57 }
58 linux_api::signal::SignalHandler::Action(action_fn) => unsafe {
59 // If there's an "earlier" context, we use it. This might be important e.g.
60 // when handling a signal like SIGSEGV, where the handler might actually
61 // inspect individual register values.
62 //
63 // Otherwise, use the the context that the kernel gave us for *this* signal
64 // handler. The register values won't make much sense to the handler, but
65 // it should WAI with functionality like `swapcontext`, which might be done
66 // in an implementation of user-space threads.
67 let ctx: *mut ucontext = if info.ctx.is_null() {
68 log::warn!("Passing a synthetic context to managed code signal handler");
69 ctx.cast()
70 } else {
71 info.ctx
72 };
73 {
74 let _prev = ExecutionContext::Application.enter();
75 action_fn(signo, &mut info.siginfo, ctx.cast::<core::ffi::c_void>())
76 }
77 },
78 linux_api::signal::SignalHandler::SigIgn | linux_api::signal::SignalHandler::SigDfl => {
79 panic!("No handler")
80 }
81 }
82}
83
84fn die_with_fatal_signal(sig: Signal) -> ! {
85 assert_eq!(ExecutionContext::current(), ExecutionContext::Shadow);
86 if sig == Signal::SIGKILL {
87 // No need to restore default action, and trying to do so would fail.
88 } else {
89 let action = sigaction::new_with_default_restorer(
90 SignalHandler::SigDfl,
91 SigActionFlags::empty(),
92 sigset_t::EMPTY,
93 );
94 unsafe { linux_api::signal::rt_sigaction(sig, &action, None) }.unwrap();
95 }
96 let pid = rustix::process::getpid();
97 rustix::process::kill_process(pid, rustix::process::Signal::from_raw(sig.into()).unwrap())
98 .unwrap();
99 unreachable!()
100}
101
102/// Handle pending unblocked signals, and return whether *all* corresponding
103/// signal actions had the SA_RESTART flag set.
104///
105/// # Safety
106///
107/// Configured handlers for all pending unblocked signals must be safe to call. (Which
108/// we basically can't ensure).
109pub unsafe fn process_signals(mut ucontext: Option<&mut ucontext>) -> bool {
110 debug_assert_eq!(ExecutionContext::current(), ExecutionContext::Shadow);
111
112 let mut host = crate::global_host_shmem::get();
113 let mut host_lock = host.protected().lock();
114
115 let mut restartable = true;
116
117 loop {
118 let Some((sig, siginfo)) = tls_process_shmem::with(|process| {
119 tls_thread_shmem::with(|thread| {
120 shim_shmem::take_pending_unblocked_signal(&host_lock, process, thread)
121 })
122 }) else {
123 break;
124 };
125
126 let action = tls_process_shmem::with(|process| unsafe {
127 *process.protected.borrow(&host_lock.root).signal_action(sig)
128 });
129
130 if matches!(unsafe { action.handler() }, SignalHandler::SigIgn) {
131 continue;
132 }
133
134 if matches!(unsafe { action.handler() }, SignalHandler::SigDfl) {
135 match defaultaction(sig) {
136 linux_api::signal::LinuxDefaultAction::IGN => continue,
137 linux_api::signal::LinuxDefaultAction::CORE
138 | linux_api::signal::LinuxDefaultAction::TERM => {
139 drop(host_lock);
140 die_with_fatal_signal(sig);
141 }
142 linux_api::signal::LinuxDefaultAction::STOP => unimplemented!(),
143 linux_api::signal::LinuxDefaultAction::CONT => unimplemented!(),
144 }
145 }
146
147 trace!("Handling emulated signal {sig:?}");
148
149 let (sigaltstack_orig_emu, mask_orig_emu): (stack_t, sigset_t) =
150 tls_thread_shmem::with(|thread| {
151 let t = thread.protected.borrow(&host_lock.root);
152 // SAFETY: Pointers in the sigaltstack are valid in the managed process.
153 let stack = unsafe { t.sigaltstack() };
154 (*stack, t.blocked_signals)
155 });
156
157 let mask_emu_during_handler = {
158 let mut m = action.mask() | mask_orig_emu;
159 if !action.flags_retain().contains(SigActionFlags::SA_NODEFER) {
160 m.add(sig)
161 }
162 m
163 };
164 tls_thread_shmem::with(|thread| {
165 thread.protected.borrow_mut(&host_lock.root).blocked_signals = mask_emu_during_handler
166 });
167
168 if action.flags_retain().contains(SigActionFlags::SA_RESETHAND) {
169 tls_process_shmem::with(|process| {
170 // SAFETY: The handler (`SigDfl`) is sound.
171 unsafe {
172 *process
173 .protected
174 .borrow_mut(&host_lock.root)
175 .signal_action_mut(sig) = sigaction::new_with_default_restorer(
176 SignalHandler::SigDfl,
177 SigActionFlags::empty(),
178 sigset_t::EMPTY,
179 )
180 };
181 });
182 }
183
184 if !action.flags_retain().contains(SigActionFlags::SA_RESTART) {
185 restartable = false;
186 }
187
188 let sigaltstack_orig_native = if action.flags_retain().contains(SigActionFlags::SA_ONSTACK)
189 && !sigaltstack_orig_emu
190 .flags_retain()
191 .contains(SigAltStackFlags::SS_DISABLE)
192 {
193 // Call the handler on the configured stack.
194
195 if sigaltstack_orig_emu
196 .flags_retain()
197 .contains(SigAltStackFlags::SS_ONSTACK)
198 {
199 // The specified stack is already in use.
200 //
201 // This *could* be ok, e.g. if the stack is in use by the
202 // current thread, and never unwound back to the earlier use;
203 // e.g. if the handler exits the process. golang appears to do
204 // this in its default SIGTERM handling. (See
205 // https://github.com/shadow/shadow/issues/3395).
206 //
207 // In other cases things could go horribly, but it'd be a bug in
208 // the managed process rather than in shadow itself.
209 log::debug!(
210 "Signal handler configured to switch to a stack that's already in use. This could go badly."
211 )
212 }
213
214 // Update the signal-stack configuration while the handler is being run.
215 let sigaltstack_emu_during_handler = if sigaltstack_orig_emu
216 .flags_retain()
217 .contains(SigAltStackFlags::SS_AUTODISARM)
218 {
219 stack_t::new(core::ptr::null_mut(), SigAltStackFlags::SS_DISABLE, 0)
220 } else {
221 stack_t::new(
222 sigaltstack_orig_emu.sp(),
223 sigaltstack_orig_emu.flags_retain() | SigAltStackFlags::SS_ONSTACK,
224 sigaltstack_orig_emu.size(),
225 )
226 };
227 tls_thread_shmem::with(|thread| {
228 // SAFETY: stack pointer in the assigned stack (if any) is valid in
229 // the managed process.
230 unsafe {
231 *thread
232 .protected
233 .borrow_mut(&host_lock.root)
234 .sigaltstack_mut() = sigaltstack_emu_during_handler
235 };
236 });
237
238 let mut sigaltstack_orig_native =
239 stack_t::new(core::ptr::null_mut(), SigAltStackFlags::empty(), 0);
240 // Set the *native* sigaltstack to the *emulated* sigaltstack,
241 // letting the kernel do the stack switch for us.
242 unsafe {
243 linux_api::signal::sigaltstack(
244 Some(&stack_t::new(
245 sigaltstack_orig_emu.sp(),
246 SigAltStackFlags::SS_AUTODISARM,
247 sigaltstack_orig_emu.size(),
248 )),
249 Some(&mut sigaltstack_orig_native),
250 )
251 }
252 .unwrap();
253 Some(sigaltstack_orig_native)
254 } else {
255 None
256 };
257
258 // Package up what our native signal handler will need to invoke the
259 // managed code syscall handler for the emulated signal.
260 let prev = SIGUSR1_SIGINFO.get().replace(Some(Sigusr1Info {
261 native_sigaltstack: sigaltstack_orig_native,
262 siginfo,
263 action,
264 ctx: ucontext
265 .as_mut()
266 .map(|c| core::ptr::from_mut(*c))
267 .unwrap_or(core::ptr::null_mut()),
268 }));
269 assert!(prev.is_none());
270
271 // Drop locks and references, since the handler could do ~anything,
272 // including exit, recurse to here again, or `swapcontext` and never
273 // return.
274 drop(host_lock);
275 drop(host);
276
277 // We raise a signal natively to let the kernel create a ucontext for us
278 // and switch stacks. We invoke the managed code's signal handler from our
279 // signal handler.
280 //
281 // We could potentially skip this if the managed code signal handler isn't
282 // configured to switch stacks and either doesn't need a context or we already
283 // have one. But that'd mean another code path to maintain, and signal
284 // handling shouldn't be on the hot path of performance for most
285 // applications. (We could also consider implementing the stack switch
286 // and/or creation of a ucontext ourselves, but again that would be more
287 // complex code to maintain).
288
289 // We install the signal handler every time, so that we can decide
290 // whether to set `SA_ONSTACK` or not based on whether we actually need
291 // to switch stacks.
292 let flags = SigActionFlags::SA_SIGINFO
293 | SigActionFlags::SA_NODEFER
294 | SigActionFlags::SA_RESETHAND
295 | if sigaltstack_orig_native.is_some() {
296 SigActionFlags::SA_ONSTACK
297 } else {
298 SigActionFlags::empty()
299 };
300 // SAFETY: `handle_sigusr1` is sound, if the handler we're calling is.
301 unsafe {
302 linux_api::signal::rt_sigaction(
303 Signal::SIGUSR1,
304 &sigaction::new_with_default_restorer(
305 SignalHandler::Action(handle_sigusr1),
306 flags,
307 sigset_t::EMPTY,
308 ),
309 None,
310 )
311 }
312 .unwrap();
313
314 let pid = rustix::process::getpid();
315 let tid = rustix::thread::gettid();
316 linux_api::signal::tgkill(pid.into(), tid.into(), Some(Signal::SIGUSR1)).unwrap();
317
318 // It's not unheard of for a signal handler to use setcontext to jump
319 // out of the signal handler to the point where the signal was raised
320 // instead of returning normally, e.g. for userspace scheduling.
321 // In that case we'll still be in `ExecutionContext::Application`, since
322 // the jump would have skipped over the execution context restorer's
323 // drop impl above. This is done in our `test_signals.rs` test.
324 //
325 // Force back to `ExecutionContext::Shadow`.
326 ExecutionContext::Shadow.enter_without_restorer();
327
328 // Reacquire locks and references.
329 host = crate::global_host_shmem::get();
330 host_lock = host.protected().lock();
331
332 // Restore mask and stack
333 tls_thread_shmem::with(|thread| {
334 let mut thread = thread.protected.borrow_mut(&host_lock.root);
335 thread.blocked_signals = mask_orig_emu;
336 // SAFETY: Pointers are valid in managed process.
337 unsafe { *thread.sigaltstack_mut() = sigaltstack_orig_emu };
338 if let Some(s) = sigaltstack_orig_native {
339 // SAFETY: We're restoring the previous, presumably valid, stack.
340 unsafe { linux_api::signal::sigaltstack(Some(&s), None) }.unwrap();
341 }
342 });
343 }
344 restartable
345}
346
347/// Handle a hardware error signal that was raised in `exe_ctx`.
348///
349/// # Safety
350///
351/// Configured handlers for all pending unblocked signals must be safe to call. (Which
352/// we basically can't ensure).
353unsafe fn handle_hardware_error_signal_inner(
354 exe_ctx: ExecutionContext,
355 signal: Signal,
356 info: &mut siginfo_t,
357 uctx: Option<&mut ucontext>,
358) {
359 if exe_ctx == ExecutionContext::Shadow {
360 // Error was raised from shim code.
361 die_with_fatal_signal(signal);
362 }
363
364 // Otherwise the error was raised from managed code, and could potentially
365 // be handled by a signal handler that it installed.
366
367 tls_thread_shmem::with(|thread| {
368 let host = global_host_shmem::get();
369 let host_lock = host.protected().lock();
370 let pending_signals = thread.protected.borrow(&host_lock.root).pending_signals;
371 if pending_signals.has(signal) {
372 warn!("Received signal {signal:?} when it was already pending");
373 } else {
374 let mut thread_protected = thread.protected.borrow_mut(&host_lock.root);
375 thread_protected.pending_signals |= signal.into();
376 thread_protected.set_pending_standard_siginfo(signal, info);
377 }
378 });
379
380 unsafe { process_signals(uctx) };
381}
382
383unsafe extern "C" fn handle_hardware_error_signal(
384 signo: i32,
385 info: *mut siginfo_t,
386 uctx: *mut core::ffi::c_void,
387) {
388 let prev_ctx = ExecutionContext::Shadow.enter();
389 let signal = Signal::try_from(signo).unwrap();
390 // SAFETY: The kernel should have given us a valid `siginfo_t` here.
391 let info = unsafe { info.as_mut().unwrap() };
392 // SAFETY: The kernel should have given us a valid `ucontext` here.
393 let uctx = unsafe { uctx.cast::<ucontext>().as_mut() };
394 // SAFETY: We can only assume that the signal handlers are sound.
395 unsafe { handle_hardware_error_signal_inner(prev_ctx.ctx(), signal, info, uctx) };
396}
397
398pub fn install_hardware_error_handlers() {
399 // SA_NODEFER: Don't block the current signal in the handler.
400 // Generating one of these signals while it is blocked is
401 // undefined behavior; the handler itself detects recursion.
402 // SA_SIGINFO: Required because we're specifying
403 // sa_sigaction.
404 // SA_ONSTACK: Use the alternate signal handling stack,
405 // to avoid interfering with userspace thread stacks.
406 let flags =
407 SigActionFlags::SA_SIGINFO | SigActionFlags::SA_NODEFER | SigActionFlags::SA_ONSTACK;
408 let handler = SignalHandler::Action(handle_hardware_error_signal);
409 let action = sigaction::new_with_default_restorer(handler, flags, sigset_t::EMPTY);
410 for signal in [
411 Signal::SIGSEGV,
412 Signal::SIGILL,
413 Signal::SIGBUS,
414 Signal::SIGFPE,
415 ] {
416 // SAFETY: We've set up a valid handler.
417 unsafe { linux_api::signal::rt_sigaction(signal, &action, None) }.unwrap();
418 }
419}
420
421mod export {
422 use super::*;
423
424 /// Handle pending unblocked signals, and return whether *all* corresponding
425 /// signal actions had the SA_RESTART flag set.
426 ///
427 /// `ucontext` will be passed through to handlers if non-NULL. This should
428 /// generally only be done if the caller has a `ucontext` that will be swapped to
429 /// after this code returns; e.g. one that was passed to our own signal handler,
430 /// which will be swapped to when that handler returns.
431 ///
432 /// If `ucontext` is NULL, one will be created at the point where we invoke
433 /// the handler, and swapped back to when it returns.
434 /// TODO: Creating `ucontext_t` is currently only implemented for handlers that
435 /// execute on a sigaltstack.
436 ///
437 /// # Safety
438 ///
439 /// `ucontext` must be dereferenceable if not NULL.
440 ///
441 /// Configured handlers for all pending unblocked signals must be safe to call. (Which
442 /// we basically can't ensure).
443 #[unsafe(no_mangle)]
444 pub unsafe extern "C-unwind" fn shim_process_signals(ucontext: *mut libc::ucontext_t) -> bool {
445 // `libc::ucontext_t` appears to be safe to cast to a kernel `ucontext`; as
446 // verified experimentally and by manual inspection of the definitions.
447 //
448 // The libc definition has some extra fields at the end, but we're
449 // careful not to copy the ucontext so they shouldn't hurt anything.
450 let ucontext: *mut ucontext = ucontext.cast();
451
452 // SAFETY: ensured by caller.
453 unsafe { process_signals(ucontext.as_mut()) }
454 }
455
456 /// Install signal handlers for signals that can be generated by hardware errors.
457 /// e.g. SIGSEGV
458 #[unsafe(no_mangle)]
459 pub unsafe extern "C-unwind" fn shim_install_hardware_error_handlers() {
460 install_hardware_error_handlers()
461 }
462
463 /// Handle a hardware error signal that was raised in `exe_ctx`.
464 ///
465 /// More-specialized error handlers (e.g. for rdtsc) can invoke this handler
466 /// directly when unable to handle the current signal (e.g. when a SIGSEGV wasn't
467 /// caused by an rdtsc instruction).
468 ///
469 /// # Safety
470 ///
471 /// `info` and `ctx` must be non-NULL and safely dereferenceable.
472 #[unsafe(no_mangle)]
473 pub unsafe extern "C-unwind" fn shim_handle_hardware_error_signal(
474 exe_ctx: ExecutionContext,
475 signo: i32,
476 info: *mut siginfo_t,
477 uctx: *mut linux_api::ucontext::linux_ucontext,
478 ) {
479 let signal = Signal::try_from(signo).unwrap();
480 // SAFETY: Caller ensures.
481 let info = unsafe { info.as_mut().unwrap() };
482 // SAFETY: Caller ensures.
483 let uctx = unsafe { uctx.as_mut() };
484 // SAFETY: We can only assume that the signal handlers are sound.
485 unsafe {
486 handle_hardware_error_signal_inner(exe_ctx, signal, info, uctx);
487 }
488 }
489}