Skip to main content

detcore/
signal_restart.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! What a syscall interrupted for a caught signal gives the guest, by Linux's
10//! x86 table (A5 design 16.6 and D40, D43).
11//!
12//! A wait that Detcore ends because a caught signal is pending ends with one
13//! of the kernel's private restart codes. On ptrace the kernel applies the
14//! table itself when it builds the handler's frame. Where Detcore ends the
15//! wait and the kernel never sees the code (an in-guest backend under its
16//! delivery gate, `Config::in_guest_signal_delivery`), Detcore applies the
17//! same table, with the action of the first caught delivery at that return,
18//! and the backend only writes the registers. Ignored instances ahead of it
19//! are discarded without touching the registers, and a later frame at the
20//! same return sees the result the first one left (`-EINTR`, or the call's
21//! number to run again), which is no longer a restart code.
22
23/// The kernel's private restart codes, as negative results
24/// (include/linux/errno.h v6.12 lines 14-18,
25/// <https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/errno.h?h=v6.12#n14>).
26pub const ERESTARTSYS: i64 = -512;
27/// See [`ERESTARTSYS`].
28pub const ERESTARTNOINTR: i64 = -513;
29/// See [`ERESTARTSYS`].
30pub const ERESTARTNOHAND: i64 = -514;
31/// See [`ERESTARTSYS`].
32pub const ERESTART_RESTARTBLOCK: i64 = -516;
33
34/// What the guest's syscall does once the caught signal's frame is built.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum CaughtSignalReturn {
37    /// The call returns this raw result to the guest: `-EINTR` in place of a
38    /// restart code, or the call's own result, which is not one.
39    Returns(i64),
40    /// The call runs again once the handler returns: the result register
41    /// holds the syscall's number again and the instruction pointer moves
42    /// back over the `syscall` instruction, so the guest executes it again.
43    Restarts,
44}
45
46/// The syscall's outcome when a caught signal whose action has `sa_flags` is
47/// delivered at its return with raw result `result` (an errno as its
48/// negative): Linux's `handle_signal`, arch/x86/kernel/signal.c v6.12 lines
49/// 264-282,
50/// <https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/x86/kernel/signal.c?h=v6.12#n264>:
51/// - `-ERESTART_RESTARTBLOCK` and `-ERESTARTNOHAND` return `-EINTR`;
52/// - `-ERESTARTSYS` returns `-EINTR` without `SA_RESTART`, and restarts
53///   with it;
54/// - `-ERESTARTNOINTR` restarts, whatever the flags;
55/// - any other result is returned as it is.
56///
57/// Only for the first caught delivery at the return. With no handler at all,
58/// the kernel restarts instead (lines 343-357): `-ERESTARTNOHAND`,
59/// `-ERESTARTSYS` and `-ERESTARTNOINTR` run the same call again, and
60/// `-ERESTART_RESTARTBLOCK` runs `restart_syscall` (lines 353-356), not the
61/// same call. Neither that case nor a default-fatal delivery, which ends the
62/// process, comes here.
63pub fn caught_signal_return(result: i64, sa_flags: u64) -> CaughtSignalReturn {
64    const EINTR: i64 = -(libc::EINTR as i64);
65    let sa_restart = libc::SA_RESTART as u32 as u64;
66    match result {
67        ERESTART_RESTARTBLOCK | ERESTARTNOHAND => CaughtSignalReturn::Returns(EINTR),
68        ERESTARTSYS if sa_flags & sa_restart == 0 => CaughtSignalReturn::Returns(EINTR),
69        ERESTARTSYS | ERESTARTNOINTR => CaughtSignalReturn::Restarts,
70        other => CaughtSignalReturn::Returns(other),
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    const EINTR: i64 = -(libc::EINTR as i64);
79    const SA_RESTART: u64 = libc::SA_RESTART as u32 as u64;
80    /// Flags beside `SA_RESTART`, which change no row.
81    const OTHER_FLAGS: u64 = (libc::SA_SIGINFO | libc::SA_ONSTACK | libc::SA_NODEFER) as u64;
82
83    #[test]
84    fn erestart_restartblock_returns_eintr_with_or_without_sa_restart() {
85        for flags in [0, SA_RESTART, OTHER_FLAGS | SA_RESTART] {
86            assert_eq!(
87                caught_signal_return(ERESTART_RESTARTBLOCK, flags),
88                CaughtSignalReturn::Returns(EINTR)
89            );
90        }
91    }
92
93    #[test]
94    fn erestartnohand_returns_eintr_with_or_without_sa_restart() {
95        for flags in [0, SA_RESTART, OTHER_FLAGS | SA_RESTART] {
96            assert_eq!(
97                caught_signal_return(ERESTARTNOHAND, flags),
98                CaughtSignalReturn::Returns(EINTR)
99            );
100        }
101    }
102
103    #[test]
104    fn erestartsys_without_sa_restart_returns_eintr() {
105        for flags in [0, OTHER_FLAGS] {
106            assert_eq!(
107                caught_signal_return(ERESTARTSYS, flags),
108                CaughtSignalReturn::Returns(EINTR)
109            );
110        }
111    }
112
113    #[test]
114    fn erestartsys_with_sa_restart_restarts() {
115        for flags in [SA_RESTART, OTHER_FLAGS | SA_RESTART] {
116            assert_eq!(
117                caught_signal_return(ERESTARTSYS, flags),
118                CaughtSignalReturn::Restarts
119            );
120        }
121    }
122
123    #[test]
124    fn erestartnointr_restarts_with_or_without_sa_restart() {
125        for flags in [0, SA_RESTART, OTHER_FLAGS] {
126            assert_eq!(
127                caught_signal_return(ERESTARTNOINTR, flags),
128                CaughtSignalReturn::Restarts
129            );
130        }
131    }
132
133    /// Results that are not restart codes, including the neighbours of the
134    /// codes (515 is `ENOIOCTLCMD`, 517 `EPROBE_DEFER`, both kernel-private
135    /// too) and `EINTR` itself, are returned as they are.
136    #[test]
137    fn any_other_result_is_returned_unchanged() {
138        for result in [0, 1, 4096, EINTR, -(libc::EAGAIN as i64), -511, -515, -517] {
139            for flags in [0, SA_RESTART] {
140                assert_eq!(
141                    caught_signal_return(result, flags),
142                    CaughtSignalReturn::Returns(result)
143                );
144            }
145        }
146    }
147}