Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 147 additions & 26 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub use self::noop::{Args, Config, Process};
mod unix {
use std::io;
use std::env::set_current_dir;
use std::ffi::{CStr, CString};
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::os::fd::{AsFd, AsRawFd};
Expand All @@ -27,7 +28,7 @@ mod unix {
use nix::sys::stat::umask;
use nix::unistd::{Gid, Group, Uid, User};
use nix::unistd::{
close, chroot, dup2, fork, getpid, setgid, setsid, setuid,
close, chroot, dup2, fork, getpid, setsid,
};
use serde::{Deserialize, Serialize};
use crate::config::{ConfigFile, ConfigPath};
Expand Down Expand Up @@ -143,27 +144,132 @@ mod unix {
}
}

if let Some(user) = self.config.user.as_ref() {
if let Err(err) = setuid(user.uid) {
self.set_user_and_group()?;

self.write_pid_file()?;

Ok(())
}

/// Changes the user and group IDs.
fn set_user_and_group(&self) -> Result<(), Failed> {
// Unfortunately, this isn’t quite as portable as we want it to
// be as most of the function we use are not available on some
// platforms. Instead of copying the cfg attributes from the nix
// crate, we define fallback functions and overwrite their symbol
// if possible using a glob import.
//
// For setting uid and gid, we need to cascase: Use `setresuid`
// if available, otherwise use `setreuid` if available, otherwise
// use `setuid`; analogous for gid. We achieve this by having
// the fallback call the next step which may itself be a fallback.

/// Dummy fallback function for `nix::unistd::initgroups`.
#[allow(dead_code)]
fn initgroups(
_user: &CStr, _group: Gid
) -> Result<(), nix::errno::Errno> {
Ok(())
}

/// Fallback function for `nix::unistd::setresgid`.
#[allow(dead_code)]
fn setresgid(
rgid: Gid, egid: Gid, _sgid: Gid
) -> Result<(), nix::errno::Errno> {
use nix::libc::{c_int, gid_t};

#[allow(dead_code)]
unsafe fn setregid(rgid: gid_t, _egid: gid_t) -> c_int {
unsafe { nix::libc::setgid(rgid) }
}

{
#[allow(unused_imports)]
use nix::libc::*;

if unsafe { setregid(rgid.as_raw(), egid.as_raw()) } != 0 {
return Err(nix::errno::Errno::last());
}
}

Ok(())
}

/// Fallback function for `nix::unistd::setresuid`.
#[allow(dead_code)]
fn setresuid(
ruid: Uid, euid: Uid, _suid: Uid
) -> Result<(), nix::errno::Errno> {
use nix::libc::{c_int, uid_t};

#[allow(dead_code)]
unsafe fn setreuid(ruid: uid_t, _euid: uid_t) -> c_int {
unsafe { nix::libc::setuid(ruid) }
}

{
#[allow(unused_imports)]
use nix::libc::*;

if unsafe { setreuid(ruid.as_raw(), euid.as_raw()) } != 0 {
return Err(nix::errno::Errno::last());
}
}

Ok(())
}

let Some(user) = self.config.user.as_ref() else {
return Ok(())
};

// If we don’t have an explicit group, we use the user’s group.
let gid = self.config.group.as_ref().map(|g| {
g.gid
}).unwrap_or_else(|| {
user.gid
});

// Let the system load the supplemental groups for the user.
{
#[allow(unused_imports)]
use nix::unistd::*;

initgroups(&user.c_name, gid).map_err(|err| {
error!(
"Fatal: failed to set user '{}': {}",
"failed to initgroups {}: {}",
user.name, err
);
return Err(Failed)
}
Failed
})?;
}

if let Some(group) = self.config.group.as_ref() {
if let Err(err) = setgid(group.gid) {
// Set the group ID.
{
#[allow(unused_imports)]
use nix::unistd::*;

setresgid(gid, gid, gid).map_err(|err| {
error!(
"Fatal: failed to set group '{}': {}",
group.name, err
"failed to set group ID: {err}"
);
return Err(Failed)
}
Failed
})?;
}

self.write_pid_file()?;
// Set the user ID.
{
#[allow(unused_imports)]
use nix::unistd::*;

setresuid(user.uid, user.uid, user.uid).map_err(|err| {
error!(
"failed to set user ID: {err}"
);
Failed
})?;
}

Ok(())
}
Expand Down Expand Up @@ -404,22 +510,39 @@ mod unix {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(try_from = "String", into = "String", expecting = "a user name")]
struct UserId {
/// The numerical user ID.
uid: Uid,

/// The user name.
///
/// We keep this information so we can produce the actual config.
/// This is used for error reporting.
name: String,

/// The user name as a C string.
///
/// This is used internally. We keep both the string and C string
/// versions because conversion can cause errors and we want to catch
/// those early.
c_name: CString,

/// The numerical user ID.
uid: Uid,

/// The numerical group ID of the user.
gid: Gid,

}

impl TryFrom<String> for UserId {
type Error = String;

fn try_from(name: String) -> Result<Self, Self::Error> {
let Ok(c_name) = CString::new(name.clone()) else {
return Err(format!("invalid user name '{name}'"))
};
match User::from_name(&name) {
Ok(Some(user)) => {
Ok(UserId { uid: user.uid, name })
Ok(UserId {
name, c_name,
gid: user.gid, uid: user.uid
})
}
Ok(None) => {
Err(format!("unknown user '{name}'"))
Expand Down Expand Up @@ -452,13 +575,11 @@ mod unix {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(try_from = "String", into = "String", expecting = "a user name")]
struct GroupId {
/// The numerical user ID.
gid: Gid,

/// The user name.
///
/// We keep this information so we can produce the actual config.
/// The group name.
name: String,

/// The numerical group ID.
gid: Gid,
}

impl TryFrom<String> for GroupId {
Expand All @@ -470,10 +591,10 @@ mod unix {
Ok(GroupId { gid: group.gid, name })
}
Ok(None) => {
Err(format!("unknown user '{name}'"))
Err(format!("unknown group '{name}'"))
}
Err(err) => {
Err(format!("failed to resolve user '{name}': {err}"))
Err(format!("failed to resolve group '{name}': {err}"))
}
}
}
Expand Down
Loading