Skip to main content

running_process_platform_internal/platform_linux/
ape.rs

1//! linux Actually Portable Executable launch mechanics: the default loader
2//! cache directories, the private-directory check installs rely on, and the
3//! sealed `memfd` fallback used when no cache directory is usable (read-only
4//! home, `noexec` `/tmp`, no runtime dir). The memfd is held open for the life
5//! of the process and exec'd via `/proc/self/fd/N`.
6
7use std::collections::HashMap;
8use std::ffi::CString;
9use std::fs::File;
10use std::io::{self, Write};
11use std::os::fd::{AsRawFd, FromRawFd};
12use std::os::unix::ffi::OsStrExt;
13use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
14use std::os::unix::process::CommandExt;
15use std::path::{Path, PathBuf};
16use std::sync::Mutex;
17
18/// An APE image is not a native Linux executable.
19pub const APE_NEEDS_LOADER: bool = true;
20
21/// Shell the kernel's `ENOEXEC` convention hands an unrecognized image to.
22pub const APE_SHELL: &str = "/bin/sh";
23
24/// Where Cosmopolitan's install instructions place a system-wide loader.
25pub const APE_SYSTEM_LOADERS: &[&str] = &["/usr/bin/ape", "/usr/local/bin/ape"];
26
27/// Which embedded loader this host runs: the Linux static ELF.
28pub const APE_LOADER_HOST: crate::platform::ape::LoaderHost = crate::platform::ape::LoaderHost::Linux;
29
30/// Whether this libc's `execvp` runs an `ENOEXEC` image with the shell, as
31/// POSIX requires. glibc does; musl does not.
32#[cfg(target_env = "musl")]
33pub const APE_EXECVP_SHELL_FALLBACK: bool = false;
34/// Whether this libc's `execvp` runs an `ENOEXEC` image with the shell, as
35/// POSIX requires. glibc does; musl does not.
36#[cfg(not(target_env = "musl"))]
37pub const APE_EXECVP_SHELL_FALLBACK: bool = true;
38
39/// The kernel refused the image's format.
40pub fn is_exec_format_error(error: &io::Error) -> bool {
41    error.raw_os_error() == Some(libc::ENOEXEC)
42}
43
44/// Whether metadata carries any execute permission bit.
45pub fn is_executable(metadata: &std::fs::Metadata) -> bool {
46    metadata.permissions().mode() & 0o111 != 0
47}
48
49/// Make an extracted loader executable by its owner and readable by others.
50pub fn mark_executable(path: &Path) -> io::Result<()> {
51    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755))
52}
53
54/// Make the next spawn fork and `execvp` instead of `posix_spawn`.
55///
56/// glibc's `posix_spawn` reports `ENOEXEC` as is; its `execvp` retries the
57/// image with `/bin/sh`. Any `pre_exec` hook makes std take the second path,
58/// and this one does nothing else.
59pub fn route_through_execvp(command: &mut std::process::Command) {
60    // SAFETY: the hook touches no state at all, so it is async-signal-safe.
61    unsafe {
62        command.pre_exec(|| Ok(()));
63    }
64}
65
66/// [`route_through_execvp`] for a Tokio command.
67#[cfg(feature = "async-process")]
68pub fn route_tokio_through_execvp(command: &mut tokio::process::Command) {
69    // SAFETY: the hook touches no state at all, so it is async-signal-safe.
70    unsafe {
71        command.pre_exec(|| Ok(()));
72    }
73}
74
75/// Default directories for extracted loaders, most durable first.
76pub fn default_loader_dirs() -> Vec<PathBuf> {
77    let set = |value: Option<std::ffi::OsString>| value.filter(|value| !value.is_empty());
78    let mut dirs = Vec::new();
79    let cache = set(crate::env_vars::XDG_CACHE_HOME.os())
80        .map(PathBuf::from)
81        .or_else(|| set(crate::env_vars::HOME.os()).map(|home| PathBuf::from(home).join(".cache")));
82    if let Some(cache) = cache {
83        dirs.push(cache.join("running-process").join("ape"));
84    }
85    if let Some(runtime) = set(crate::env_vars::XDG_RUNTIME_DIR.os()) {
86        dirs.push(PathBuf::from(runtime).join("running-process").join("ape"));
87    }
88    // SAFETY: geteuid has no preconditions and cannot fail.
89    let uid = unsafe { libc::geteuid() };
90    dirs.push(std::env::temp_dir().join(format!("running-process-ape-{uid}")));
91    dirs
92}
93
94/// Whether `dir` may hold an executable this crate installs: create it (mode
95/// 0700) if needed, then accept it only when it is a real directory owned by
96/// the effective user with no group/other write access -- so no other account
97/// can plant or swap a file in it -- on a mount that allows exec.
98pub fn private_exec_dir(dir: &Path) -> bool {
99    let _ = std::fs::DirBuilder::new()
100        .recursive(true)
101        .mode(0o700)
102        .create(dir);
103    // SAFETY: geteuid has no preconditions and cannot fail.
104    let uid = unsafe { libc::geteuid() };
105    let private = std::fs::symlink_metadata(dir).is_ok_and(|meta| {
106        meta.file_type().is_dir() && meta.uid() == uid && meta.mode() & 0o022 == 0
107    });
108    private && mount_allows_exec(dir)
109}
110
111fn mount_allows_exec(dir: &Path) -> bool {
112    let Ok(path) = CString::new(dir.as_os_str().as_bytes()) else {
113        return false;
114    };
115    let mut stats = std::mem::MaybeUninit::<libc::statvfs>::uninit();
116    // SAFETY: `path` is NUL-terminated and `stats` points to writable storage.
117    if unsafe { libc::statvfs(path.as_ptr(), stats.as_mut_ptr()) } != 0 {
118        return false;
119    }
120    // SAFETY: a successful statvfs initialized the complete output structure.
121    let stats = unsafe { stats.assume_init() };
122    stats.f_flag & libc::ST_NOEXEC == 0
123}
124
125/// Last-resort executable with no filesystem home: a sealed memfd exec'd via
126/// `/proc/self/fd/N`. Valid only for direct children of this process.
127pub fn anonymous_executable(bytes: &[u8], name: &str) -> Option<PathBuf> {
128    memfd_loader(bytes, name)
129}
130
131/// Sealed memfds kept open for the process lifetime, keyed by loader name.
132static MEMFDS: Mutex<Option<HashMap<String, File>>> = Mutex::new(None);
133
134fn memfd_loader(bytes: &[u8], name: &str) -> Option<PathBuf> {
135    let mut guard = MEMFDS.lock().unwrap_or_else(|error| error.into_inner());
136    let fds = guard.get_or_insert_with(HashMap::new);
137    if let Some(file) = fds.get(name) {
138        return Some(fd_path(file));
139    }
140    let file = {
141        // The memfd is writable until sealed; keep it out of forked children.
142        let _fork = crate::platform::ape::exclusive_fork_guard();
143        create_sealed_memfd(bytes, name)?
144    };
145    let path = fd_path(&file);
146    // The child resolves this path before close-on-exec runs; without /proc
147    // it cannot, so refuse rather than hand out a dead path.
148    if !path.exists() {
149        return None;
150    }
151    fds.insert(name.to_owned(), file);
152    Some(path)
153}
154
155fn create_sealed_memfd(bytes: &[u8], name: &str) -> Option<File> {
156    let cname = CString::new(name).ok()?;
157    let base = libc::MFD_CLOEXEC | libc::MFD_ALLOW_SEALING;
158    // MFD_EXEC (Linux 6.3+) keeps the memfd executable under
159    // `vm.memfd_noexec=1`; older kernels reject the flag with EINVAL.
160    let fd = [base | libc::MFD_EXEC, base].into_iter().find_map(|flags| {
161        // Raw syscall, not the libc wrapper: a binary linked against an old
162        // glibc (2.17 for manylinux2014 wheels) predates `memfd_create()`,
163        // which glibc added in 2.27.
164        // SAFETY: `cname` is NUL-terminated; the flags are valid memfd flags.
165        let fd = unsafe { libc::syscall(libc::SYS_memfd_create, cname.as_ptr(), flags) };
166        i32::try_from(fd).ok().filter(|fd| *fd >= 0)
167    })?;
168    // SAFETY: `fd` is a freshly created descriptor owned by nobody else.
169    let mut file = unsafe { File::from_raw_fd(fd) };
170    file.write_all(bytes).ok()?;
171    file.set_permissions(std::fs::Permissions::from_mode(0o500))
172        .ok()?;
173    let seals = libc::F_SEAL_SHRINK | libc::F_SEAL_GROW | libc::F_SEAL_WRITE | libc::F_SEAL_SEAL;
174    // SAFETY: `file` owns a valid memfd created with MFD_ALLOW_SEALING.
175    if unsafe { libc::fcntl(file.as_raw_fd(), libc::F_ADD_SEALS, seals) } != 0 {
176        return None;
177    }
178    Some(file)
179}
180
181fn fd_path(file: &File) -> PathBuf {
182    PathBuf::from(format!("/proc/self/fd/{}", file.as_raw_fd()))
183}
184
185#[cfg(test)]
186#[path = "ape_tests.rs"]
187mod tests;