Skip to main content

syd/
seal.rs

1//
2// Syd: rock-solid application kernel
3// src/seal.rs: Execute program as sealed anonymous file
4//
5// Copyright (c) 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
6// Based in part upon pentacle which is:
7//   Copyright (c) iliana destroyer of worlds <iliana@buttslol.net>
8//   SPDX-License-Identifier: MIT
9//
10// SPDX-License-Identifier: GPL-3.0
11
12// SAFETY: This module has been liberated from unsafe code!
13#![forbid(unsafe_code)]
14
15// Last sync with pentacle:
16// Version 1.0.0
17// Commit:e606ab250e6655865bb93a6d98157093f2eb455f
18
19use std::{
20    convert::Infallible,
21    env,
22    ffi::{CStr, CString},
23    fs::Permissions,
24    os::{
25        fd::AsFd,
26        unix::{ffi::OsStringExt, fs::PermissionsExt},
27    },
28};
29
30use libc::mode_t;
31use nix::{
32    errno::Errno,
33    fcntl::{fcntl, openat, AtFlags, FcntlArg, OFlag, SealFlag},
34    libc::{
35        c_int, c_uint, F_SEAL_FUTURE_WRITE, F_SEAL_GROW, F_SEAL_SEAL, F_SEAL_SHRINK, F_SEAL_WRITE,
36        MFD_ALLOW_SEALING, MFD_CLOEXEC, MFD_EXEC, MFD_NOEXEC_SEAL,
37    },
38    sys::stat::Mode,
39    unistd::execveat,
40};
41
42use crate::{
43    compat::{fstatx, MFdFlags, STATX_TYPE},
44    config::ENV_SKIP_SCMP,
45    confine::secure_getenv,
46    err::err2no,
47    fd::SafeOwnedFd,
48    io::ReadFd,
49    lookup::FileType,
50    proc::proc_open,
51    retry::retry_on_eintr,
52};
53
54// Default memory fd name.
55const DEFAULT_MEMFD_NAME: &CStr = c"syd";
56
57// not yet present in the libc crate
58// linux: include/uapi/linux/fcntl.h
59const F_SEAL_EXEC: c_int = 0x0020;
60
61const OPTIONS: SealOptions = SealOptions::new().close_on_exec(true).executable(true);
62
63/// Ensure the currently running program is a sealed anonymous file.
64///
65/// For safety, the executable path is located in `/proc/self/maps`, and
66/// executable's inode and device ID are verified on open. On verification
67/// errors `Errno::EBADF` is returned.
68///
69/// If the current executable is not a sealed anonymous file, a new
70/// anonymous file is created, the executable content is copied to it,
71/// the file is sealed, and [`CommandExt::exec`] is called. When the
72/// program begins again, this function will detect the executable as a
73/// sealed anonymous file and return `Ok(())`.
74///
75/// You should call this function at the beginning of `main`. This
76/// function has the same implications as [`CommandExt::exec`]: no
77/// destructors on the current stack or any other thread's stack will be
78/// run.
79///
80/// # Errors
81///
82/// An error is returned if the executable file is not a regular file,
83/// file fails to open, file verification fails, `memfd_create(2)`
84/// fails, the `fcntl(2)` `F_GET_SEALS` or `F_ADD_SEALS` commands fail,
85/// or copying from executable file to the anonymous file fails.
86pub fn ensure_sealed() -> Result<(), Errno> {
87    // Open procfs safely and validate.
88    let fd_proc = proc_open(None)?;
89
90    // Open proc_pid_exe(5) safely.
91    #[expect(clippy::disallowed_methods)]
92    let fd = openat(
93        fd_proc,
94        c"self/exe",
95        OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_CLOEXEC,
96        Mode::empty(),
97    )
98    .map(SafeOwnedFd::from)?;
99
100    if OPTIONS.is_sealed(&fd) {
101        // Already sealed, move on...
102        Ok(())
103    } else {
104        // Copy into memfd, seal and reexec.
105        Err(SealedCommand::new(fd)?.exec().unwrap_err())
106    }
107}
108
109/// A [`Command`] wrapper that spawns sealed memory-backed programs.
110pub struct SealedCommand {
111    memfd: SafeOwnedFd,
112}
113
114impl SealedCommand {
115    /// Constructs a new [`SealedCommand`] for launching the program
116    /// data in `program` as a sealed memory-backed file.
117    ///
118    /// The memory-backed file will close on `execveat(2)`.
119    ///
120    /// # Errors
121    ///
122    /// An error is returned if `program` is not a regular file,
123    /// `memfd_create(2)` fails, the `fcntl(2)` `F_GET_SEALS` or
124    /// `F_ADD_SEALS` commands fail, or copying from `program` to the
125    /// anonymous file fails.
126    pub fn new<Fd>(mut program: Fd) -> Result<Self, Errno>
127    where
128        Fd: ReadFd,
129    {
130        // Check the file type and bail if it's not a regular file.
131        let statx = retry_on_eintr(|| fstatx(&program, STATX_TYPE))?;
132        let ftype = FileType::from(mode_t::from(statx.stx_mode));
133        if !ftype.is_file() {
134            return Err(Errno::ENOEXEC);
135        }
136
137        let mut memfd = OPTIONS.create()?;
138        crate::io::copy(&mut program, &mut memfd)?;
139        OPTIONS.seal(&mut memfd)?;
140
141        Ok(Self { memfd })
142    }
143
144    /// Execute the memory-backed file with execveat(2) and AT_EMPTY_PATH.
145    ///
146    /// The file will be closed on execveat(2).
147    pub fn exec(self) -> Result<Infallible, Errno> {
148        // Force RUST_BACKTRACE environment variable to 0 for Syd.
149        // Passthrough the original value to the sandbox process.
150        // See syd.rs for the other branch.
151        // Rest is handled in unshare/child.rs.
152        match env::var_os("RUST_BACKTRACE") {
153            Some(val) => env::set_var("SYD_RUST_BACKTRACE", val),
154            None => env::remove_var("SYD_RUST_BACKTRACE"),
155        };
156        if secure_getenv(ENV_SKIP_SCMP).is_none() {
157            env::set_var("RUST_BACKTRACE", "0");
158        }
159
160        // Collect arguments.
161        let args = env::args_os()
162            .map(|arg| CString::new(arg.into_vec()).or(Err(Errno::EINVAL)))
163            .collect::<Result<Vec<CString>, Errno>>()?;
164
165        // Collect environment variables.
166        let envs = env::vars_os()
167            .map(|(k, v)| {
168                let mut bytes = k.into_vec();
169                bytes.push(b'=');
170                bytes.extend(v.into_vec());
171                CString::new(bytes).or(Err(Errno::EINVAL))
172            })
173            .collect::<Result<Vec<CString>, Errno>>()?;
174
175        execveat(self.memfd, c"", &args, &envs, AtFlags::AT_EMPTY_PATH)
176    }
177}
178
179macro_rules! set_flag {
180    ($flags:expr, $flag:expr, $value:expr) => {
181        if $value {
182            $flags |= $flag;
183        } else {
184            $flags &= !$flag;
185        }
186    };
187}
188
189macro_rules! seal {
190    (
191        $seal_ident:ident
192        $( { $( #[ $attr:meta ] )* } )? ,
193        $must_seal_ident:ident
194        $( { $( #[ $must_attr:meta ] )* } )? ,
195        $( ? $preflight:ident : )? $flag:expr,
196        $try_to:expr,
197        $default:expr
198    ) => {
199        #[doc = concat!("If `true`, try to ", $try_to, ".")]
200        #[doc = ""]
201        #[doc = "If `false`, also set"]
202        #[doc = concat!("[`SealOptions::", stringify!($must_seal_ident), "`]")]
203        #[doc = "to `false`."]
204        #[doc = ""]
205        #[doc = concat!("This flag is `", $default, "` by default.")]
206        $($( #[ $attr ] )*)?
207        pub const fn $seal_ident(mut self, $seal_ident: bool) -> SealOptions {
208            if true $( && self.$preflight() )? {
209                set_flag!(self.seal_flags, $flag, $seal_ident);
210            }
211            if !$seal_ident {
212                self.must_seal_flags &= !$flag;
213            }
214            self
215        }
216
217        #[doc = "If `true`, also set"]
218        #[doc = concat!("[`SealOptions::", stringify!($seal_ident), "`] to `true`")]
219        #[doc = "and ensure it is successful when [`SealOptions::seal`] is called."]
220        #[doc = ""]
221        #[doc = concat!("This flag is `", $default, "` by default.")]
222        $($( #[ $must_attr ] )*)?
223        pub const fn $must_seal_ident(mut self, $must_seal_ident: bool) -> SealOptions {
224            if $must_seal_ident {
225                self.seal_flags |= $flag;
226            }
227            set_flag!(self.must_seal_flags, $flag, $must_seal_ident);
228            self
229        }
230    };
231}
232
233/// Options for creating a sealed anonymous file.
234#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
235#[must_use]
236pub struct SealOptions {
237    memfd_flags: c_uint,
238    seal_flags: c_int,
239    must_seal_flags: c_int,
240}
241
242impl Default for SealOptions {
243    fn default() -> Self {
244        Self::new()
245    }
246}
247
248impl SealOptions {
249    /// Create a default set of options ready for configuration.
250    ///
251    /// This is equivalent to:
252    /// ```
253    /// use syd::seal::SealOptions;
254    /// let options = SealOptions::new()
255    ///     .close_on_exec(true)
256    ///     .must_seal_seals(true)
257    ///     .must_seal_shrinking(true)
258    ///     .must_seal_growing(true)
259    ///     .must_seal_writing(true)
260    ///     .seal_future_writing(false)
261    ///     .seal_executable(false);
262    /// assert_eq!(options, SealOptions::default());
263    /// ```
264    pub const fn new() -> Self {
265        Self {
266            memfd_flags: MFD_ALLOW_SEALING | MFD_CLOEXEC,
267            seal_flags: F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE,
268            must_seal_flags: F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE,
269        }
270    }
271
272    /// Sets the close-on-exec (`CLOEXEC`) flag for the new file.
273    ///
274    /// When a child process is created, the child normally inherits any open file descriptors.
275    /// Setting the close-on-exec flag will cause this file descriptor to automatically be closed
276    /// instead.
277    ///
278    /// This flag is `true` by default, matching the behavior of [`std::fs`].
279    pub const fn close_on_exec(mut self, close_on_exec: bool) -> SealOptions {
280        set_flag!(self.memfd_flags, MFD_CLOEXEC, close_on_exec);
281        self
282    }
283
284    /// Sets whether the resulting file must have or not have execute permission set.
285    ///
286    /// If set, the OS is explicitly asked to set the execute permission when `exec` is
287    /// `true`, or unset the execute permission when `exec` is `false`. If the OS refuses,
288    /// [`SealOptions::create`] tries to set or unset the execute permission, and returns an error
289    /// if it fails.
290    ///
291    /// Calling this function enables the equivalent of calling [`SealOptions::seal_executable`]
292    /// with `true` for implementation reasons.
293    ///
294    /// This flag is neither `true` nor `false` by default; instead behavior is delegated to the
295    /// OS's default behavior.
296    ///
297    /// # Context
298    ///
299    /// The original `memfd_create(2)` implementation on Linux creates anonymous files with the
300    /// executable permission set. Later in Linux 6.3, programs and system administrators were
301    /// given tools to control this (see also <https://lwn.net/Articles/918106/>):
302    ///
303    /// - Setting the sysctl `vm.memfd_noexec = 1` disables creating executable anonymous files
304    ///   unless the program requests it with `MFD_EXEC`.
305    /// - Setting the sysctl `vm.memfd_noexec = 2` disables the ability to create executable
306    ///   anonymous files altogether, and `MFD_NOEXEC_SEAL` _must_ be used.
307    /// - Calling `memfd_create(2)` with `MFD_NOEXEC_SEAL` enables the `F_SEAL_EXEC` seal.
308    ///
309    /// Linux prior to 6.3 is unaware of `MFD_EXEC` and `F_SEAL_EXEC`. If `memfd_create(2)` sets
310    /// `errno` to `EINVAL`, this library retries the call without possibly-unknown flags, and the
311    /// permission bits of the memfd are adjusted depending on this setting.
312    pub const fn executable(mut self, executable: bool) -> SealOptions {
313        self.memfd_flags = self.memfd_flags & !MFD_EXEC & !MFD_NOEXEC_SEAL
314            | if executable {
315                MFD_EXEC
316            } else {
317                MFD_NOEXEC_SEAL
318            };
319        self.seal_flags |= F_SEAL_EXEC;
320        self
321    }
322
323    const fn is_executable_set(&self) -> bool {
324        self.memfd_flags & (MFD_EXEC | MFD_NOEXEC_SEAL) != 0
325    }
326
327    seal!(
328        seal_seals,
329        must_seal_seals,
330        F_SEAL_SEAL,
331        "prevent further seals from being set on this file",
332        true
333    );
334    seal!(
335        seal_shrinking,
336        must_seal_shrinking,
337        F_SEAL_SHRINK,
338        "prevent shrinking this file",
339        true
340    );
341    seal!(
342        seal_growing,
343        must_seal_growing,
344        F_SEAL_GROW,
345        "prevent growing this file",
346        true
347    );
348    seal!(
349        seal_writing,
350        must_seal_writing,
351        F_SEAL_WRITE,
352        "prevent writing to this file",
353        true
354    );
355    seal!(
356        seal_future_writing {
357            #[doc = ""]
358            #[doc = "This requires at least Linux 5.1."]
359        },
360        must_seal_future_writing {
361            #[doc = ""]
362            #[doc = "This requires at least Linux 5.1."]
363        },
364        F_SEAL_FUTURE_WRITE,
365        "prevent directly writing to this file or creating new writable mappings, \
366            but allow writes to existing writable mappings",
367        false
368    );
369    seal!(
370        seal_executable {
371            #[doc = ""]
372            #[doc = "If [`SealOptions::executable`] has already been called,"]
373            #[doc = "this function does nothing."]
374            #[doc = ""]
375            #[doc = "This requires at least Linux 6.3."]
376        },
377        must_seal_executable {
378            #[doc = ""]
379            #[doc = "This requires at least Linux 6.3."]
380        },
381        ? seal_executable_preflight : F_SEAL_EXEC,
382        "prevent modifying the executable permission of the file",
383        false
384    );
385
386    const fn seal_executable_preflight(&self) -> bool {
387        !self.is_executable_set()
388    }
389
390    /// Create an anonymous file, copy the contents of `reader` to it, and seal it.
391    ///
392    /// # Errors
393    ///
394    /// This method returns an error when any of [`SealOptions::create`], [`syd::io::copy`], or
395    /// [`SealOptions::seal`] fail.
396    pub fn copy_and_seal<Fd>(&self, reader: &mut Fd) -> Result<SafeOwnedFd, Errno>
397    where
398        Fd: ReadFd,
399    {
400        let mut file = self.create()?;
401        crate::io::copy(reader, &mut file)?;
402        self.seal(&mut file)?;
403        Ok(file)
404    }
405
406    /// Create an unsealed anonymous file with these options.
407    ///
408    /// It is the caller's responsibility to seal this file after writing with
409    /// [`SealOptions::seal`]. If possible, avoid using this function and prefer
410    /// [`SealOptions::copy_and_seal`].
411    ///
412    /// # Errors
413    ///
414    /// This method returns an error when:
415    /// - `memfd_create(2)` fails
416    /// - `SealOptions::executable` was set but permissions cannot be changed as required
417    pub fn create(&self) -> Result<SafeOwnedFd, Errno> {
418        let fd = match memfd_create(DEFAULT_MEMFD_NAME, self.memfd_flags) {
419            Ok(fd) => fd,
420            Err(Errno::EINVAL) if self.is_executable_set() => {
421                // Linux prior to 6.3 will not know about `MFD_EXEC` or `MFD_NOEXEC_SEAL`,
422                // and returns `EINVAL` when it gets unknown flag bits. Retry without the
423                // possibly-unknown flag, and then attempt to set the appropriate permissions.
424                //
425                // (If `vm.memfd_noexec = 2`, we won't hit this branch because the OS returns
426                // EACCES.)
427                memfd_create(
428                    DEFAULT_MEMFD_NAME,
429                    self.memfd_flags & !MFD_EXEC & !MFD_NOEXEC_SEAL,
430                )?
431            }
432            Err(errno) => return Err(errno),
433        };
434
435        if self.is_executable_set() {
436            let permissions = fd.metadata().map_err(|err| err2no(&err))?.permissions();
437            let new_permissions =
438                Permissions::from_mode(if self.memfd_flags & MFD_NOEXEC_SEAL != 0 {
439                    permissions.mode() & !0o111
440                } else if self.memfd_flags & MFD_EXEC != 0 {
441                    permissions.mode() | 0o111
442                } else {
443                    return Ok(fd);
444                });
445            if permissions != new_permissions {
446                fd.set_permissions(new_permissions)
447                    .map_err(|err| err2no(&err))?;
448            }
449        }
450
451        Ok(fd)
452    }
453
454    /// Seal an anonymous file with these options.
455    ///
456    /// This should be called on a file created with [`SealOptions::create`]. Attempting to use
457    /// this method on other files will likely fail.
458    ///
459    /// # Errors
460    ///
461    /// This method returns an error when:
462    /// - the `fcntl(2)` `F_ADD_SEALS` command fails (other than `EINVAL`).
463    /// - the `fcntl(2)` `F_GET_SEALS` command fails.
464    /// - if any required seals are not present (in this case errno is set to `EBADF`).
465    pub fn seal<Fd: AsFd>(&self, fd: Fd) -> Result<(), Errno> {
466        // Set seals in groups, based on how recently the seal was added to Linux.
467        // Ignore `EINVAL`; we'll verify against `self.must_seal_flags`.
468        for group in [
469            F_SEAL_EXEC,                                              // Linux 6.3
470            F_SEAL_FUTURE_WRITE,                                      // Linux 5.1
471            F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE, // Linux 3.17
472        ] {
473            match fcntl_add_seals(&fd, self.seal_flags & group) {
474                Ok(()) => {}
475                Err(Errno::EINVAL) => {}
476                Err(errno) => return Err(errno),
477            }
478        }
479
480        if self.is_sealed_inner(fd)? {
481            Ok(())
482        } else {
483            Err(Errno::EBADF)
484        }
485    }
486
487    /// Check if `file` is sealed as required by these options.
488    ///
489    /// If the file doesn't support sealing (or `fcntl(2)` otherwise
490    /// returns an error), this method returns `false`.
491    pub fn is_sealed<Fd: AsFd>(&self, fd: Fd) -> bool {
492        self.is_sealed_inner(fd).unwrap_or(false)
493    }
494
495    fn is_sealed_inner<Fd: AsFd>(&self, fd: Fd) -> Result<bool, Errno> {
496        Ok(fcntl_get_seals(fd)? & self.must_seal_flags == self.must_seal_flags)
497    }
498}
499
500fn memfd_create(name: &CStr, flags: c_uint) -> Result<SafeOwnedFd, Errno> {
501    nix::sys::memfd::memfd_create(name, MFdFlags::from_bits_retain(flags).into())
502        .map(SafeOwnedFd::from)
503}
504
505fn fcntl_get_seals<Fd: AsFd>(fd: Fd) -> Result<c_int, Errno> {
506    fcntl(fd, FcntlArg::F_GET_SEALS)
507}
508
509fn fcntl_add_seals<Fd: AsFd>(fd: Fd, arg: c_int) -> Result<(), Errno> {
510    fcntl(fd, FcntlArg::F_ADD_SEALS(SealFlag::from_bits_retain(arg))).map(drop)
511}
512
513#[cfg(test)]
514mod test {
515    use std::{fs::File, os::unix::fs::PermissionsExt as _};
516
517    use super::*;
518
519    #[test]
520    fn new() {
521        let options = SealOptions {
522            memfd_flags: MFD_ALLOW_SEALING,
523            seal_flags: 0,
524            must_seal_flags: 0,
525        };
526        assert_eq!(
527            options
528                .close_on_exec(true)
529                .must_seal_seals(true)
530                .must_seal_shrinking(true)
531                .must_seal_growing(true)
532                .must_seal_writing(true)
533                .seal_future_writing(false)
534                .seal_executable(false),
535            SealOptions::new()
536        );
537    }
538
539    #[test]
540    fn flags() {
541        const ALL_SEALS: c_int = F_SEAL_SEAL
542            | F_SEAL_SHRINK
543            | F_SEAL_GROW
544            | F_SEAL_WRITE
545            | F_SEAL_FUTURE_WRITE
546            | F_SEAL_EXEC;
547
548        let mut options = SealOptions::new();
549        assert_eq!(options.memfd_flags & MFD_ALLOW_SEALING, MFD_ALLOW_SEALING);
550
551        assert_eq!(options.memfd_flags & MFD_CLOEXEC, MFD_CLOEXEC);
552        options = options.close_on_exec(false);
553        assert_eq!(options.memfd_flags & MFD_CLOEXEC, 0);
554        options = options.close_on_exec(true);
555        assert_eq!(options.memfd_flags & MFD_CLOEXEC, MFD_CLOEXEC);
556
557        assert_eq!(
558            options.seal_flags & ALL_SEALS,
559            ALL_SEALS & !F_SEAL_FUTURE_WRITE & !F_SEAL_EXEC
560        );
561        assert_eq!(
562            options.must_seal_flags & ALL_SEALS,
563            ALL_SEALS & !F_SEAL_FUTURE_WRITE & !F_SEAL_EXEC
564        );
565        options = options
566            .must_seal_future_writing(true)
567            .must_seal_executable(true);
568        assert_eq!(options.seal_flags & ALL_SEALS, ALL_SEALS);
569        assert_eq!(options.must_seal_flags & ALL_SEALS, ALL_SEALS);
570        // `seal_*(false)` unsets `must_seal_*`
571        options = options
572            .seal_seals(false)
573            .seal_shrinking(false)
574            .seal_growing(false)
575            .seal_writing(false)
576            .seal_future_writing(false)
577            .seal_executable(false);
578        assert_eq!(options.seal_flags & ALL_SEALS, 0);
579        assert_eq!(options.must_seal_flags & ALL_SEALS, 0);
580        // `seal_*(true)` does not set `must_seal_*`
581        options = options
582            .seal_seals(true)
583            .seal_shrinking(true)
584            .seal_growing(true)
585            .seal_writing(true)
586            .seal_future_writing(true)
587            .seal_executable(true);
588        assert_eq!(options.seal_flags & ALL_SEALS, ALL_SEALS);
589        assert_eq!(options.must_seal_flags & ALL_SEALS, 0);
590        // `must_seal_*(true)` sets `seal_*`
591        options = options
592            .seal_seals(false)
593            .seal_shrinking(false)
594            .seal_growing(false)
595            .seal_writing(false)
596            .seal_future_writing(false)
597            .seal_executable(false);
598        assert_eq!(options.seal_flags & ALL_SEALS, 0);
599        assert_eq!(options.must_seal_flags & ALL_SEALS, 0);
600        options = options
601            .must_seal_seals(true)
602            .must_seal_shrinking(true)
603            .must_seal_growing(true)
604            .must_seal_writing(true)
605            .must_seal_future_writing(true)
606            .must_seal_executable(true);
607        assert_eq!(options.seal_flags & ALL_SEALS, ALL_SEALS);
608        assert_eq!(options.must_seal_flags & ALL_SEALS, ALL_SEALS);
609        // `must_seal_*(false)` does not unset `seal_*`
610        options = options
611            .must_seal_seals(false)
612            .must_seal_shrinking(false)
613            .must_seal_growing(false)
614            .must_seal_writing(false)
615            .must_seal_future_writing(false)
616            .must_seal_executable(false);
617        assert_eq!(options.seal_flags & ALL_SEALS, ALL_SEALS);
618        assert_eq!(options.must_seal_flags & ALL_SEALS, 0);
619    }
620
621    #[test]
622    fn execute_flags() {
623        let mut options = SealOptions::new();
624        assert_eq!(options.seal_flags & F_SEAL_EXEC, 0);
625        options = options.seal_executable(true);
626        assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
627        options = options.seal_executable(false);
628        assert_eq!(options.seal_flags & F_SEAL_EXEC, 0);
629
630        for _ in 0..2 {
631            options = options.executable(true);
632            assert_eq!(options.memfd_flags & (MFD_EXEC | MFD_NOEXEC_SEAL), MFD_EXEC);
633            assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
634            // no-op once `executable` is called
635            options = options.seal_executable(false);
636            assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
637
638            options = options.executable(false);
639            assert_eq!(
640                options.memfd_flags & (MFD_EXEC | MFD_NOEXEC_SEAL),
641                MFD_NOEXEC_SEAL
642            );
643            assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
644            // no-op once `executable` is called
645            options = options.seal_executable(false);
646            assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
647        }
648
649        assert_eq!(options.must_seal_flags & F_SEAL_EXEC, 0);
650        options = options.must_seal_executable(true);
651        assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
652        assert_eq!(options.must_seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
653        options = options.seal_executable(false);
654        assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
655        assert_eq!(options.must_seal_flags & F_SEAL_EXEC, 0);
656    }
657
658    #[test]
659    fn executable() {
660        let mut null = File::open("/dev/null").unwrap();
661        let file = SealOptions::new()
662            .executable(false)
663            .copy_and_seal(&mut null)
664            .unwrap();
665        assert_eq!(file.metadata().unwrap().permissions().mode() & 0o111, 0);
666
667        let file = SealOptions::new()
668            .executable(true)
669            .copy_and_seal(&mut null)
670            .unwrap();
671        assert_eq!(file.metadata().unwrap().permissions().mode() & 0o111, 0o111);
672    }
673}