syd/libseccomp/
notify.rs

1// SPDX-License-Identifier: Apache-2.0 or MIT
2//
3// Copyright 2021 Sony Group Corporation
4//
5
6use std::{ops::BitOr, os::unix::io::RawFd};
7
8use bitflags::bitflags;
9
10use super::cvt;
11use crate::{
12    libseccomp::{
13        api::ensure_supported_api,
14        error::{Result, SeccompError},
15        ScmpArch, ScmpFilterContext, ScmpSyscall, ScmpVersion,
16    },
17    libseccomp_sys::*,
18};
19
20fn get_errno() -> i32 {
21    std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
22}
23
24/// Checks the libseccomp API level and the libseccomp version for
25/// the userspace notification.
26///
27/// This function succeeds if the libseccomp API level and the libseccomp
28/// version being used are equal to or greater than 6 and 2.5.0.
29///
30/// # Errors
31///
32/// If both the libseccomp API level and the libseccomp version being used are
33/// less than 6 and 2.5.0, an error will be returned.
34fn notify_supported() -> Result<()> {
35    ensure_supported_api("seccomp notification", 6, ScmpVersion::from((2, 5, 0)))?;
36
37    Ok(())
38}
39
40/// Represents a file descriptor used for the userspace notification.
41pub type ScmpFd = RawFd;
42
43bitflags! {
44    /// Userspace notification response flags
45    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46    pub struct ScmpNotifRespFlags: u32 {
47        /// Tells the kernel to continue executing the system call that triggered the
48        /// notification. Must only be used when the notification response's error and value is 0.
49        const CONTINUE = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
50    }
51}
52
53/// Userspace notification response flag
54///
55/// Tells the kernel to continue executing the system call that triggered the
56/// notification. Must only be used when the notification response's error is 0.
57#[deprecated(
58    since = "0.3.0",
59    note = "Use ScmpNotifRespFlags::CONTINUE or ScmpNotifRespFlags::CONTINUE.bits()"
60)]
61pub const NOTIF_FLAG_CONTINUE: u32 = ScmpNotifRespFlags::CONTINUE.bits();
62
63impl ScmpFilterContext {
64    /// Gets a file descriptor for the userspace notification associated with the
65    /// given filter context.
66    ///
67    /// Such a file descriptor is only valid after the filter has been loaded
68    /// and only when the filter uses the [`crate::ScmpAction::Notify`] action.
69    /// The file descriptor can be used to retrieve and respond to notifications
70    /// associated with the filter (see [`ScmpNotifReq::receive()`],
71    /// [`ScmpNotifResp::respond()`], and [`notify_id_valid()`]).
72    ///
73    /// **Note**: This file descriptor is shared between all threads.
74    ///
75    /// This function returns a raw file descriptor for the userspace notification.
76    /// The ownership of this file descriptor is not moved and you must not close it.
77    ///
78    /// This function corresponds to
79    /// [`seccomp_notify_fd`](https://man7.org/linux/man-pages/man3/seccomp_notify_fd.3.html).
80    ///
81    /// # Errors
82    ///
83    /// If an issue is encountered getting the file descriptor,
84    /// an error will be returned.
85    /// # Examples
86    ///
87    /// ```
88    /// # use syd::libseccomp::*;
89    /// let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
90    /// ctx.add_rule(ScmpAction::Notify, ScmpSyscall::from_name("getpid")?)?;
91    /// ctx.load()?;
92    /// let fd = ctx.get_notify_fd()?;
93    /// # Ok::<(), Box<dyn std::error::Error>>(())
94    /// ```
95    pub fn get_notify_fd(&self) -> Result<ScmpFd> {
96        notify_supported()?;
97
98        let ret = unsafe { seccomp_notify_fd(self.as_ptr()) };
99        if ret < 0 {
100            return Err(SeccompError::from_errno(ret));
101        }
102
103        Ok(ret)
104    }
105}
106
107/// Describes the system call context that triggered a notification.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109#[non_exhaustive]
110pub struct ScmpNotifData {
111    /// The syscall number
112    pub syscall: ScmpSyscall,
113    /// The filter architecture
114    pub arch: ScmpArch,
115    /// Address of the instruction that triggered a notification
116    pub instr_pointer: u64,
117    /// Arguments (up to 6) for the syscall
118    pub args: [u64; 6],
119}
120
121impl ScmpNotifData {
122    fn from_sys(data: seccomp_data) -> Result<Self> {
123        Ok(Self {
124            syscall: ScmpSyscall::from(data.nr),
125            arch: ScmpArch::from_sys(data.arch)?,
126            instr_pointer: data.instruction_pointer,
127            args: data.args,
128        })
129    }
130}
131
132/// Represents a userspace notification request.
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
134#[non_exhaustive]
135pub struct ScmpNotifReq {
136    /// Notification ID
137    pub id: u64,
138    /// Process that triggered the notification event
139    pub pid: u32,
140    /// Filter flags (see seccomp(2))
141    pub flags: u32,
142    /// System call context that triggered the notification
143    pub data: ScmpNotifData,
144}
145
146impl ScmpNotifReq {
147    fn from_sys(req: seccomp_notif) -> Result<Self> {
148        Ok(Self {
149            id: req.id,
150            pid: req.pid,
151            flags: req.flags,
152            data: ScmpNotifData::from_sys(req.data)?,
153        })
154    }
155
156    /// Retrieves a userspace notification from a filter whose
157    /// [`crate::ScmpAction::Notify`] action has triggered.
158    ///
159    /// The caller is expected to process the notification and return a
160    /// response via [`ScmpNotifResp::respond()`]. Each invocation of
161    /// this function returns one notification.
162    /// As multiple notifications may be pending at any time, this function is
163    /// normally called within a polling loop.
164    ///
165    /// This function returns a userspace notification request.
166    ///
167    /// This function corresponds to
168    /// [`seccomp_notify_receive`](https://man7.org/linux/man-pages/man3/seccomp_notify_receive.3.html).
169    ///
170    /// # Arguments
171    ///
172    /// * `fd` - A file descriptor for the userspace notification
173    ///
174    /// # Errors
175    ///
176    /// If an issue is encountered getting a notification request,
177    /// an error will be returned.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// # use syd::libseccomp::*;
183    /// # use std::process::exit;
184    /// // Get the current process ID
185    /// let parent_pid = std::process::id();
186    /// let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
187    /// ctx.add_rule(ScmpAction::Notify, ScmpSyscall::from_name("getpid")?)?;
188    /// ctx.load()?;
189    /// let fd = ctx.get_notify_fd()?;
190    /// let pid = unsafe { libc::fork() };
191    /// if pid == 0 {
192    ///     // Child process
193    ///
194    ///     let child_pid = std::process::id();
195    ///     // The child PID will be same as the parent PID by the user notification.
196    ///     assert_eq!(child_pid, parent_pid);
197    ///     exit(0);
198    /// }
199    /// let req = ScmpNotifReq::receive(fd)?;
200    /// let resp = ScmpNotifResp::new_val(req.id, parent_pid as i64, ScmpNotifRespFlags::empty());
201    /// resp.respond(fd);
202    /// # Ok::<(), Box<dyn std::error::Error>>(())
203    /// ```
204    pub fn receive(fd: ScmpFd) -> Result<Self> {
205        notify_supported()?;
206
207        let mut req_ptr: *mut seccomp_notif = std::ptr::null_mut();
208
209        // We only use the request here; the response is unused.
210        cvt(unsafe { seccomp_notify_alloc(&mut req_ptr, std::ptr::null_mut()) })?;
211
212        loop {
213            let ret = unsafe { seccomp_notify_receive(fd, req_ptr) };
214            let errno = get_errno();
215
216            if ret == 0 {
217                break;
218            } else if errno == libc::EINTR {
219                continue;
220            } else {
221                unsafe { seccomp_notify_free(req_ptr, std::ptr::null_mut()) };
222                return Err(SeccompError::from_errno(ret));
223            }
224        }
225
226        // Copy notify request before freeing the memory.
227        let req = seccomp_notif {
228            id: unsafe { (*req_ptr).id },
229            pid: unsafe { (*req_ptr).pid },
230            flags: unsafe { (*req_ptr).flags },
231            data: unsafe { (*req_ptr).data },
232        };
233
234        unsafe { seccomp_notify_free(req_ptr, std::ptr::null_mut()) };
235
236        Self::from_sys(req)
237    }
238}
239
240/// Represents a userspace notification response.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
242#[non_exhaustive]
243pub struct ScmpNotifResp {
244    /// Notification ID (must match the corresponding `ScmpNotifReq` ID)
245    pub id: u64,
246    /// Return value for the syscall that created the notification.
247    /// Only relevant if the error is 0.
248    pub val: i64,
249    /// An error code.
250    /// Must be 0 if no error occurred, or an error constant from package
251    /// syscall (e.g., `libc::EPERM`, etc). In the latter case, it's used
252    /// as an error return from the syscall that created the notification.
253    pub error: i32,
254    /// Userspace notification response flag
255    pub flags: u32,
256}
257
258impl ScmpNotifResp {
259    unsafe fn to_sys(self, resp: *mut seccomp_notif_resp) {
260        unsafe {
261            (*resp).id = self.id;
262            (*resp).val = self.val;
263            (*resp).error = self.error;
264            (*resp).flags = self.flags;
265        }
266    }
267
268    /// Creates `ScmpNotifResp` from the specified arguments.
269    ///
270    /// It is recommended to use the convenient functions [`new_val`](ScmpNotifResp::new_val),
271    /// [`new_error`](ScmpNotifResp::new_error) and [`new_continue`](ScmpNotifResp::new_continue)
272    /// rather than this function.
273    ///
274    /// # Arguments
275    ///
276    /// * `id` - Notification ID
277    /// * `val` - Return value for the syscall that created the notification
278    /// * `error` - An error code
279    /// * `flags` - Userspace notification response flag
280    ///
281    /// # Examples
282    ///
283    /// ```ignore
284    /// let resp = ScmpNotifResp::new(req.id, val, error, flags);
285    /// ```
286    #[must_use]
287    pub fn new(id: u64, val: i64, error: i32, flags: u32) -> Self {
288        Self {
289            id,
290            val,
291            error,
292            flags,
293        }
294    }
295
296    /// Creates `ScmpNotifResp` for a spoofed success response.
297    ///
298    /// # Arguments
299    ///
300    /// * `id` - Notification ID
301    /// * `val` - Return value for the syscall that created the notification
302    /// * `flags` - Reserved for future use, specify as `ScmpNotifRespFlags::empty()`.
303    ///
304    /// # Examples
305    ///
306    /// ```ignore
307    /// let resp = ScmpNotifResp::new_val(req.id, val, ScmpNotifRespFlags::empty());
308    /// ```
309    #[must_use]
310    pub fn new_val(id: u64, val: i64, flags: ScmpNotifRespFlags) -> Self {
311        Self {
312            id,
313            val,
314            error: 0,
315            flags: flags.bits(),
316        }
317    }
318
319    /// Creates `ScmpNotifResp` for a spoofed error response.
320    ///
321    /// # Arguments
322    ///
323    /// * `id` - Notification ID
324    /// * `error` - An *negative* error code
325    /// * `flags` - Reserved for future use, specify as `ScmpNotifRespFlags::empty()`.
326    ///
327    /// # Examples
328    ///
329    /// ```ignore
330    /// let resp = ScmpNotifResp::new_error(req.id, error, ScmpNotifRespFlags::empty());
331    /// ```
332    #[must_use]
333    pub fn new_error(id: u64, error: i32, flags: ScmpNotifRespFlags) -> Self {
334        debug_assert!(error.is_negative());
335        Self {
336            id,
337            val: 0,
338            error,
339            flags: flags.bits(),
340        }
341    }
342
343    /// Creates `ScmpNotifResp` which continues the syscall execution.
344    ///
345    /// # Arguments
346    ///
347    /// * `id` - Notification ID
348    /// * `flags` - Reserved for future use, specify as `ScmpNotifRespFlags::empty()`.
349    ///
350    /// # Examples
351    ///
352    /// ```ignore
353    /// let resp = ScmpNotifResp::new_continue(req.id, ScmpNotifRespFlags::empty());
354    /// ```
355    #[must_use]
356    pub fn new_continue(id: u64, flags: ScmpNotifRespFlags) -> Self {
357        Self {
358            id,
359            val: 0,
360            error: 0,
361            flags: ScmpNotifRespFlags::CONTINUE.bitor(flags).bits(),
362        }
363    }
364
365    /// Responds to a userspace notification retrieved via [`ScmpNotifReq::receive()`].
366    ///
367    /// The response ID must match that of the corresponding notification retrieved
368    /// via [`ScmpNotifReq::receive()`].
369    ///
370    /// This function corresponds to
371    /// [`seccomp_notify_respond`](https://man7.org/linux/man-pages/man3/seccomp_notify_respond.3.html).
372    ///
373    /// # Arguments
374    ///
375    /// * `fd` - A file descriptor for the userspace notification
376    ///
377    /// # Errors
378    ///
379    /// If an issue is encountered responding a notification,
380    /// an error will be returned.
381    ///
382    /// # Examples
383    ///
384    /// ```
385    /// # use syd::libseccomp::*;
386    /// # use std::process::exit;
387    /// // Get the current process ID
388    /// let parent_pid = std::process::id();
389    /// let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
390    /// ctx.add_rule(ScmpAction::Notify, ScmpSyscall::from_name("getpid")?)?;
391    /// ctx.load()?;
392    /// let fd = ctx.get_notify_fd()?;
393    /// let pid = unsafe { libc::fork() };
394    /// if pid == 0 {
395    ///     // Child process
396    ///
397    ///     let child_pid = std::process::id();
398    ///     // The child PID will be same as the parent PID by the user notification.
399    ///     assert_eq!(child_pid, parent_pid);
400    ///     exit(0);
401    /// }
402    /// let req = ScmpNotifReq::receive(fd)?;
403    /// let resp = ScmpNotifResp::new_val(req.id, parent_pid as i64, ScmpNotifRespFlags::empty());
404    /// resp.respond(fd);
405    /// # Ok::<(), Box<dyn std::error::Error>>(())
406    /// ```
407    pub fn respond(&self, fd: ScmpFd) -> Result<()> {
408        notify_supported()?;
409
410        let mut resp_ptr: *mut seccomp_notif_resp = std::ptr::null_mut();
411
412        // We only use the response here; the request is unused.
413        cvt(unsafe { seccomp_notify_alloc(std::ptr::null_mut(), &mut resp_ptr) })?;
414
415        unsafe { self.to_sys(resp_ptr) };
416
417        loop {
418            let ret = unsafe { seccomp_notify_respond(fd, resp_ptr) };
419            let errno = get_errno();
420
421            if ret == 0 {
422                break;
423            } else if errno == libc::EINTR {
424                continue;
425            } else {
426                unsafe { seccomp_notify_free(std::ptr::null_mut(), resp_ptr) };
427                return Err(SeccompError::from_errno(ret));
428            }
429        }
430
431        unsafe { seccomp_notify_free(std::ptr::null_mut(), resp_ptr) };
432
433        Ok(())
434    }
435}
436
437/// Checks if a userspace notification is still valid.
438///
439/// A return value of `Ok` means the notification is still valid.
440/// Otherwise the notification is not valid. This can be used to mitigate
441/// time-of-check-time-of-use (TOCTOU) attacks as described in [`seccomp_notify_id_valid(2)`].
442///
443/// [`seccomp_notify_id_valid(2)`]: https://man7.org/linux/man-pages/man3/seccomp_notify_id_valid.3.html
444///
445/// This function corresponds to
446/// [`seccomp_notify_id_valid`](https://man7.org/linux/man-pages/man3/seccomp_notify_id_valid.3.html).
447///
448/// # Arguments
449///
450/// * `fd` - A file descriptor for the userspace notification
451/// * `id` - Notification ID
452///
453/// # Errors
454///
455/// If the notification ID is invalid, an error will be returned.
456///
457/// # Examples
458///
459/// ```ignore
460/// # use syd::libseccomp::*;
461/// # use std::{fs::File, io::Read, process::Command, thread};
462/// let notify_fd = thread::spawn(|| -> Result<ScmpFd, error::SeccompError> {
463///     let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
464///     ctx.add_rule(ScmpAction::Notify, ScmpSyscall::from_name("openat")?)?
465///         .load()?;
466///     let notify_fd = ctx.get_notify_fd()?;
467///
468///     /// Example command execution
469///     let _ = Command::new("ls").spawn();
470///     Ok(notify_fd)
471/// })
472/// .join()
473/// .unwrap()?;
474///
475/// let req = ScmpNotifReq::receive(notify_fd)?;
476/// assert_eq!(req.data.arch, ScmpArch::native());
477/// assert_eq!(req.data.syscall, ScmpSyscall::from_name("openat")?);
478///
479/// let mut stat = File::open(&format!("/proc/{}/stat", req.pid))?;
480///
481/// // Checks if a notification is still valid.
482/// notify_id_valid(notify_fd, req.id)?;
483///
484/// let mut stat_data = String::new();
485/// let _ = stat.read_to_string(&mut stat_data)?;
486/// println!("{}", stat_data);
487///
488/// // Checks if a notification is still valid.
489/// notify_id_valid(notify_fd, req.id)?;
490///
491/// let resp = ScmpNotifResp::new_continue(req.id, ScmpNotifRespFlags::empty());
492/// resp.respond(notify_fd)?;
493/// # Ok::<(), Box<dyn std::error::Error>>(())
494/// ```
495pub fn notify_id_valid(fd: ScmpFd, id: u64) -> Result<()> {
496    notify_supported()?;
497
498    loop {
499        let ret = unsafe { seccomp_notify_id_valid(fd, id) };
500        let errno = get_errno();
501
502        if ret == 0 {
503            break;
504        } else if errno == libc::EINTR {
505            continue;
506        } else {
507            return Err(SeccompError::from_errno(ret));
508        }
509    }
510
511    Ok(())
512}