seccomp_stream/
lib.rs

1use libseccomp_sys::{
2    seccomp_notif, seccomp_notif_resp, seccomp_notify_alloc, seccomp_notify_free,
3    seccomp_notify_id_valid, seccomp_notify_receive, seccomp_notify_respond,
4    SECCOMP_USER_NOTIF_FLAG_CONTINUE,
5};
6use std::fs::{File, OpenOptions};
7use std::io;
8use std::io::ErrorKind;
9use std::os::fd::{AsRawFd, RawFd};
10use std::os::raw::c_int;
11use std::pin::Pin;
12use std::task::{Context, Poll};
13
14use tokio::io::unix::AsyncFd;
15use tokio::io::Interest;
16use tokio_stream::Stream;
17
18pub use syscalls::Sysno;
19
20#[cfg(not(target_os = "linux"))]
21compile_error!("There is little to no point to run this crate on non-Linux systems!");
22
23/// Represents a notification from the seccomp system call.
24///
25/// This struct contains information about a system call that has been intercepted by seccomp.
26/// It includes the system call number (`syscall`), the arguments to the system call (`args`),
27/// and other relevant information such as the process ID (`pid`) and a file descriptor (`fd`).
28#[derive(Debug, Copy, Clone)]
29pub struct Notification {
30    /// The unique identifier for the notification.
31    id: u64,
32    /// The process ID that made the system call.
33    pub pid: u32,
34    /// The system call number.
35    pub syscall: crate::Sysno,
36    /// The arguments to the system call.
37    pub args: [u64; 6],
38    /// A file descriptor associated with the notification.
39    fd: RawFd,
40}
41
42/// Represents the type of response to a seccomp notification.
43///
44/// This enum is used to specify the outcome of handling a seccomp notification.
45/// It can indicate success with a return value, a raw error code, or an `io::Error` for convenience.
46pub enum ResponseType {
47    /// Indicates success with a specific return value.
48    Success(i64),
49    /// Indicates an error that will be written to the targets errno.
50    RawError(i32),
51    /// Indicates an error with an `io::Error`. It will be converted to an integer and written to the targets errno.
52    Error(io::Error),
53}
54
55/// Converts a raw error code to a `Result`.
56///
57/// This function takes a raw error code (as an `c_int`) and converts it to a `Result`.
58/// If the error code is 0, it returns `Ok(())`. Otherwise, it converts the error code
59/// to an `io::Error` and returns `Err(io::Error)`.
60fn cvt(result: c_int) -> Result<(), io::Error> {
61    match result {
62        0 => Ok(()),
63        _ => Err(io::Error::last_os_error()),
64    }
65}
66
67/// Represents a raw seccomp notification.
68///
69/// This struct wraps a raw pointer to a `seccomp_notif` structure.
70/// It is used to manage the memory of the seccomp notification.
71///
72/// You should *probably* not be using this directly
73#[derive(Debug)]
74struct RawNotification(*mut seccomp_notif);
75
76impl Drop for RawNotification {
77    /// Frees the memory associated with the raw seccomp notification.
78    ///
79    /// This method is called when a `RawNotification` is dropped.
80    /// It ensures that the memory allocated for the `seccomp_notif` structure is properly freed.
81    fn drop(&mut self) {
82        let ptr = std::mem::replace(&mut self.0, std::ptr::null_mut());
83        if !ptr.is_null() {
84            unsafe {
85                seccomp_notify_free(ptr, std::ptr::null_mut());
86            }
87        }
88    }
89}
90
91/// Represents a raw seccomp response.
92///
93/// This struct wraps a raw pointer to a `seccomp_notif_resp` structure.
94/// It is used to manage the memory of the seccomp response.
95///
96/// You should *probably* not be using this directly
97#[derive(Debug)]
98struct RawResponse(*mut seccomp_notif_resp);
99
100impl Drop for RawResponse {
101    /// Frees the memory associated with the raw seccomp response.
102    ///
103    /// This method is called when a `RawResponse` is dropped.
104    /// It ensures that the memory allocated for the `seccomp_notif_resp` structure is properly freed.
105    fn drop(&mut self) {
106        let ptr = std::mem::replace(&mut self.0, std::ptr::null_mut());
107        if !ptr.is_null() {
108            unsafe {
109                seccomp_notify_free(std::ptr::null_mut(), ptr);
110            }
111        }
112    }
113}
114
115impl RawResponse {
116    /// Allocates a new raw seccomp response.
117    ///
118    /// This method allocates memory for a `seccomp_notif_resp` structure and returns a `RawResponse`
119    /// that wraps the pointer to this structure.
120    pub fn new() -> Result<Self, io::Error> {
121        let mut response = std::ptr::null_mut();
122
123        cvt(unsafe { seccomp_notify_alloc(std::ptr::null_mut(), &mut response) })?;
124
125        Ok(Self(response))
126    }
127
128    /// Sends a continue response for a seccomp notification.
129    ///
130    /// This method sets the response to continue the execution of the intercepted system call
131    /// and sends the response back to the kernel.
132    ///
133    /// # Safety
134    ///
135    /// This method is unsafe because continuing a syscall is inherently unsafe.
136    /// Please consult the notes on
137    /// [SECCOMP_USER_NOTIF_FLAG_CONTINUE in man seccomp_unotify(2)](https://man7.org/linux/man-pages/man2/seccomp_unotify.2.html#NOTES)
138    pub unsafe fn send_continue(self, fd: RawFd, id: u64) -> Result<(), io::Error> {
139        (*self.0).id = id;
140        (*self.0).val = 0;
141        (*self.0).error = 0;
142
143        (*self.0).flags |= SECCOMP_USER_NOTIF_FLAG_CONTINUE;
144
145        cvt(unsafe { seccomp_notify_respond(fd, self.0) })?;
146        Ok(())
147    }
148
149    /// Sends a response for a seccomp notification.
150    ///
151    /// This method sets the response based on the provided `ResponseType` and sends the response
152    /// back to the kernel.
153    pub fn send(self, fd: RawFd, id: u64, response_type: ResponseType) -> Result<(), io::Error> {
154        unsafe {
155            (*self.0).id = id;
156        }
157
158        match response_type {
159            ResponseType::Success(val) => unsafe {
160                (*self.0).val = val;
161                (*self.0).error = 0;
162            },
163            ResponseType::RawError(err) => unsafe {
164                (*self.0).val = 0;
165                (*self.0).error = -err;
166            },
167            ResponseType::Error(err) => unsafe {
168                (*self.0).val = 0;
169                (*self.0).error = -err.raw_os_error().ok_or_else(|| {
170                    io::Error::new(
171                        ErrorKind::InvalidData,
172                        "Supplied io::Error did not map to an OS error!",
173                    )
174                })?;
175            },
176        }
177
178        cvt(unsafe { seccomp_notify_respond(fd, self.0) })?;
179        Ok(())
180    }
181}
182
183impl RawNotification {
184    /// Allocates a new raw seccomp notification.
185    ///
186    /// This method allocates memory for a `seccomp_notif` structure and returns a `RawNotification`
187    /// that wraps the pointer to this structure.
188    pub fn new() -> Result<Self, io::Error> {
189        let mut notification = std::ptr::null_mut();
190
191        cvt(unsafe { seccomp_notify_alloc(&mut notification, std::ptr::null_mut()) })?;
192
193        Ok(Self(notification))
194    }
195
196    /// Receives a seccomp notification.
197    ///
198    /// This method receives a seccomp notification from the kernel and returns the notification
199    /// as a `seccomp_notif` structure.
200    ///
201    /// # Blocking
202    ///
203    /// This method **will** block unless you've previously received a readable event from
204    /// epoll / select / poll.
205    pub fn recv(self, fd: RawFd) -> Result<seccomp_notif, io::Error> {
206        cvt(unsafe { seccomp_notify_receive(fd, self.0) })?;
207        Ok(unsafe { *self.0 })
208    }
209}
210
211impl Notification {
212    /// Constructs a `Notification` from a raw `seccomp_notif` structure and a file descriptor.
213    ///
214    /// This method takes a `seccomp_notif` structure and a file descriptor (`RawFd`) as input.
215    /// It constructs a `Notification` instance by extracting the relevant fields from the `seccomp_notif`
216    /// structure and the file descriptor.
217    ///
218    /// # Arguments
219    ///
220    /// * `notif` - A raw `seccomp_notif` structure containing the notification data.
221    /// * `fd` - A file descriptor associated with the notification.
222    ///
223    /// # Returns
224    ///
225    /// A `Notification` instance with the extracted data.
226    pub fn from_raw(notif: seccomp_notif, fd: RawFd) -> Self {
227        Self {
228            id: notif.id,
229            pid: notif.pid,
230            syscall: Sysno::from(notif.data.nr),
231            args: notif.data.args,
232            fd: fd.as_raw_fd(),
233        }
234    }
235
236    /// Checks if the notification is valid.
237    ///
238    /// This method checks the validity of the notification by calling `seccomp_notify_id_valid`
239    /// with the file descriptor and the notification ID. It returns `true` if the notification
240    /// is valid, and `false` otherwise.
241    ///
242    /// # Returns
243    ///
244    /// `true` if the notification is valid, `false` otherwise.
245    pub fn valid(&self) -> bool {
246        cvt(unsafe { seccomp_notify_id_valid(self.fd, self.id) }).is_ok()
247    }
248
249    /// Opens the memory file of the process associated with the notification.
250    ///
251    /// This method attempts to open the memory file of the process identified by the notification's
252    /// process ID. It constructs the path to the memory file and attempts to open it with read and
253    /// write permissions. If the notification is not valid (i.e., the process has quit), it returns
254    /// an error indicating that the process has quit.
255    ///
256    /// # Safety
257    ///
258    /// This method is unsafe because opening or reading the memory of a remote process is inherently prone to race conditions.
259    /// While writing to remote memory is possible, it is **never** safe.
260    /// Proceed with caution - here be demons!
261    ///
262    /// # Errors
263    ///
264    /// Returns an `io::Error` if the memory file cannot be opened or if the notification is not valid.
265    pub unsafe fn open(&self) -> Result<File, io::Error> {
266        // Build the path to procfs
267        // TODO: This is not very robust or tested against weird environments
268        let path = format!("/proc/{}/mem", self.pid);
269        let file = OpenOptions::new().read(true).write(true).open(path)?;
270
271        // If our target got killed and the pid re-used we might be non the wiser so
272        // we explicitly re-check here to make sure we actually have the correct file open.
273        // TODO: Add tests for this
274        // This is untested since it'd require killing the process and
275        // we currently run all our tests within the same process
276        if !self.valid() {
277            return Err(io::Error::new(
278                ErrorKind::NotFound,
279                "Process has quit while trying to access its memory!",
280            ));
281        }
282
283        Ok(file)
284    }
285}
286
287/// A wrapper around a file descriptor for seccomp notifications.
288///
289/// This struct is a wrapper around a `RawFd` that represents a file descriptor for seccomp notifications.
290/// It implements the `AsRawFd` trait to allow access to the underlying file descriptor.
291#[derive(Debug)]
292struct SeccompFd(RawFd);
293
294impl AsRawFd for SeccompFd {
295    /// Returns the raw file descriptor.
296    ///
297    /// This method returns the underlying `RawFd` of the `SeccompFd`.
298    fn as_raw_fd(&self) -> RawFd {
299        self.0.as_raw_fd()
300    }
301}
302
303/// A stream of seccomp notifications.
304///
305/// This struct provides an asynchronous stream of seccomp notifications.
306/// It wraps an `AsyncFd` around a `SeccompFd` to enable asynchronous operations on the seccomp file descriptor.
307#[derive(Debug)]
308pub struct NotificationStream {
309    inner: AsyncFd<SeccompFd>,
310}
311
312impl NotificationStream {
313    /// Creates a new `NotificationStream` from a raw file descriptor.
314    ///
315    /// This method initializes a `NotificationStream` with the given raw file descriptor.
316    /// It sets up the `AsyncFd` to be ready for reading seccomp notifications.
317    ///
318    /// # Arguments
319    ///
320    /// * `fd` - The raw file descriptor for seccomp notifications.
321    ///
322    /// # Returns
323    ///
324    /// A `Result` containing a new `NotificationStream` or an `io::Error` if the operation fails.
325    pub fn new(fd: RawFd) -> Result<Self, io::Error> {
326        Ok(Self {
327            inner: AsyncFd::with_interest(SeccompFd(fd), Interest::READABLE | Interest::WRITABLE)?,
328        })
329    }
330
331    /// Receives a seccomp notification.
332    ///
333    /// This method will block until it receives a seccomp notification.
334    /// You can prevent it from blocking by listening for `self.inner` to become readable
335    /// using epoll / poll / select.
336    ///
337    /// # Returns
338    ///
339    /// A `Result` containing a `Notification` or an `io::Error`.
340    pub fn blocking_recv(&self) -> Result<Notification, io::Error> {
341        let raw = RawNotification::new()?.recv(self.inner.as_raw_fd())?;
342        Ok(Notification::from_raw(raw, self.inner.as_raw_fd()))
343    }
344
345    /// Receives a seccomp notification asynchronously.
346    ///
347    /// This method will not block unless other threads are simultaneously listening for notifications.
348    ///
349    /// # Returns
350    ///
351    /// A `Result` containing a `Notification` or an `io::Error`.
352    pub async fn recv(&self) -> Result<Notification, io::Error> {
353        // TODO: This is kinda dangerous when other threads are waiting for a notification.
354        let guard = self.inner.readable().await?;
355        let result = self.blocking_recv();
356        drop(guard);
357        result
358    }
359
360    /// Sends a response to a seccomp notification.
361    ///
362    /// This method sends a response to a seccomp notification based on the provided `ResponseType`.
363    ///
364    /// # Arguments
365    ///
366    /// * `notif` - The notification to which the response is being sent.
367    /// * `response_type` - The type of response to send.
368    ///
369    /// # Returns
370    ///
371    /// A `Result` indicating the success or failure of the operation.
372    pub fn send(&self, notif: Notification, response_type: ResponseType) -> Result<(), io::Error> {
373        let raw = RawResponse::new()?;
374        raw.send(self.inner.as_raw_fd(), notif.id, response_type)?;
375        Ok(())
376    }
377
378    /// Sends a continue response to a seccomp notification.
379    ///
380    /// This method sends a continue response to a seccomp notification.
381    /// It returns a `Result` indicating the success or failure of the operation.
382    ///
383    /// # Arguments
384    ///
385    /// * `notif` - The notification to which the continue response is being sent.
386    ///
387    /// # Returns
388    ///
389    /// A `Result` indicating the success or failure of the operation.
390    ///
391    /// # Safety
392    ///
393    /// This method is unsafe because continuing a syscall is inherently prone to race conditions.
394    /// See `RawResponse::send_continue` for more information.
395    pub unsafe fn send_continue(&self, notif: Notification) -> Result<(), io::Error> {
396        let raw = RawResponse::new()?;
397        raw.send_continue(self.inner.as_raw_fd(), notif.id)?;
398        Ok(())
399    }
400}
401
402impl Stream for NotificationStream {
403    type Item = Notification;
404
405    /// Polls the stream for the next notification.
406    ///
407    /// This method polls the stream to check if a new notification is ready to be received.
408    /// It returns a `Poll` indicating whether a notification is ready, an error occurred, or the operation is pending.
409    ///
410    /// # Arguments
411    ///
412    /// * `self` - A mutable reference to `self`.
413    /// * `cx` - A context for the current task.
414    ///
415    /// # Returns
416    ///
417    /// A `Poll` indicating the state of the next notification.
418    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
419        match self.inner.poll_read_ready(cx) {
420            Poll::Ready(Ok(mut guard)) => {
421                if guard.ready().is_read_closed() {
422                    Poll::Ready(None)
423                } else {
424                    let x = self.blocking_recv().ok();
425                    guard.clear_ready();
426                    Poll::Ready(x)
427                }
428            }
429            // I don't think this ever happens
430            Poll::Ready(Err(_)) => Poll::Ready(None),
431            Poll::Pending => Poll::Pending,
432        }
433    }
434}
435
436#[cfg(test)]
437#[allow(clippy::await_holding_lock)]
438mod tests {
439
440    use super::*;
441    use libseccomp::{
442        reset_global_state, ScmpAction, ScmpArch, ScmpFd, ScmpFilterContext, ScmpSyscall,
443    };
444    use std::error::Error;
445    use std::ffi::CStr;
446    use std::fmt::Debug;
447    use std::io::{Seek, SeekFrom, Write};
448    use std::sync::atomic::{AtomicUsize, Ordering};
449    use std::sync::Mutex;
450    use std::thread;
451
452    use std::time::Duration;
453
454    use std::thread::JoinHandle;
455
456    use tokio::sync::oneshot;
457    use tokio_stream::StreamExt;
458
459    // libseccomp is **NOT** thread-safe, so we'll have to prevent our tests from running simultaneously.
460    static SECCOMP_MUTEX: Mutex<()> = Mutex::new(());
461
462    /// Runs a closure in a new thread with a seccomp() filter applied.
463    /// Currently it will only filter `uname`.
464    ///
465    /// # Arguments
466    ///
467    /// * `fd_tx` - A oneshot sender to transmit the notification fd back to the main thread
468    /// * `func` - The closure that will run in a seccomp'd thread
469    fn run_with_seccomp<F, Output>(fd_tx: oneshot::Sender<ScmpFd>, func: F) -> JoinHandle<Output>
470    where
471        F: FnOnce() -> Output + Send + 'static,
472        Output: Send + Clone + Debug + 'static,
473    {
474        thread::spawn(move || {
475            // Just to be sure, clean the global state.
476            // This doesn't actually call into kernel, just clears a global struct.
477            reset_global_state().unwrap();
478            // Construct and load a filter
479            let filter = setup().expect("Failed to setup SECCOMP!");
480            // Retrieve the fd from libseccomp
481            let fd = filter
482                .get_notify_fd()
483                .expect("Did not receive fd from seccomp()!");
484
485            // Send the fd over to the main thread
486            // This works because all threads within a process share fds
487            fd_tx.send(fd).unwrap();
488
489            // Evaluate the user supplied function
490            let result = func();
491
492            drop(filter);
493
494            result
495        })
496    }
497
498    /// Creates and loads a seccomp() filter that will cause calls to `uname`
499    /// to send notifications to user space.
500    fn setup() -> Result<ScmpFilterContext, Box<dyn Error>> {
501        // Creates a new filter that will allow everything by default
502        let mut filter = ScmpFilterContext::new_filter(ScmpAction::Allow)?;
503        // This will make the filter trigger for the native arch
504        filter.add_arch(ScmpArch::Native)?;
505        // Make the filter notify us for `uname`
506        let syscall = ScmpSyscall::from_name("uname")?;
507        filter.add_rule(ScmpAction::Notify, syscall)?;
508        // Apply the filter to the current thread
509        filter.load()?;
510
511        Ok(filter)
512    }
513
514    #[tokio::test]
515    async fn test_drop() -> Result<(), io::Error> {
516        // This test fails only when it segfaults lol
517        RawResponse(std::ptr::null_mut());
518        RawNotification(std::ptr::null_mut());
519        Ok(())
520    }
521
522    #[tokio::test]
523    async fn test_bad_error() -> Result<(), io::Error> {
524        // Lock so we don't trash the global state of libseccomp with concurrent accesses
525        let guard = SECCOMP_MUTEX.lock().unwrap();
526        let (fd_tx, fd_rx) = oneshot::channel::<ScmpFd>();
527
528        let handle = run_with_seccomp(fd_tx, move || {
529            let mut n = unsafe { std::mem::zeroed() };
530            let r = cvt(unsafe { libc::uname(&mut n) });
531
532            assert!(matches!(r, Ok(())));
533        });
534
535        let fd = fd_rx.await.expect("Did not receive FD!");
536
537        let mut stream =
538            NotificationStream::new(fd).expect("Failed to construct NotificationStream");
539
540        let notification = tokio::time::timeout(Duration::from_secs(5), stream.next())
541            .await
542            .expect("Did not receive a notification in time!")
543            .unwrap();
544
545        assert!(matches!(notification.syscall, Sysno::uname));
546
547        stream
548            .send(
549                notification,
550                ResponseType::Error(io::Error::new(ErrorKind::Other, "Custom Error")),
551            )
552            .expect_err("Expected error!");
553
554        unsafe { stream.send_continue(notification) }.unwrap();
555
556        handle.join().expect("Failed to wait for thread!");
557
558        drop(guard);
559        Ok(())
560    }
561
562    #[tokio::test]
563    async fn test_error() -> Result<(), io::Error> {
564        // Lock so we don't trash the global state of libseccomp with concurrent accesses
565        let guard = SECCOMP_MUTEX.lock().unwrap();
566        let (fd_tx, fd_rx) = oneshot::channel::<ScmpFd>();
567
568        let handle = run_with_seccomp(fd_tx, move || {
569            let mut n = unsafe { std::mem::zeroed() };
570            let r = cvt(unsafe { libc::uname(&mut n) });
571            assert!(matches!(r, Err(e) if e.kind() == ErrorKind::Unsupported));
572        });
573
574        let fd = fd_rx.await.expect("Did not receive FD!");
575
576        let mut stream =
577            NotificationStream::new(fd).expect("Failed to construct NotificationStream");
578
579        let notification = tokio::time::timeout(Duration::from_secs(5), stream.next())
580            .await
581            .expect("Did not receive a notification in time!")
582            .unwrap();
583
584        assert!(matches!(notification.syscall, Sysno::uname));
585
586        stream
587            .send(
588                notification,
589                ResponseType::Error(io::Error::from_raw_os_error(libc::ENOSYS)),
590            )
591            .expect("Failed to send response");
592
593        handle.join().expect("Failed to wait for thread!");
594
595        drop(guard);
596        Ok(())
597    }
598
599    #[tokio::test]
600    async fn test_raw_error() -> Result<(), io::Error> {
601        // Lock so we don't trash the global state of libseccomp with concurrent accesses
602        let guard = SECCOMP_MUTEX.lock().unwrap();
603        let (fd_tx, fd_rx) = oneshot::channel::<ScmpFd>();
604
605        let handle = run_with_seccomp(fd_tx, move || {
606            let mut n = unsafe { std::mem::zeroed() };
607            let r = cvt(unsafe { libc::uname(&mut n) });
608            assert!(matches!(r, Err(e) if e.kind() == ErrorKind::Unsupported));
609        });
610
611        let fd = fd_rx.await.expect("Did not receive FD!");
612
613        let mut stream =
614            NotificationStream::new(fd).expect("Failed to construct NotificationStream");
615
616        let notification = tokio::time::timeout(Duration::from_secs(5), stream.next())
617            .await
618            .expect("Did not receive a notification in time!")
619            .unwrap();
620
621        assert!(matches!(notification.syscall, Sysno::uname));
622
623        stream
624            .send(notification, ResponseType::RawError(libc::ENOSYS))
625            .expect("Failed to send response");
626
627        handle.join().expect("Failed to wait for thread!");
628
629        drop(guard);
630        Ok(())
631    }
632
633    #[tokio::test]
634    async fn test_continue() -> Result<(), io::Error> {
635        // Lock so we don't trash the global state of libseccomp with concurrent accesses
636        let guard = SECCOMP_MUTEX.lock().unwrap();
637        let (fd_tx, fd_rx) = oneshot::channel::<ScmpFd>();
638
639        let handle = run_with_seccomp(fd_tx, move || {
640            let mut n = unsafe { std::mem::zeroed() };
641            let r = unsafe { libc::uname(&mut n) };
642            assert_eq!(r, 0);
643            unsafe { CStr::from_ptr(&n.sysname[0]) }
644                .to_str()
645                .expect("Invalid UTF-8 reply!")
646                .to_owned()
647        });
648
649        let fd = fd_rx.await.expect("Did not receive FD!");
650
651        let mut stream =
652            NotificationStream::new(fd).expect("Failed to construct NotificationStream");
653
654        let notification = tokio::time::timeout(Duration::from_secs(5), stream.next())
655            .await
656            .expect("Did not receive a notification in time!")
657            .unwrap();
658
659        assert!(matches!(notification.syscall, Sysno::uname));
660
661        unsafe { stream.send_continue(notification) }.expect("Failed to send response");
662
663        let sysname = handle.join().expect("Failed to wait for thread!");
664        assert_eq!(sysname, "Linux");
665        drop(guard);
666        Ok(())
667    }
668
669    #[tokio::test]
670    async fn test_continue_recv() -> Result<(), io::Error> {
671        // Lock so we don't trash the global state of libseccomp with concurrent accesses
672        let guard = SECCOMP_MUTEX.lock().unwrap();
673        let (fd_tx, fd_rx) = oneshot::channel::<ScmpFd>();
674
675        let handle = run_with_seccomp(fd_tx, move || {
676            let mut n = unsafe { std::mem::zeroed() };
677            let r = unsafe { libc::uname(&mut n) };
678            assert_eq!(r, 0);
679            unsafe { CStr::from_ptr(&n.sysname[0]) }
680                .to_str()
681                .expect("Invalid UTF-8 reply!")
682                .to_owned()
683        });
684
685        let fd = fd_rx.await.expect("Did not receive FD!");
686
687        let stream = NotificationStream::new(fd).expect("Failed to construct NotificationStream");
688
689        let notification = tokio::time::timeout(Duration::from_secs(5), stream.recv())
690            .await
691            .expect("Did not receive a notification in time!")
692            .unwrap();
693
694        assert!(matches!(notification.syscall, Sysno::uname));
695
696        unsafe { stream.send_continue(notification) }.expect("Failed to send response");
697
698        let sysname = handle.join().expect("Failed to wait for thread!");
699        assert_eq!(sysname, "Linux");
700        drop(guard);
701        Ok(())
702    }
703
704    #[tokio::test]
705    async fn test_intercept() -> Result<(), io::Error> {
706        // Lock so we don't trash the global state of libseccomp with concurrent accesses
707        let guard = SECCOMP_MUTEX.lock().unwrap();
708        let (fd_tx, fd_rx) = oneshot::channel::<ScmpFd>();
709
710        let handle = run_with_seccomp(fd_tx, move || {
711            let mut n = unsafe { std::mem::zeroed() };
712            let r = unsafe { libc::uname(&mut n) };
713            assert_eq!(r, 0);
714            unsafe { CStr::from_ptr(&n.sysname[0]) }
715                .to_str()
716                .expect("Invalid UTF-8 reply!")
717                .to_owned()
718        });
719
720        let fd = fd_rx.await.expect("Did not receive FD!");
721
722        let mut stream =
723            NotificationStream::new(fd).expect("Failed to construct NotificationStream");
724
725        let notification = tokio::time::timeout(Duration::from_secs(5), stream.next())
726            .await
727            .expect("Did not receive a notification in time!")
728            .unwrap();
729
730        assert!(matches!(notification.syscall, Sysno::uname));
731
732        let mut file = unsafe { notification.open() }.expect("Failed to open memory!");
733        file.seek(SeekFrom::Start(notification.args[0]))
734            .expect("Failed to seek!");
735        file.write_all(b"seccomp")
736            .expect("Failed to write spoofed reply!");
737
738        stream
739            .send(notification, ResponseType::Success(0))
740            .expect("Failed to send response");
741
742        let sysname = handle.join().expect("Failed to wait for thread!");
743        assert_eq!(sysname, "seccomp");
744        drop(guard);
745        Ok(())
746    }
747
748    #[tokio::test]
749    async fn test_parallel() -> Result<(), io::Error> {
750        // Lock so we don't trash the global state of libseccomp with concurrent accesses
751        let guard = SECCOMP_MUTEX.lock().unwrap();
752        let (fd_tx, fd_rx) = oneshot::channel::<ScmpFd>();
753
754        let handle = run_with_seccomp(fd_tx, move || {
755            let first = std::thread::spawn(move || {
756                for _ in 0..20 {
757                    let mut n = unsafe { std::mem::zeroed() };
758                    let r = unsafe { libc::uname(&mut n) };
759                    assert_eq!(r, 0);
760                }
761            });
762
763            let second = std::thread::spawn(move || {
764                for _ in 0..20 {
765                    let mut n = unsafe { std::mem::zeroed() };
766                    let r = unsafe { libc::uname(&mut n) };
767                    assert_eq!(r, 0);
768                }
769            });
770
771            first.join().unwrap();
772            second.join().unwrap();
773        });
774
775        let fd = fd_rx.await.expect("Did not receive FD!");
776
777        let mut stream =
778            NotificationStream::new(fd).expect("Failed to construct NotificationStream");
779
780        let counter = AtomicUsize::new(0);
781
782        while let Some(notification) = stream.next().await {
783            counter.fetch_add(1, Ordering::Relaxed);
784            unsafe { stream.send_continue(notification) }.unwrap();
785        }
786
787        assert_eq!(counter.into_inner(), 40);
788        handle.join().unwrap();
789        drop(guard);
790        Ok(())
791    }
792}