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 {}