cloudfox-coreshift-core 2.5.1

Low-level Linux and Android systems primitives for CoreShift (CloudFox)
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/

//! `fork`/`execve` backend.
//!
//! Owns the child setup protocol (`ChildSetupOp`, the error pipe handshake),
//! fd-policy handling in the child, and the `fork` launch path used when
//! [`SpawnBackend::Fork`](crate::spawn::SpawnBackend::Fork) is selected.

use std::os::unix::io::RawFd;

use crate::CoreError;
use crate::error::syscall_ret;
use crate::signal::SignalRuntime;
use libc::{c_char, pid_t};

use super::exec::ExecArgv;
use super::{Pipes, SpawnDrain, SpawnFdPolicy, SpawnOptions, environ, errno, make_cloexec_pipe};

#[repr(u8)]
#[derive(Clone, Copy)]
enum ChildSetupOp {
    DupStdin = 1,
    DupStdout = 2,
    DupStderr = 3,
    Setsid = 4,
    Chdir = 5,
    Setpgid = 6,
    SignalMask = 7,
    Execve = 8,
}

impl ChildSetupOp {
    fn as_str(self) -> &'static str {
        match self {
            Self::DupStdin => "fork child dup2 stdin",
            Self::DupStdout => "fork child dup2 stdout",
            Self::DupStderr => "fork child dup2 stderr",
            Self::Setsid => "fork child setsid",
            Self::Chdir => "fork child chdir",
            Self::Setpgid => "fork child setpgid",
            Self::SignalMask => "fork child signal setup",
            Self::Execve => "fork child execve",
        }
    }

    fn from_u8(value: u8) -> Self {
        match value {
            1 => Self::DupStdin,
            2 => Self::DupStdout,
            3 => Self::DupStderr,
            4 => Self::Setsid,
            5 => Self::Chdir,
            6 => Self::Setpgid,
            7 => Self::SignalMask,
            _ => Self::Execve,
        }
    }
}

unsafe fn report_child_setup_error(fd: RawFd, op: ChildSetupOp, code: i32) -> ! {
    let mut msg = [0u8; 5];
    msg[..4].copy_from_slice(&code.to_ne_bytes());
    msg[4] = op as u8;
    let mut written = 0;
    while written < msg.len() {
        let n = unsafe {
            libc::write(
                fd,
                msg[written..].as_ptr().cast::<libc::c_void>(),
                msg.len() - written,
            )
        };
        if n <= 0 {
            break;
        }
        written += n as usize;
    }
    unsafe {
        libc::_exit(127);
    }
}

fn read_child_setup_error(fd: RawFd) -> Result<Option<CoreError>, CoreError> {
    let mut msg = [0u8; 5];
    let mut read_len = 0;
    loop {
        let n = unsafe {
            libc::read(
                fd,
                msg[read_len..].as_mut_ptr().cast::<libc::c_void>(),
                msg.len() - read_len,
            )
        };
        if n == 0 {
            return Ok(None);
        }
        if n < 0 {
            let code = errno();
            if code == libc::EINTR {
                continue;
            }
            return Err(CoreError::sys(code, "read fork child setup error"));
        }
        read_len += n as usize;
        if read_len == msg.len() {
            let code = i32::from_ne_bytes([msg[0], msg[1], msg[2], msg[3]]);
            return Ok(Some(CoreError::sys(
                code,
                ChildSetupOp::from_u8(msg[4]).as_str(),
            )));
        }
    }
}

fn collect_required_pipe_fds(pipes: &Pipes) -> Vec<RawFd> {
    let mut fds = Vec::new();
    if let Some(fd) = &pipes.stdin_r {
        fds.push(fd.raw());
    }
    if let Some(fd) = &pipes.stdin_w {
        fds.push(fd.raw());
    }
    if let Some(fd) = &pipes.stdout_r {
        fds.push(fd.raw());
    }
    if let Some(fd) = &pipes.stdout_w {
        fds.push(fd.raw());
    }
    if let Some(fd) = &pipes.stderr_r {
        fds.push(fd.raw());
    }
    if let Some(fd) = &pipes.stderr_w {
        fds.push(fd.raw());
    }
    fds
}

fn collect_open_fds_for_child_policy(policy: &SpawnFdPolicy) -> Result<Vec<RawFd>, CoreError> {
    match policy {
        SpawnFdPolicy::CloexecOnly => Ok(Vec::new()),
        SpawnFdPolicy::CloseFrom3 | SpawnFdPolicy::Allowlist(_) => {
            let dir_fd = unsafe {
                libc::open(
                    c"/proc/self/fd".as_ptr(),
                    libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
                )
            };
            if dir_fd < 0 {
                return Err(CoreError::sys(errno(), "open /proc/self/fd"));
            }

            let dir = unsafe { libc::fdopendir(dir_fd) };
            if dir.is_null() {
                let code = errno();
                unsafe {
                    libc::close(dir_fd);
                }
                return Err(CoreError::sys(code, "fdopendir /proc/self/fd"));
            }

            let mut open_fds = Vec::new();
            loop {
                let entry = unsafe { libc::readdir(dir) };
                if entry.is_null() {
                    break;
                }
                let name = unsafe { std::ffi::CStr::from_ptr((*entry).d_name.as_ptr()) };
                if let Ok(s) = name.to_str()
                    && let Ok(fd) = s.parse::<RawFd>()
                    && fd != dir_fd
                {
                    open_fds.push(fd);
                }
            }
            unsafe {
                libc::closedir(dir);
            }
            Ok(open_fds)
        }
    }
}

fn close_child_fds_for_policy(policy: &SpawnFdPolicy, required_fds: &[RawFd], open_fds: &[RawFd]) {
    match policy {
        SpawnFdPolicy::CloexecOnly => {}
        SpawnFdPolicy::CloseFrom3 | SpawnFdPolicy::Allowlist(_) => {
            for &fd in open_fds {
                if fd > 2
                    && !required_fds.contains(&fd)
                    && !matches!(policy, SpawnFdPolicy::Allowlist(allowlist) if allowlist.contains(&fd))
                {
                    unsafe {
                        libc::close(fd);
                    }
                }
            }
        }
    }
}

pub(super) fn spawn_fork_internal(opts: SpawnOptions) -> Result<(pid_t, SpawnDrain), CoreError> {
    let mut pipes = Pipes::new(
        opts.stdin.as_deref(),
        opts.capture_stdout,
        opts.capture_stderr,
    )?;

    let exe_ptr = match &opts.ctx.argv {
        ExecArgv::Dynamic(v) => v[0].as_ptr(),
    };

    let argv = opts.ctx.get_argv_ptrs();
    let envp = opts.ctx.get_envp_ptrs();
    let cwd_cstr = &opts.ctx.cwd;
    let (child_error_r, child_error_w) = make_cloexec_pipe()?;
    let mut required_fds = collect_required_pipe_fds(&pipes);
    required_fds.push(child_error_w);
    let open_fds = collect_open_fds_for_child_policy(&opts.fd_policy)?;

    let pid = unsafe { libc::fork() };

    if pid < 0 {
        unsafe {
            libc::close(child_error_r);
            libc::close(child_error_w);
        }
        pipes.close_all();
        syscall_ret(-1, "fork")?;
    }

    if pid == 0 {
        // Child
        unsafe {
            libc::close(child_error_r);
        }

        // dup stdin
        if let (Some(r), Some(_)) = (&pipes.stdin_r, &pipes.stdin_w) {
            unsafe {
                if libc::dup2(r.raw(), 0) < 0 {
                    report_child_setup_error(child_error_w, ChildSetupOp::DupStdin, errno());
                }
            }
        }

        // dup stdout
        if let (Some(_), Some(w)) = (&pipes.stdout_r, &pipes.stdout_w) {
            unsafe {
                if libc::dup2(w.raw(), 1) < 0 {
                    report_child_setup_error(child_error_w, ChildSetupOp::DupStdout, errno());
                }
            }
        }

        // dup stderr
        if let (Some(_), Some(w)) = (&pipes.stderr_r, &pipes.stderr_w) {
            unsafe {
                if libc::dup2(w.raw(), 2) < 0 {
                    report_child_setup_error(child_error_w, ChildSetupOp::DupStderr, errno());
                }
            }
        }

        // SAFETY: Close all pipe FDs in child before exec, except the ones duped to 0,1,2.
        pipes.close_all();

        close_child_fds_for_policy(&opts.fd_policy, &required_fds, &open_fds);

        // setsid
        if opts.pgroup.isolated {
            // SAFETY: safe to call setsid in child.
            unsafe {
                if libc::setsid() < 0 {
                    report_child_setup_error(child_error_w, ChildSetupOp::Setsid, errno());
                }
            }
        }

        // chdir
        if let Some(cwd) = cwd_cstr {
            // SAFETY: cwd is a valid null-terminated CString.
            unsafe {
                if libc::chdir(cwd.as_ptr()) != 0 {
                    report_child_setup_error(child_error_w, ChildSetupOp::Chdir, errno());
                }
            }
        }

        // setpgid
        if let Some(pg) = opts.pgroup.leader {
            // SAFETY: valid pgroup.
            unsafe {
                if libc::setpgid(0, pg) < 0 {
                    report_child_setup_error(child_error_w, ChildSetupOp::Setpgid, errno());
                }
            }
        }

        let envp_ptr = envp.as_ref().map_or_else(
            || unsafe { environ as *const *mut c_char },
            |e: &Vec<*mut c_char>| e.as_ptr(),
        );

        // unblock signals and reset SIGPIPE
        // SAFETY: valid signal mask array manipulation
        if let Err(err) = SignalRuntime::unblock_all() {
            unsafe {
                report_child_setup_error(
                    child_error_w,
                    ChildSetupOp::SignalMask,
                    err.raw_os_error().unwrap_or(libc::EIO),
                );
            }
        }
        if let Err(err) = SignalRuntime::reset_default(libc::SIGPIPE) {
            unsafe {
                report_child_setup_error(
                    child_error_w,
                    ChildSetupOp::SignalMask,
                    err.raw_os_error().unwrap_or(libc::EIO),
                );
            }
        }

        // exec
        // SAFETY: exe_ptr is null-terminated. argv and envp_ptr are valid null-terminated arrays.
        unsafe {
            libc::execve(
                exe_ptr,
                argv.as_ptr() as *const *const _,
                envp_ptr as *const *const _,
            );
            report_child_setup_error(child_error_w, ChildSetupOp::Execve, errno());
        }
    }

    // Parent
    unsafe {
        libc::close(child_error_w);
    }
    match read_child_setup_error(child_error_r) {
        Ok(Some(err)) => {
            unsafe {
                libc::close(child_error_r);
                let mut status = 0;
                let _ = libc::waitpid(pid, &mut status, 0);
            }
            pipes.close_all();
            return Err(err);
        }
        Ok(None) => {}
        Err(err) => {
            unsafe {
                libc::close(child_error_r);
            }
            pipes.close_all();
            return Err(err);
        }
    }
    unsafe {
        libc::close(child_error_r);
    }
    drop(pipes.stdin_r.take());
    drop(pipes.stdout_w.take());
    drop(pipes.stderr_w.take());

    let drain = crate::io::DrainState::new(
        pipes.stdin_w.take().filter(|_| opts.stdin.is_some()),
        opts.stdin,
        pipes.stdout_r.take(),
        pipes.stderr_r.take(),
        opts.max_output,
        opts.early_exit,
    )?;

    Ok((pid, drain))
}