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