shadow_rs/host/syscall/handler/
signal.rs

1use linux_api::errno::Errno;
2use linux_api::signal::{
3    LinuxDefaultAction, SigAltStackFlags, SigProcMaskAction, Signal, SignalHandler, defaultaction,
4    siginfo_t,
5};
6use shadow_shim_helper_rs::explicit_drop::{ExplicitDrop, ExplicitDropper};
7use shadow_shim_helper_rs::syscall_types::ForeignPtr;
8
9use crate::host::process::Process;
10use crate::host::syscall::handler::{SyscallContext, SyscallHandler, ThreadContext};
11use crate::host::thread::Thread;
12
13impl SyscallHandler {
14    log_syscall!(
15        kill,
16        /* rv */ std::ffi::c_int,
17        /* pid */ linux_api::posix_types::kernel_pid_t,
18        /* sig */ std::ffi::c_int,
19    );
20    pub fn kill(
21        ctx: &mut SyscallContext,
22        pid: linux_api::posix_types::kernel_pid_t,
23        sig: std::ffi::c_int,
24    ) -> Result<(), Errno> {
25        log::trace!("kill called on pid {pid} with signal {sig}");
26
27        let pid = if pid == -1 {
28            // kill(2): If pid equals -1, then sig is sent to every process for which the calling
29            // process has permission to send signals, except for process 1.
30            //
31            // Currently unimplemented, and unlikely to be needed in the context of a shadow
32            // simulation.
33            log::warn!("kill with pid=-1 unimplemented");
34            return Err(Errno::ENOTSUP);
35        } else if pid == 0 {
36            // kill(2): If pid equals 0, then sig is sent to every process in the process group of
37            // the calling process.
38            //
39            // Currently every emulated process is in its own process group.
40            //
41            // FIXME: The above comment is no longer true since implementing fork(). See
42            // https://github.com/shadow/shadow/issues/3315
43            ctx.objs.process.id()
44        } else if pid < -1 {
45            // kill(2): If pid is less than -1, then sig is sent to every process in the process
46            // group whose ID is -pid.
47            //
48            // Currently every emulated process is in its own process group, where pgid=pid.
49            //
50            // FIXME: The above comment is no longer true since implementing fork(). See
51            // https://github.com/shadow/shadow/issues/3315
52            (-pid).try_into().or(Err(Errno::ESRCH))?
53        } else {
54            pid.try_into().or(Err(Errno::ESRCH))?
55        };
56
57        let Some(target_process) = ctx.objs.host.process_borrow(pid) else {
58            log::debug!("Process {pid} not found");
59            return Err(Errno::ESRCH);
60        };
61        let target_process = &*target_process.borrow(ctx.objs.host.root());
62
63        Self::signal_process(ctx.objs, target_process, sig)
64    }
65
66    /// Send a signal to `target_process` from the thread and process in `objs`. A signal of 0 will
67    /// be ignored.
68    fn signal_process(
69        objs: &ThreadContext,
70        target_process: &Process,
71        signal: std::ffi::c_int,
72    ) -> Result<(), Errno> {
73        if signal == 0 {
74            return Ok(());
75        }
76
77        let Ok(signal) = Signal::try_from(signal) else {
78            return Err(Errno::EINVAL);
79        };
80
81        if signal.is_realtime() {
82            log::warn!("Unimplemented signal {signal:?}");
83            return Err(Errno::ENOTSUP);
84        }
85
86        let sender_pid = objs.process.id().into();
87        let siginfo = siginfo_t::new_for_kill(signal, sender_pid, 0);
88
89        target_process.signal(objs.host, Some(objs.thread), &siginfo);
90
91        Ok(())
92    }
93
94    log_syscall!(
95        tkill,
96        /* rv */ std::ffi::c_int,
97        /* pid */ linux_api::posix_types::kernel_pid_t,
98        /* sig */ std::ffi::c_int,
99    );
100    pub fn tkill(
101        ctx: &mut SyscallContext,
102        tid: linux_api::posix_types::kernel_pid_t,
103        sig: std::ffi::c_int,
104    ) -> Result<(), Errno> {
105        log::trace!("tkill called on tid {tid} with signal {sig}");
106
107        let tid = tid.try_into().or(Err(Errno::ESRCH))?;
108
109        let Some(target_thread) = ctx.objs.host.thread_cloned_rc(tid) else {
110            return Err(Errno::ESRCH);
111        };
112        let target_thread = ExplicitDropper::new(target_thread, |value| {
113            value.explicit_drop(ctx.objs.host.root())
114        });
115        let target_thread = &*target_thread.borrow(ctx.objs.host.root());
116
117        Self::signal_thread(ctx.objs, target_thread, sig)
118    }
119
120    log_syscall!(
121        tgkill,
122        /* rv */ std::ffi::c_int,
123        /* tgid */ linux_api::posix_types::kernel_pid_t,
124        /* pid */ linux_api::posix_types::kernel_pid_t,
125        /* sig */ std::ffi::c_int,
126    );
127    pub fn tgkill(
128        ctx: &mut SyscallContext,
129        tgid: linux_api::posix_types::kernel_pid_t,
130        tid: linux_api::posix_types::kernel_pid_t,
131        sig: std::ffi::c_int,
132    ) -> Result<(), Errno> {
133        log::trace!("tgkill called on tgid {tgid} and tid {tid} with signal {sig}");
134
135        let tgid = tgid.try_into().or(Err(Errno::ESRCH))?;
136        let tid = tid.try_into().or(Err(Errno::ESRCH))?;
137
138        let Some(target_thread) = ctx.objs.host.thread_cloned_rc(tid) else {
139            return Err(Errno::ESRCH);
140        };
141        let target_thread = ExplicitDropper::new(target_thread, |value| {
142            value.explicit_drop(ctx.objs.host.root())
143        });
144        let target_thread = &*target_thread.borrow(ctx.objs.host.root());
145
146        if target_thread.process_id() != tgid {
147            return Err(Errno::ESRCH);
148        }
149
150        Self::signal_thread(ctx.objs, target_thread, sig)
151    }
152
153    /// Send a signal to `target_thread` from the thread and process in `objs`. A signal of 0 will
154    /// be ignored.
155    fn signal_thread(
156        objs: &ThreadContext,
157        target_thread: &Thread,
158        signal: std::ffi::c_int,
159    ) -> Result<(), Errno> {
160        if signal == 0 {
161            return Ok(());
162        }
163
164        let Ok(signal) = Signal::try_from(signal) else {
165            return Err(Errno::EINVAL);
166        };
167
168        if signal.is_realtime() {
169            log::warn!("Unimplemented signal {signal:?}");
170            return Err(Errno::ENOTSUP);
171        }
172
173        // need to scope the shmem lock since `wakeup_for_signal` below takes its own shmem lock
174        let mut cond = {
175            let shmem_lock = &*objs.host.shim_shmem_lock_borrow().unwrap();
176
177            let target_process = objs
178                .host
179                .process_borrow(target_thread.process_id())
180                .unwrap();
181            let target_process = &*target_process.borrow(objs.host.root());
182
183            let process_shmem = target_process.borrow_as_runnable().unwrap();
184            let process_shmem = &*process_shmem.shmem();
185            let process_protected = process_shmem.protected.borrow(&shmem_lock.root);
186
187            let thread_shmem = target_thread.shmem();
188            let mut thread_protected = thread_shmem.protected.borrow_mut(&shmem_lock.root);
189
190            let action = unsafe { process_protected.signal_action(signal) };
191            let action_handler = unsafe { action.handler() };
192
193            let signal_is_ignored = match action_handler {
194                SignalHandler::SigIgn => true,
195                SignalHandler::SigDfl => defaultaction(signal) == LinuxDefaultAction::IGN,
196                _ => false,
197            };
198
199            if signal_is_ignored {
200                // don't deliver an ignored signal
201                return Ok(());
202            }
203
204            if thread_protected.pending_signals.has(signal) {
205                // Signal is already pending. From signal(7): In the case where a standard signal is
206                // already pending, the siginfo_t structure (see sigaction(2)) associated with that
207                // signal is not overwritten on arrival of subsequent instances of the same signal.
208                return Ok(());
209            }
210
211            thread_protected.pending_signals.add(signal);
212
213            let sender_pid = objs.process.id();
214            let sender_tid = objs.thread.id();
215
216            let siginfo = siginfo_t::new_for_tkill(signal, sender_pid.into(), 0);
217
218            thread_protected.set_pending_standard_siginfo(signal, &siginfo);
219
220            if sender_tid == target_thread.id() {
221                // Target is the current thread. It'll be handled synchronously when the current
222                // syscall returns (if it's unblocked).
223                return Ok(());
224            }
225
226            if thread_protected.blocked_signals.has(signal) {
227                // Target thread has the signal blocked. We'll leave it pending, but no need to
228                // schedule an event to process the signal. It'll get processed synchronously when
229                // the thread executes a syscall that would unblock the signal.
230                return Ok(());
231            }
232
233            let Some(cond) = target_thread.syscall_condition_mut() else {
234                // We may be able to get here if a thread is signalled before it runs for the first
235                // time. Just return; the signal will be delivered when the thread runs.
236                return Ok(());
237            };
238
239            cond
240        };
241
242        let was_scheduled = cond.wakeup_for_signal(objs.host, signal);
243
244        // it won't be scheduled if the signal is blocked, but we previously checked if the signal
245        // was blocked above
246        assert!(was_scheduled);
247
248        Ok(())
249    }
250
251    log_syscall!(
252        rt_sigaction,
253        /* rv */ std::ffi::c_int,
254        /* sig */ std::ffi::c_int,
255        /* act */ *const std::ffi::c_void,
256        /* oact */ *const std::ffi::c_void,
257        /* sigsetsize */ libc::size_t,
258    );
259    pub fn rt_sigaction(
260        ctx: &mut SyscallContext,
261        sig: std::ffi::c_int,
262        act: ForeignPtr<linux_api::signal::sigaction>,
263        oact: ForeignPtr<linux_api::signal::sigaction>,
264        sigsetsize: libc::size_t,
265    ) -> Result<(), Errno> {
266        // rt_sigaction(2):
267        // > Consequently, a new system call, rt_sigaction(), was added to support an enlarged
268        // > sigset_t type. The new system call takes a fourth argument, size_t sigsetsize, which
269        // > specifies the size in bytes of the signal sets in act.sa_mask and oldact.sa_mask. This
270        // > argument is currently required to have the value sizeof(sigset_t) (or the error EINVAL
271        // > results)
272        // Assuming by "sizeof(sigset_t)" it means the kernel's `linux_sigset_t` and not glibc's
273        // `sigset_t`...
274        if sigsetsize != size_of::<linux_api::signal::sigset_t>() {
275            return Err(Errno::EINVAL);
276        }
277
278        let Ok(sig) = Signal::try_from(sig) else {
279            return Err(Errno::EINVAL);
280        };
281
282        let shmem_lock = ctx.objs.host.shim_shmem_lock_borrow().unwrap();
283        let process_shmem = ctx.objs.process.shmem();
284        let mut process_protected = process_shmem.protected.borrow_mut(&shmem_lock.root);
285
286        if !oact.is_null() {
287            let old_action = unsafe { process_protected.signal_action(sig) };
288            ctx.objs
289                .process
290                .memory_borrow_mut()
291                .write(oact, old_action)?;
292        }
293
294        if act.is_null() {
295            // nothing left to do
296            return Ok(());
297        }
298
299        if sig == Signal::SIGKILL || sig == Signal::SIGSTOP {
300            return Err(Errno::EINVAL);
301        }
302
303        let new_action = ctx.objs.process.memory_borrow().read(act)?;
304        unsafe { *process_protected.signal_action_mut(sig) = new_action };
305
306        Ok(())
307    }
308
309    log_syscall!(
310        rt_sigprocmask,
311        /* rv */ std::ffi::c_int,
312        /* how */ std::ffi::c_int,
313        /* nset */ *const std::ffi::c_void,
314        /* oset */ *const std::ffi::c_void,
315        /* sigsetsize */ libc::size_t,
316    );
317    pub fn rt_sigprocmask(
318        ctx: &mut SyscallContext,
319        how: std::ffi::c_int,
320        nset: ForeignPtr<linux_api::signal::sigset_t>,
321        oset: ForeignPtr<linux_api::signal::sigset_t>,
322        sigsetsize: libc::size_t,
323    ) -> Result<(), Errno> {
324        // From sigprocmask(2): This argument is currently required to have a fixed architecture
325        // specific value (equal to sizeof(kernel_sigset_t)).
326        // We use `sigset_t` from `linux_api`, which is a wrapper around `linux_sigset_t` from
327        // the kernel and should be equivalent to `kernel_sigset_t`.
328        if sigsetsize != size_of::<linux_api::signal::sigset_t>() {
329            warn_once_then_debug!("Bad sigsetsize {sigsetsize}");
330            return Err(Errno::EINVAL);
331        }
332
333        let shmem_lock = ctx.objs.host.shim_shmem_lock_borrow().unwrap();
334        let thread_shmem = ctx.objs.thread.shmem();
335        let mut thread_protected = thread_shmem.protected.borrow_mut(&shmem_lock.root);
336
337        let current_set = thread_protected.blocked_signals;
338
339        if !oset.is_null() {
340            ctx.objs
341                .process
342                .memory_borrow_mut()
343                .write(oset, &current_set)?;
344        }
345
346        if nset.is_null() {
347            // nothing left to do
348            return Ok(());
349        }
350
351        let set = ctx.objs.process.memory_borrow().read(nset)?;
352
353        let set = match SigProcMaskAction::try_from(how) {
354            Ok(SigProcMaskAction::SIG_BLOCK) => set | current_set,
355            Ok(SigProcMaskAction::SIG_UNBLOCK) => !set & current_set,
356            Ok(SigProcMaskAction::SIG_SETMASK) => set,
357            Err(_) => return Err(Errno::EINVAL),
358        };
359
360        thread_protected.blocked_signals = set;
361
362        Ok(())
363    }
364
365    log_syscall!(
366        sigaltstack,
367        /* rv */ std::ffi::c_int,
368        /* uss */ *const std::ffi::c_void,
369        /* uoss */ *const std::ffi::c_void,
370    );
371    pub fn sigaltstack(
372        ctx: &mut SyscallContext,
373        uss: ForeignPtr<linux_api::signal::stack_t>,
374        uoss: ForeignPtr<linux_api::signal::stack_t>,
375    ) -> Result<(), Errno> {
376        let shmem_lock = ctx.objs.host.shim_shmem_lock_borrow().unwrap();
377        let thread_shmem = ctx.objs.thread.shmem();
378        let mut thread_protected = thread_shmem.protected.borrow_mut(&shmem_lock.root);
379
380        let old_ss = unsafe { *thread_protected.sigaltstack() };
381
382        if !uss.is_null() {
383            if old_ss.flags_retain().contains(SigAltStackFlags::SS_ONSTACK) {
384                // sigaltstack(2): EPERM An attempt was made to change the
385                // alternate signal stack while it was active.
386                return Err(Errno::EPERM);
387            }
388
389            let mut new_ss = ctx.objs.process.memory_borrow().read(uss)?;
390            if new_ss.flags_retain().contains(SigAltStackFlags::SS_DISABLE) {
391                // sigaltstack(2): To disable an existing stack, specify ss.ss_flags
392                // as SS_DISABLE. In this case, the kernel ignores any other flags
393                // in ss.ss_flags and the remaining fields in ss.
394                new_ss = shadow_pod::zeroed();
395                new_ss.ss_flags = SigAltStackFlags::SS_DISABLE.bits();
396            }
397
398            let unrecognized_flags = new_ss
399                .flags_retain()
400                .difference(SigAltStackFlags::SS_DISABLE | SigAltStackFlags::SS_AUTODISARM);
401
402            if !unrecognized_flags.is_empty() {
403                warn_once_then_debug!(
404                    "Unrecognized signal stack flags {unrecognized_flags:?} in {:?}",
405                    new_ss.flags_retain(),
406                );
407                // Unrecognized flag.
408                return Err(Errno::EINVAL);
409            }
410
411            *unsafe { thread_protected.sigaltstack_mut() } = new_ss;
412        }
413
414        // It may look wrong that we can return EFAULT here even after we updated the alt stack
415        // above, but this is how Linux behaves.
416        if !uoss.is_null() {
417            ctx.objs.process.memory_borrow_mut().write(uoss, &old_ss)?;
418        }
419
420        Ok(())
421    }
422}
423
424#[cfg(test)]
425mod test {
426    use super::*;
427
428    /// The bitflags crate does some weird things with unrecognized flags, so this test ensures that
429    /// they work as expected for how we use them above.
430    #[test]
431    fn unrecognized_flags_difference() {
432        let foo = 1 << 28;
433        // ensure this flag is unused
434        assert_eq!(SigAltStackFlags::from_bits(foo), None);
435        let foo = SigAltStackFlags::from_bits_retain(foo);
436        assert_eq!(
437            foo.difference(SigAltStackFlags::SS_DISABLE).bits(),
438            (1 << 28) & !SigAltStackFlags::SS_DISABLE.bits(),
439        );
440        assert_eq!(
441            (foo - SigAltStackFlags::SS_DISABLE).bits(),
442            (1 << 28) & !SigAltStackFlags::SS_DISABLE.bits(),
443        );
444    }
445
446    #[test]
447    fn unrecognized_flags_empty() {
448        let foo = 1 << 28;
449        assert_ne!(foo, 0);
450        // ensure this flag is unused
451        assert_eq!(SigAltStackFlags::from_bits(foo), None);
452        let foo = SigAltStackFlags::from_bits_retain(foo);
453        assert_ne!(foo.bits(), 0);
454        assert!(!foo.is_empty());
455    }
456}