shadow_rs/host/syscall/
types.rs

1//! Types used in emulating syscalls.
2
3use std::marker::PhantomData;
4use std::mem::size_of;
5
6use linux_api::errno::Errno;
7use log::Level::Debug;
8use log::*;
9use shadow_shim_helper_rs::emulated_time::EmulatedTime;
10use shadow_shim_helper_rs::syscall_types::{ForeignPtr, SyscallReg};
11
12use crate::cshadow as c;
13use crate::host::descriptor::{File, FileState};
14use crate::host::syscall::Trigger;
15use crate::host::syscall::condition::SyscallCondition;
16
17/// Wrapper around a [`ForeignPtr`] that encapsulates its size and current position.
18#[derive(Copy, Clone)]
19pub struct ForeignArrayPtr<T> {
20    base: ForeignPtr<T>,
21    count: usize,
22    _phantom: std::marker::PhantomData<T>,
23}
24
25impl<T> std::fmt::Debug for ForeignArrayPtr<T> {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        f.debug_struct("ForeignArrayPtr")
28            .field("base", &self.base)
29            .field("count", &self.count)
30            .field("size_of::<T>", &size_of::<T>())
31            .finish()
32    }
33}
34
35impl<T> ForeignArrayPtr<T> {
36    /// Creates a typed pointer. Note though that the pointer *isn't* guaranteed
37    /// to be aligned for `T`.
38    pub fn new(ptr: ForeignPtr<T>, count: usize) -> Self {
39        if log_enabled!(Debug) && usize::from(ptr) % std::mem::align_of::<T>() != 0 {
40            // Linux allows unaligned pointers from user-space, being careful to
41            // avoid unaligned accesses that aren's supported by the CPU.
42            // https://www.kernel.org/doc/html/latest/core-api/unaligned-memory-access.html.
43            //
44            // We do the same (e.g. by avoiding direct dereference of such
45            // pointers even if mmap'd into shadow), but some bugs may slip
46            // through (e.g. by asking for a u8 pointer from the mapping code,
47            // but then casting it to some other type). Here we leave a debug
48            // message here as a sign-post that this could be the root cause of
49            // weirdness that happens afterwards.
50            debug!(
51                "Creating unaligned pointer {ptr:?}. This is legal, but could trigger latent bugs."
52            );
53        }
54        ForeignArrayPtr {
55            base: ptr,
56            count,
57            _phantom: PhantomData,
58        }
59    }
60
61    /// Raw foreign pointer.
62    pub fn ptr(&self) -> ForeignPtr<T> {
63        self.base
64    }
65
66    /// Number of items pointed to.
67    pub fn len(&self) -> usize {
68        self.count
69    }
70
71    pub fn is_empty(&self) -> bool {
72        self.count == 0
73    }
74
75    pub fn is_null(&self) -> bool {
76        self.base.is_null()
77    }
78
79    /// Cast to type `U`. Fails if the total size isn't a multiple of `sizeof<U>`.
80    pub fn cast<U>(&self) -> Option<ForeignArrayPtr<U>> {
81        let count_bytes = self.count * size_of::<T>();
82        if count_bytes % size_of::<U>() != 0 {
83            return None;
84        }
85        Some(ForeignArrayPtr::new(
86            self.base.cast::<U>(),
87            count_bytes / size_of::<U>(),
88        ))
89    }
90
91    /// Cast to u8. Infallible since `size_of<u8>` is 1.
92    pub fn cast_u8(&self) -> ForeignArrayPtr<u8> {
93        self.cast::<u8>().unwrap()
94    }
95
96    /// Return a slice of this pointer.
97    pub fn slice<R: std::ops::RangeBounds<usize>>(&self, range: R) -> ForeignArrayPtr<T> {
98        use std::ops::Bound;
99        let excluded_end = match range.end_bound() {
100            Bound::Included(e) => e + 1,
101            Bound::Excluded(e) => *e,
102            Bound::Unbounded => self.count,
103        };
104        let included_start = match range.start_bound() {
105            Bound::Included(s) => *s,
106            Bound::Excluded(s) => s + 1,
107            Bound::Unbounded => 0,
108        };
109        assert!(included_start <= excluded_end);
110        assert!(excluded_end <= self.count);
111        // `<=` rather than `<`, to allow empty slice at end of ptr.
112        // e.g. `assert_eq!(&[1,2,3][3..3], &[])` passes.
113        assert!(included_start <= self.count);
114
115        ForeignArrayPtr {
116            base: self.base.add(included_start),
117            count: excluded_end - included_start,
118            _phantom: PhantomData,
119        }
120    }
121}
122
123// Calling all of these errors is stretching the semantics of 'error' a bit,
124// but it makes for fluent programming in syscall handlers using the `?` operator.
125#[derive(Debug, PartialEq, Eq)]
126pub enum SyscallError {
127    Failed(Failed),
128    Blocked(Blocked),
129    Native,
130}
131
132#[derive(Debug, PartialEq, Eq)]
133pub struct Blocked {
134    pub condition: SyscallCondition,
135    pub restartable: bool,
136}
137
138#[derive(Debug, PartialEq, Eq)]
139pub struct Failed {
140    pub errno: linux_api::errno::Errno,
141    pub restartable: bool,
142}
143
144pub type SyscallResult = Result<SyscallReg, SyscallError>;
145
146impl From<SyscallReturn> for SyscallResult {
147    fn from(r: SyscallReturn) -> Self {
148        match r {
149            SyscallReturn::Done(done) => {
150                match crate::utility::syscall::raw_return_value_to_result(i64::from(done.retval)) {
151                    Ok(r) => Ok(r),
152                    Err(e) => Err(SyscallError::Failed(Failed {
153                        errno: e,
154                        restartable: done.restartable,
155                    })),
156                }
157            }
158            // SAFETY: XXX: We're assuming this points to a valid SysCallCondition.
159            SyscallReturn::Block(blocked) => Err(SyscallError::Blocked(Blocked {
160                condition: unsafe { SyscallCondition::consume_from_c(blocked.cond) },
161                restartable: blocked.restartable,
162            })),
163            SyscallReturn::Native => Err(SyscallError::Native),
164        }
165    }
166}
167
168impl From<SyscallResult> for SyscallReturn {
169    fn from(syscall_return: SyscallResult) -> Self {
170        match syscall_return {
171            Ok(r) => SyscallReturn::Done(SyscallReturnDone {
172                retval: r,
173                // N/A for non-error result (and non-EINTR result in particular)
174                restartable: false,
175            }),
176            Err(SyscallError::Failed(failed)) => SyscallReturn::Done(SyscallReturnDone {
177                retval: (-(i64::from(failed.errno))).into(),
178                restartable: failed.restartable,
179            }),
180            Err(SyscallError::Blocked(blocked)) => SyscallReturn::Block(SyscallReturnBlocked {
181                cond: blocked.condition.into_inner(),
182                restartable: blocked.restartable,
183            }),
184            Err(SyscallError::Native) => SyscallReturn::Native,
185        }
186    }
187}
188
189impl From<linux_api::errno::Errno> for SyscallError {
190    fn from(e: linux_api::errno::Errno) -> Self {
191        SyscallError::Failed(Failed {
192            errno: e,
193            restartable: false,
194        })
195    }
196}
197
198impl From<std::io::Error> for SyscallError {
199    fn from(e: std::io::Error) -> Self {
200        match std::io::Error::raw_os_error(&e) {
201            Some(e) => SyscallError::Failed(Failed {
202                // this probably won't panic if rust's io::Error does only return "raw os" error
203                // values
204                errno: Errno::try_from(u16::try_from(e).unwrap()).unwrap(),
205                restartable: false,
206            }),
207            None => {
208                let default = Errno::ENOTSUP;
209                warn!("Mapping error {} to {}", e, default);
210                SyscallError::from(default)
211            }
212        }
213    }
214}
215
216impl SyscallError {
217    pub fn new_blocked_on_file(file: File, state: FileState, restartable: bool) -> Self {
218        Self::Blocked(Blocked {
219            condition: SyscallCondition::new(Trigger::from_file(file, state)),
220            restartable,
221        })
222    }
223
224    pub fn new_blocked_on_child(restartable: bool) -> Self {
225        Self::Blocked(Blocked {
226            condition: SyscallCondition::new(Trigger::child()),
227            restartable,
228        })
229    }
230
231    pub fn new_blocked_until(unblock_time: EmulatedTime, restartable: bool) -> Self {
232        Self::Blocked(Blocked {
233            condition: SyscallCondition::new_from_wakeup_time(unblock_time),
234            restartable,
235        })
236    }
237
238    pub fn new_interrupted(restartable: bool) -> Self {
239        Self::Failed(Failed {
240            errno: Errno::EINTR,
241            restartable,
242        })
243    }
244
245    /// Returns the [condition](SyscallCondition) that the syscall is blocked on.
246    pub fn blocked_condition(&mut self) -> Option<&mut SyscallCondition> {
247        if let Self::Blocked(Blocked { condition, .. }) = self {
248            Some(condition)
249        } else {
250            None
251        }
252    }
253}
254
255#[derive(Copy, Clone, Debug)]
256#[repr(C)]
257pub struct SyscallReturnDone {
258    pub retval: SyscallReg,
259    // Only meaningful when `retval` is -EINTR.
260    //
261    // Whether the interrupted syscall is restartable.
262    pub restartable: bool,
263}
264
265#[derive(Copy, Clone, Debug)]
266#[repr(C)]
267pub struct SyscallReturnBlocked {
268    pub cond: *mut c::SysCallCondition,
269    // True if the syscall is restartable in the case that it was interrupted by
270    // a signal. e.g. if the syscall was a `read` operation on a socket without
271    // a configured timeout. See socket(7).
272    pub restartable: bool,
273}
274
275#[derive(Copy, Clone, Debug)]
276#[repr(i8, C)]
277pub enum SyscallReturn {
278    /// Done executing the syscall; ready to let the plugin thread resume.
279    Done(SyscallReturnDone),
280    /// We don't have the result yet.
281    Block(SyscallReturnBlocked),
282    /// Direct plugin to make the syscall natively.
283    Native,
284}
285
286mod export {
287    use shadow_shim_helper_rs::syscall_types::UntypedForeignPtr;
288
289    use super::*;
290
291    #[unsafe(no_mangle)]
292    pub unsafe extern "C-unwind" fn syscallreturn_makeDone(retval: SyscallReg) -> SyscallReturn {
293        SyscallReturn::Done(SyscallReturnDone {
294            retval,
295            restartable: false,
296        })
297    }
298
299    #[unsafe(no_mangle)]
300    pub unsafe extern "C-unwind" fn syscallreturn_makeDoneI64(retval: i64) -> SyscallReturn {
301        SyscallReturn::Done(SyscallReturnDone {
302            retval: retval.into(),
303            restartable: false,
304        })
305    }
306
307    #[unsafe(no_mangle)]
308    pub unsafe extern "C-unwind" fn syscallreturn_makeDoneU64(retval: u64) -> SyscallReturn {
309        SyscallReturn::Done(SyscallReturnDone {
310            retval: retval.into(),
311            restartable: false,
312        })
313    }
314
315    #[unsafe(no_mangle)]
316    pub unsafe extern "C-unwind" fn syscallreturn_makeDonePtr(
317        retval: UntypedForeignPtr,
318    ) -> SyscallReturn {
319        SyscallReturn::Done(SyscallReturnDone {
320            retval: retval.into(),
321            restartable: false,
322        })
323    }
324
325    #[unsafe(no_mangle)]
326    pub unsafe extern "C-unwind" fn syscallreturn_makeDoneErrno(err: i32) -> SyscallReturn {
327        debug_assert!(err > 0);
328        // Should use `syscallreturn_makeInterrupted` instead
329        debug_assert!(err != libc::EINTR);
330        SyscallReturn::Done(SyscallReturnDone {
331            retval: (-err).into(),
332            restartable: false,
333        })
334    }
335
336    #[unsafe(no_mangle)]
337    pub unsafe extern "C-unwind" fn syscallreturn_makeInterrupted(
338        restartable: bool,
339    ) -> SyscallReturn {
340        SyscallReturn::Done(SyscallReturnDone {
341            retval: (-libc::EINTR).into(),
342            restartable,
343        })
344    }
345
346    #[unsafe(no_mangle)]
347    pub unsafe extern "C-unwind" fn syscallreturn_makeBlocked(
348        cond: *mut c::SysCallCondition,
349        restartable: bool,
350    ) -> SyscallReturn {
351        SyscallReturn::Block(SyscallReturnBlocked { cond, restartable })
352    }
353
354    #[unsafe(no_mangle)]
355    pub unsafe extern "C-unwind" fn syscallreturn_makeNative() -> SyscallReturn {
356        SyscallReturn::Native
357    }
358
359    #[unsafe(no_mangle)]
360    pub unsafe extern "C-unwind" fn syscallreturn_blocked(
361        scr: *mut SyscallReturn,
362    ) -> *mut SyscallReturnBlocked {
363        let scr = unsafe { scr.as_mut().unwrap() };
364        let SyscallReturn::Block(b) = scr else {
365            panic!("Unexpected scr {:?}", scr);
366        };
367        b
368    }
369
370    #[unsafe(no_mangle)]
371    pub unsafe extern "C-unwind" fn syscallreturn_done(
372        scr: *mut SyscallReturn,
373    ) -> *mut SyscallReturnDone {
374        let scr = unsafe { scr.as_mut().unwrap() };
375        let SyscallReturn::Done(d) = scr else {
376            panic!("Unexpected scr {:?}", scr);
377        };
378        d
379    }
380}