1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright 2022 Red Hat, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::fs::{File, OpenOptions};
use std::io::{Error, ErrorKind, Write};
use std::os::unix::fs::{MetadataExt, OpenOptionsExt};
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::Path;
use std::result::Result;
use std::{fs, io, process};

fn try_lock_file(file: &File) -> Result<(), Error> {
    // Safe because 'file' must exist and we check the return value.
    let file_fd = file.as_raw_fd();
    let ret = unsafe { libc::flock(file_fd, libc::LOCK_EX | libc::LOCK_NB) };
    if ret == -1 {
        return Err(Error::last_os_error());
    }
    Ok(())
}

pub fn write_pid_file(pid_file_name: &Path) -> std::result::Result<File, std::io::Error> {
    let mut pid_file = loop {
        let file = OpenOptions::new()
            .mode(libc::S_IRUSR | libc::S_IWUSR)
            .custom_flags(libc::O_CLOEXEC)
            .write(true)
            .create(true)
            .open(pid_file_name)?;

        try_lock_file(&file)?;

        // Let's make sure the file we locked still exists in the filesystem.
        let locked = file.metadata()?.ino();
        let current = match fs::metadata(pid_file_name) {
            Ok(stat) => stat.ino(),
            _ => continue, // the pid file got removed or some error happened, try again.
        };

        if locked == current {
            break file; // lock successfully acquired.
        }
        // the file changed, other process is racing with us, so try again.
    };

    let pid = format!("{}\n", process::id());
    pid_file.write_all(pid.as_bytes())?;

    Ok(pid_file)
}

unsafe fn pidfd_open(pid: libc::pid_t, flags: libc::c_uint) -> libc::c_int {
    libc::syscall(libc::SYS_pidfd_open, pid, flags) as libc::c_int
}

/// Helper function to create a process and sets the parent process
/// death signal SIGTERM
pub fn sfork() -> io::Result<i32> {
    let cur_pid = unsafe { libc::getpid() };

    // We use pidfd_open(2) to check the parent's pid because if the
    // child is created inside a pid namespace, getppid(2) will always
    // return 0
    let parent_pidfd = unsafe { pidfd_open(cur_pid, 0) };
    if parent_pidfd == -1 {
        return Err(Error::last_os_error());
    }

    // We wrap the parent PID file descriptor in a File object to ensure that is
    // auto-closed when it goes out of scope. But, since nothing can be read, using read(2),
    // from a PID file descriptor returned by pidfd_open(2) (it fails with EINVAL), we
    // use a new type PidFd to prevent using the File's methods directly, and in the hope
    // that whoever wants to do so will read this first.
    // This is a temporary solution until OwnedFd is stabilized.
    struct PidFd(File);
    let _pidfd = unsafe { PidFd(File::from_raw_fd(parent_pidfd)) };

    let child_pid = unsafe { libc::fork() };
    if child_pid == -1 {
        return Err(Error::last_os_error());
    }

    if child_pid == 0 {
        // Request to receive SIGTERM on parent's death.
        let ret = unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) };
        assert_eq!(ret, 0); // This shouldn't fail because libc::SIGTERM is a valid signal number

        // Check if the original parent died before libc::prctl() was called
        let mut pollfds = libc::pollfd {
            fd: parent_pidfd,
            events: libc::POLLIN,
            revents: 0,
        };
        let num_fds = unsafe { libc::poll(&mut pollfds, 1, 0) };
        if num_fds == -1 {
            return Err(io::Error::last_os_error());
        }
        if num_fds != 0 {
            // The original parent died
            return Err(Error::new(
                ErrorKind::Other,
                "Parent process died unexpectedly",
            ));
        }
    }
    Ok(child_pid)
}

pub fn wait_for_child(pid: i32) -> ! {
    // Drop all capabilities, since the parent doesn't require any
    // capabilities, as it'd be just waiting for the child to exit.
    capng::clear(capng::Set::BOTH);
    if let Err(e) = capng::apply(capng::Set::BOTH) {
        // Don't exit the process here since we already have a child.
        error!("warning: can't apply the parent capabilities: {}", e);
    }

    let mut status = 0;
    // On success, `libc::waitpid()` returns the PID of the child.
    if unsafe { libc::waitpid(pid, &mut status, 0) } != pid {
        error!("Error during waitpid()");
        process::exit(1);
    }

    let exit_code = if libc::WIFEXITED(status) {
        libc::WEXITSTATUS(status)
    } else if libc::WIFSIGNALED(status) {
        let signal = libc::WTERMSIG(status);
        error!("Child process terminated by signal {}", signal);
        -signal
    } else {
        error!("Unexpected waitpid status: {:#X}", status);
        libc::EXIT_FAILURE
    };

    process::exit(exit_code);
}

/// Add a capability to the effective set
/// # Errors
/// An error variant will be returned:
/// - if the input string does not match the name, without the 'CAP_' prefix,
/// of any of the capability defined in `linux/capabiliy.h`.
/// - if `capng::get_caps_process()` cannot get  the  capabilities  and bounding set of the process.
/// - if `capng::update()` fails to update the internal posix capabilities settings.
/// - if `capng::apply()` fails to transfer the specified internal posix capabilities
///   settings to the kernel.
pub fn add_cap_to_eff(cap_name: &str) -> capng::Result<()> {
    use capng::{Action, CUpdate, Set, Type};
    let cap = capng::name_to_capability(cap_name)?;
    capng::get_caps_process()?;

    let req = vec![CUpdate {
        action: Action::ADD,
        cap_type: Type::EFFECTIVE,
        capability: cap,
    }];

    capng::update(req)?;
    capng::apply(Set::CAPS)?;

    Ok(())
}