smithay/utils/
sealed_file.rs

1use std::{
2    ffi::CStr,
3    fs::File,
4    io::Write,
5    os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
6};
7
8/// A file whose fd cannot be written by other processes
9///
10/// This mechanism is useful for giving clients access to large amounts of
11/// information such as keymaps without them being able to write to the handle.
12///
13/// On Linux, Android, and FreeBSD, this uses a sealed memfd. On other platforms
14/// it creates a POSIX shared memory object with `shm_open`, opens a read-only
15/// copy, and unlinks it.
16#[derive(Debug)]
17pub struct SealedFile {
18    file: File,
19    size: usize,
20}
21
22impl SealedFile {
23    /// Create a `[SealedFile]` with the given nul-terminated C string.
24    pub fn with_content(name: &CStr, contents: &CStr) -> Result<Self, std::io::Error> {
25        Self::with_data(name, contents.to_bytes_with_nul())
26    }
27
28    /// Create a `[SealedFile]` with the given binary data.
29    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "android"))]
30    pub fn with_data(name: &CStr, data: &[u8]) -> Result<Self, std::io::Error> {
31        use rustix::fs::{MemfdFlags, SealFlags};
32        use std::io::Seek;
33
34        let fd = rustix::fs::memfd_create(name, MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING)?;
35
36        let mut file: File = fd.into();
37        file.write_all(data)?;
38        file.flush()?;
39
40        file.seek(std::io::SeekFrom::Start(0))?;
41
42        rustix::fs::fcntl_add_seals(
43            &file,
44            SealFlags::SEAL | SealFlags::SHRINK | SealFlags::GROW | SealFlags::WRITE,
45        )?;
46
47        Ok(Self {
48            file,
49            size: data.len(),
50        })
51    }
52
53    /// Create a `[SealedFile]` with the given binary data.
54    #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "android")))]
55    pub fn with_data(name: &CStr, data: &[u8]) -> Result<Self, std::io::Error> {
56        use rand::{distr::Alphanumeric, Rng};
57        use rustix::{
58            io::Errno,
59            shm::{self, Mode},
60        };
61
62        let mut rng = rand::rng();
63
64        // `memfd_create` isn't available. Instead, try `shm_open` with a randomized name, and
65        // loop a couple times if it exists.
66        let mut n = 0;
67        let (shm_name, mut file) = loop {
68            let mut shm_name = name.to_bytes().to_owned();
69            shm_name.push(b'-');
70            shm_name.extend((0..7).map(|_| rng.sample(Alphanumeric)));
71            let fd = shm::open(
72                shm_name.as_slice(),
73                shm::OFlags::RDWR | shm::OFlags::CREATE | shm::OFlags::EXCL,
74                Mode::RWXU,
75            );
76            if !matches!(fd, Err(Errno::EXIST)) || n > 3 {
77                break (shm_name, File::from(fd?));
78            }
79            n += 1;
80        };
81
82        // Sealing isn't available, so re-open read-only.
83        let fd_rdonly = shm::open(shm_name.as_slice(), shm::OFlags::RDONLY, Mode::empty())?;
84        let file_rdonly = File::from(fd_rdonly);
85
86        // Unlink so another process can't open shm file.
87        let _ = shm::unlink(shm_name.as_slice());
88
89        file.write_all(data)?;
90        file.flush()?;
91
92        Ok(Self {
93            file: file_rdonly,
94            size: data.len(),
95        })
96    }
97
98    /// Size of the data contained in the sealed file.
99    pub fn size(&self) -> usize {
100        self.size
101    }
102}
103
104impl AsRawFd for SealedFile {
105    fn as_raw_fd(&self) -> RawFd {
106        self.file.as_raw_fd()
107    }
108}
109
110impl AsFd for SealedFile {
111    fn as_fd(&self) -> BorrowedFd<'_> {
112        self.file.as_fd()
113    }
114}