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#[derive(Debug, Copy, Clone)]
29pub struct Notification {
30 id: u64,
32 pub pid: u32,
34 pub syscall: crate::Sysno,
36 pub args: [u64; 6],
38 fd: RawFd,
40}
41
42pub enum ResponseType {
47 Success(i64),
49 RawError(i32),
51 Error(io::Error),
53}
54
55fn cvt(result: c_int) -> Result<(), io::Error> {
61 match result {
62 0 => Ok(()),
63 _ => Err(io::Error::last_os_error()),
64 }
65}
66
67#[derive(Debug)]
74struct RawNotification(*mut seccomp_notif);
75
76impl Drop for RawNotification {
77 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#[derive(Debug)]
98struct RawResponse(*mut seccomp_notif_resp);
99
100impl Drop for RawResponse {
101 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 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 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 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 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 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 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 pub fn valid(&self) -> bool {
246 cvt(unsafe { seccomp_notify_id_valid(self.fd, self.id) }).is_ok()
247 }
248
249 pub unsafe fn open(&self) -> Result<File, io::Error> {
266 let path = format!("/proc/{}/mem", self.pid);
269 let file = OpenOptions::new().read(true).write(true).open(path)?;
270
271 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#[derive(Debug)]
292struct SeccompFd(RawFd);
293
294impl AsRawFd for SeccompFd {
295 fn as_raw_fd(&self) -> RawFd {
299 self.0.as_raw_fd()
300 }
301}
302
303#[derive(Debug)]
308pub struct NotificationStream {
309 inner: AsyncFd<SeccompFd>,
310}
311
312impl NotificationStream {
313 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 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 pub async fn recv(&self) -> Result<Notification, io::Error> {
353 let guard = self.inner.readable().await?;
355 let result = self.blocking_recv();
356 drop(guard);
357 result
358 }
359
360 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 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 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 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 static SECCOMP_MUTEX: Mutex<()> = Mutex::new(());
461
462 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 reset_global_state().unwrap();
478 let filter = setup().expect("Failed to setup SECCOMP!");
480 let fd = filter
482 .get_notify_fd()
483 .expect("Did not receive fd from seccomp()!");
484
485 fd_tx.send(fd).unwrap();
488
489 let result = func();
491
492 drop(filter);
493
494 result
495 })
496 }
497
498 fn setup() -> Result<ScmpFilterContext, Box<dyn Error>> {
501 let mut filter = ScmpFilterContext::new_filter(ScmpAction::Allow)?;
503 filter.add_arch(ScmpArch::Native)?;
505 let syscall = ScmpSyscall::from_name("uname")?;
507 filter.add_rule(ScmpAction::Notify, syscall)?;
508 filter.load()?;
510
511 Ok(filter)
512 }
513
514 #[tokio::test]
515 async fn test_drop() -> Result<(), io::Error> {
516 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 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 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 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 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 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 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 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}