char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somrearrange libs into lib and extensions into extsf4a0f5b

main
7.6 KiB211 linesraw
1//! Linux specifics of the Unix backend.
2
3use std::ffi::CString;
4use std::io;
5use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd};
6use std::os::unix::ffi::OsStrExt;
7
8use super::reserve;
9
10pub(super) fn pipe() -> io::Result<(OwnedFd, OwnedFd)> {
11    let mut fds = [0; 2];
12    if unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) } < 0 {
13        return Err(io::Error::last_os_error());
14    }
15    Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) })
16}
17
18/// Returns the master and slave of a new terminal.
19pub(super) fn pty() -> io::Result<(OwnedFd, OwnedFd)> {
20    let fd = unsafe { libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY | libc::O_CLOEXEC) };
21    if fd < 0 {
22        return Err(io::Error::last_os_error());
23    }
24    let master = unsafe { OwnedFd::from_raw_fd(fd) };
25    if unsafe { libc::grantpt(fd) } < 0 || unsafe { libc::unlockpt(fd) } < 0 {
26        return Err(io::Error::last_os_error());
27    }
28    let fd = unsafe {
29        libc::ioctl(
30            fd,
31            libc::TIOCGPTPEER,
32            libc::O_RDWR | libc::O_NOCTTY | libc::O_CLOEXEC,
33        )
34    };
35    if fd < 0 {
36        return Err(io::Error::last_os_error());
37    }
38    Ok((master, unsafe { OwnedFd::from_raw_fd(fd) }))
39}
40
41/// The authorized executable, pinned by an `O_PATH` descriptor through exec.
42pub struct Executable {
43    fd: OwnedFd,
44}
45impl Executable {
46    pub fn open_at(
47        directory: &impl AsFd,
48        name: &std::ffi::OsStr,
49    ) -> io::Result<(Self, std::fs::Metadata)> {
50        let label = CString::new(name.as_bytes())?;
51        // Follow the final link (multi-call/symlinked entries) while the parent
52        // directory fd pins the lookup; the entry authorization covers the
53        // lexical name, and argv[0] preserves it for multicall dispatch.
54        let flags = libc::O_PATH | libc::O_CLOEXEC;
55        let fd = unsafe { libc::openat(directory.as_fd().as_raw_fd(), label.as_ptr(), flags) };
56        if fd < 0 {
57            return Err(io::Error::last_os_error());
58        }
59        let fd = unsafe { OwnedFd::from_raw_fd(fd) };
60        let metadata = std::fs::File::from(reserve(fd.as_raw_fd(), 3)?).metadata()?;
61        Ok((Self { fd }, metadata))
62    }
63    /// Moves the descriptor clear of every requested child descriptor.
64    pub(super) fn reserve(&self, minimum: RawFd) -> io::Result<Self> {
65        Ok(Self {
66            fd: reserve(self.fd.as_raw_fd(), minimum)?,
67        })
68    }
69    /// Runs after fork; returns only on failure.
70    pub(super) fn execute(
71        &self,
72        argv: *const *const libc::c_char,
73        env: *const *const libc::c_char,
74    ) -> io::Error {
75        let execute = || unsafe { libc::fexecve(self.fd.as_raw_fd(), argv, env) };
76        execute();
77        let error = io::Error::last_os_error();
78        // Script interpreters need the pinned script fd after exec; binaries do not.
79        if error.raw_os_error() == Some(libc::ENOENT) {
80            if unsafe { libc::fcntl(self.fd.as_raw_fd(), libc::F_SETFD, 0) } < 0 {
81                return io::Error::last_os_error();
82            }
83            execute();
84            return io::Error::last_os_error();
85        }
86        error
87    }
88}
89
90/// Taken before fork, so the child can tell whether its owner already died.
91pub(super) struct Owner(libc::pid_t);
92impl Owner {
93    pub fn new() -> Self {
94        Self(unsafe { libc::getpid() })
95    }
96    /// Orphaned children must not outlive an abruptly killed owner.
97    pub fn bind(&self) -> io::Result<()> {
98        if unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL as u64) } < 0 {
99            return Err(io::Error::last_os_error());
100        }
101        if unsafe { libc::getppid() } != self.0 {
102            return Err(io::Error::new(io::ErrorKind::NotConnected, "owner exited"));
103        }
104        Ok(())
105    }
106}
107
108pub(super) struct Descriptors;
109impl Descriptors {
110    pub fn new() -> io::Result<Self> {
111        Ok(Self)
112    }
113    /// Marks every descriptor from 3 up CLOEXEC; spawn re-arms the declared
114    /// ones. Marking rather than closing keeps the executable and exec-error
115    /// pipe open until exec.
116    pub fn restrict(&mut self) -> io::Result<()> {
117        let result = unsafe {
118            libc::syscall(
119                libc::SYS_close_range,
120                3u32,
121                u32::MAX,
122                libc::CLOSE_RANGE_CLOEXEC,
123            )
124        };
125        if result == 0 {
126            return Ok(());
127        }
128        let error = io::Error::last_os_error();
129        if !matches!(
130            error.raw_os_error(),
131            Some(libc::ENOSYS | libc::EINVAL | libc::EPERM)
132        ) {
133            return Err(error);
134        }
135
136        // RLIMIT_NOFILE can be lowered below existing descriptors, so scanning
137        // only to that limit would leak them. getdents64 avoids allocation after
138        // fork and visits exactly the child's open descriptors, including high FDs.
139        let directory = unsafe {
140            libc::open(
141                c"/proc/self/fd".as_ptr(),
142                libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
143            )
144        };
145        if directory < 0 {
146            let error = io::Error::last_os_error();
147            return Err(
148                if matches!(error.raw_os_error(), Some(libc::ENOENT | libc::ENOTDIR)) {
149                    io::Error::from_raw_os_error(libc::ENOTSUP)
150                } else {
151                    error
152                },
153            );
154        }
155        let directory = unsafe { OwnedFd::from_raw_fd(directory) };
156        #[repr(align(8))]
157        struct Buffer([u8; 8192]);
158        let mut buffer = Buffer([0; 8192]);
159        loop {
160            let count = unsafe {
161                libc::syscall(
162                    libc::SYS_getdents64,
163                    directory.as_raw_fd(),
164                    buffer.0.as_mut_ptr(),
165                    buffer.0.len(),
166                )
167            };
168            if count < 0 {
169                let error = io::Error::last_os_error();
170                if error.kind() == io::ErrorKind::Interrupted {
171                    continue;
172                }
173                return Err(error);
174            }
175            if count == 0 {
176                return Ok(());
177            }
178            let mut entries = &buffer.0[..count as usize];
179            while !entries.is_empty() {
180                // linux_dirent64: ino(8), off(8), reclen(2), type(1), name.
181                if entries.len() < 20 {
182                    return Err(io::Error::from_raw_os_error(libc::EIO));
183                }
184                let length = u16::from_ne_bytes([entries[16], entries[17]]) as usize;
185                if length < 20 || length > entries.len() {
186                    return Err(io::Error::from_raw_os_error(libc::EIO));
187                }
188                let name = &entries[19..length];
189                let end = name
190                    .iter()
191                    .position(|byte| *byte == 0)
192                    .ok_or_else(|| io::Error::from_raw_os_error(libc::EIO))?;
193                let fd = name[..end].iter().try_fold(0i32, |value, byte| {
194                    if !byte.is_ascii_digit() {
195                        return None;
196                    }
197                    value.checked_mul(10)?.checked_add((byte - b'0') as i32)
198                });
199                if let Some(fd) = fd.filter(|fd| *fd >= 3) {
200                    let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
201                    if flags < 0
202                        || unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) } < 0
203                    {
204                        return Err(io::Error::last_os_error());
205                    }
206                }
207                entries = &entries[length..];
208            }
209        }
210    }
211}