seccomp/
notify.rs

1#![cfg(feature = "notify")]
2//! A wrapper for the SECCOMP Notify interface.
3//!
4//! ## Implementation
5//! This implementation does not make any assumptions about how you get the
6//! SECCOMP FD. However, there are some considerations you should take into account:
7//! * SECCOMP applies across threads. If you place the monitor in a separate thread,
8//!   making sending the FD easy, the monitor will be confined by the policy it's monitoring.
9//!   This can cause deadlock where the monitor uses a syscall, which the kernel sends an
10//!   event for, to which the monitor cannot handle because its currently waiting for itself
11//!   to request it.
12//! * FDs can be passed across a socket, but you cannot get the SECCOMP FD until you have
13//!   loaded the filter. This means you need to ensure that the syscalls used to send the
14//!   FD (`connect`, `sendmsg`, etc) are not sent to the notifier, who does not have
15//!   the SECCOMP FD yet. `fd_socket` provides functions to send and receive a FD between
16//!   processes. See `antimony-monitor`, and Antimony as a whole, to see how you can
17//!   notify safely (Hint: Notify all Syscalls except those needed to send FD, which are
18//!   instead logged on Audit, with a separate thread for reading the log).
19
20use crate::{action::Action, raw, syscall::Syscall};
21use nix::errno::Errno;
22use std::{
23    error, fmt,
24    os::fd::{AsRawFd, OwnedFd, RawFd},
25    ptr::{self, null_mut},
26};
27
28/// Errors regarding to Notify.
29#[derive(Debug)]
30pub enum Error {
31    /// If the pair cannot be allocated.
32    Allocation(Errno),
33
34    /// If a request has become invalid
35    InvalidId,
36
37    /// If there was an error receiving a request from the kernel.
38    Receive(Errno),
39
40    /// If there was an error sending a response to a request.
41    Respond(Errno),
42}
43impl error::Error for Error {
44    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
45        match self {
46            Self::Allocation(errno) => Some(errno),
47            Self::Receive(errno) => Some(errno),
48            _ => None,
49        }
50    }
51}
52impl fmt::Display for Error {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Self::Allocation(errno) => write!(f, "Failed to allocate notification pair: {errno}"),
56            Self::InvalidId => write!(f, "Received ID is no longer valid"),
57            Self::Receive(errno) => write!(f, "Failed to receive event: {errno}"),
58            Self::Respond(errno) => write!(f, "Failed to respond to event: {errno}"),
59        }
60    }
61}
62
63/// A trait for transmitting a SECCOMP Notify FD to a Monitor.
64///
65/// Executors, such as `spawn`, should perform the following actions
66/// from the Filter.
67///
68/// 1. Call `Notifier::exempt()`
69/// 2. Call `Notifier::prepare()`
70/// 3. Call `seccomp_load()`
71/// 4. Call `Notifier::handle()`
72///
73/// Then, call `execve()`.
74/// See Antimony for a socket implementation.
75pub trait Notifier: Send + 'static {
76    /// Return the list of syscalls that are used by the Notifier itself
77    /// in order to transmit the SECCOMP FD. These syscalls will be used
78    /// between `seccomp_load()` and `execve()`. For example, if sending
79    /// the FD across a socket, you should pass `sendmsg`.
80    ///
81    /// The action should NOT be Notify, as that will cause a deadlock.
82    /// Instead, either Allow, or Log.
83    fn exempt(&self) -> Vec<(Action, Syscall)> {
84        Vec::new()
85    }
86
87    /// Prepare for `seccomp_load`. This function is the last thing run
88    /// before `seccomp_load`, and as such is the last time you will
89    /// not be confined by the Filter. This can be used, for example,
90    /// to wait for a socket, then connect to it.
91    fn prepare(&mut self) -> Result<(), String> {
92        Ok(())
93    }
94
95    /// Handle the SECCOMP FD. This function runs under the confined
96    /// SECCOMP Filter, and should transmit the OwnedFD to the
97    /// Notify Monitor. The more you do here, the more syscalls
98    /// you will need; consider moving as much as possible to
99    /// `prepare()`
100    fn handle(&mut self, fd: OwnedFd);
101}
102
103/// A Notification Pair.
104///
105/// ## Examples
106///
107/// ```rust,ignore
108/// let pair = seccomp::notify::Pair::new().unwrap();
109/// loop {
110///     match pair.recv(raw) {
111///         Ok(Some(_)) => pair.reply(raw, |req, resp| {
112///             resp.val = 0;
113///
114///             // Deny syscall 1.
115///             resp.error = if req.data.nr == 1 {
116///                 EPERM
117///             } else {
118///                 0
119///             };
120///
121///             // Allow everything else.
122///             resp.flags = 1;
123///         }).unwrap(),
124///         Ok(None) => continue,
125///         Err(_) => break
126///     }
127/// }
128/// ```
129pub struct Pair {
130    /// The structure filled by the kernel on new events.
131    req: *mut raw::seccomp_notif,
132
133    /// The constructed response to send back.
134    resp: *mut raw::seccomp_notif_resp,
135}
136impl Pair {
137    /// Construct a new Pair.
138    pub fn new() -> Result<Self, Error> {
139        let (req, resp) = unsafe {
140            let mut req: *mut raw::seccomp_notif = null_mut();
141            let mut resp: *mut raw::seccomp_notif_resp = null_mut();
142            match raw::seccomp_notify_alloc(&mut req, &mut resp) {
143                0 => (req, resp),
144                e => return Err(Error::Allocation(Errno::from_raw(e))),
145            }
146        };
147        Ok(Self { req, resp })
148    }
149
150    /// Receive a new event.
151    /// This function fails if the kernel returns an error.
152    pub fn recv(&self, fd: RawFd) -> Result<Option<()>, Error> {
153        // We need to wipe the structure each time.
154        unsafe {
155            ptr::write_bytes(self.req, 0, 1);
156        }
157        // Call seccomp_notify_receive
158        let ret = unsafe { raw::seccomp_notify_receive(fd, self.req) };
159        if ret < 0 {
160            match Errno::last() {
161                Errno::EINTR | Errno::EAGAIN | Errno::ENOENT => Ok(None),
162                err => Err(Error::Receive(err)),
163            }
164        } else {
165            Ok(Some(()))
166        }
167    }
168
169    /// Reply to the last event.
170    ///
171    /// ## Handle
172    /// Handle offloads the actual decision of the request to your application.
173    /// It takes a constant reference to the event from the kernel, and a mutable
174    /// reference to the response. Parse the former to populate the latter, and
175    /// the Pair will send the response over.
176    ///
177    /// The request will always be valid, and the ID will already be set.
178    ///
179    pub fn reply<F>(&self, fd: RawFd, handle: F) -> Result<(), Error>
180    where
181        F: Fn(&raw::seccomp_notif, &mut raw::seccomp_notif_resp),
182    {
183        let (req, resp) = unsafe { (&*self.req, &mut *self.resp) };
184
185        // Ensure the request is still valid.
186        let valid = unsafe { raw::seccomp_notify_id_valid(fd, req.id) };
187        if valid != 0 {
188            return Ok(());
189        }
190
191        // Set the ID.
192        resp.id = req.id;
193
194        // Delegate the decision to the closure.
195        handle(req, resp);
196
197        // Send response
198        let respond_ret = unsafe { raw::seccomp_notify_respond(fd.as_raw_fd(), self.resp) };
199        if respond_ret < 0 {
200            let errno = Errno::from_raw(-respond_ret);
201            match errno {
202                Errno::ECANCELED => Ok(()),
203                e => Err(Error::Respond(e)),
204            }
205        } else {
206            Ok(())
207        }
208    }
209}
210impl Drop for Pair {
211    fn drop(&mut self) {
212        unsafe { raw::seccomp_notify_free(self.req, self.resp) }
213    }
214}
215// The Notify API is Thread Safe, and we're moving the Pair anyways.
216unsafe impl Send for Pair {}