privdrop-0.5.4/.cargo_vcs_info.json0000644000000001360000000000100127070ustar { "git": { "sha1": "88ae0160f75f69f4f349fd3b96eece46be5ddae3" }, "path_in_vcs": "" }privdrop-0.5.4/.github/dependabot.yml000064400000000000000000000002211046102023000156620ustar 00000000000000version: 2 updates: - package-ecosystem: cargo directory: "/" schedule: interval: daily time: "04:00" open-pull-requests-limit: 10 privdrop-0.5.4/.gitignore000064400000000000000000000000221046102023000134610ustar 00000000000000target Cargo.lock privdrop-0.5.4/.travis.yml000064400000000000000000000003111046102023000136030ustar 00000000000000sudo: required language: rust rust: - nightly - beta - stable os: - linux - osx script: - sudo mkdir -p /var/empty - sudo chmod 555 /var/empty - sudo $( which cargo ) test --verbose privdrop-0.5.4/Cargo.toml0000644000000017310000000000100107070ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "privdrop" version = "0.5.4" authors = ["Frank Denis "] description = "A simple crate to drop privileges" homepage = "https://github.com/jedisct1/rust-privdrop" readme = "README.md" keywords = [ "privileges", "drop", ] categories = ["os::unix-apis"] license = "ISC" repository = "https://github.com/jedisct1/rust-privdrop" [dependencies.libc] version = "0.2" [dependencies.nix] version = "0.26" [badges.travis-ci] repository = "jedisct1/rust-privdrop" privdrop-0.5.4/Cargo.toml.orig000064400000000000000000000007141046102023000143700ustar 00000000000000[package] name = "privdrop" version = "0.5.4" description = "A simple crate to drop privileges" authors = ["Frank Denis "] keywords = ["privileges", "drop"] license = "ISC" homepage = "https://github.com/jedisct1/rust-privdrop" repository = "https://github.com/jedisct1/rust-privdrop" categories = ["os::unix-apis"] edition = "2018" [badges] travis-ci = { repository = "jedisct1/rust-privdrop" } [dependencies] libc = "0.2" nix = "0.26" privdrop-0.5.4/LICENSE000064400000000000000000000013661046102023000125120ustar 00000000000000Copyright (c) 2016-2023 Frank Denis Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. privdrop-0.5.4/README.md000064400000000000000000000001361046102023000127560ustar 00000000000000# privdrop A simple crate to drop privileges. [API documentation](https://docs.rs/privdrop) privdrop-0.5.4/src/errors.rs000064400000000000000000000022651046102023000141550ustar 00000000000000use std::error::Error; use std::fmt; #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum ErrorKind { SysError, } #[derive(Debug)] enum ErrorRepr { FromNix(nix::Error), WithDescription(ErrorKind, &'static str), } #[derive(Debug)] pub struct PrivDropError { repr: ErrorRepr, } impl Error for PrivDropError { fn cause(&self) -> Option<&dyn Error> { match self.repr { ErrorRepr::FromNix(ref e) => Some(e as &dyn Error), _ => None, } } } impl fmt::Display for PrivDropError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self.repr { ErrorRepr::FromNix(ref e) => e.fmt(f), ErrorRepr::WithDescription(_, description) => description.fmt(f), } } } impl From for PrivDropError { fn from(e: nix::Error) -> PrivDropError { PrivDropError { repr: ErrorRepr::FromNix(e), } } } impl From<(ErrorKind, &'static str)> for PrivDropError { fn from((kind, description): (ErrorKind, &'static str)) -> PrivDropError { PrivDropError { repr: ErrorRepr::WithDescription(kind, description), } } } privdrop-0.5.4/src/lib.rs000064400000000000000000000001771046102023000134070ustar 00000000000000pub use self::errors::*; pub use self::privdrop::*; mod errors; mod privdrop; pub mod reexports { pub use {libc, nix}; } privdrop-0.5.4/src/privdrop.rs000064400000000000000000000237511046102023000145110ustar 00000000000000use std::ffi::{CString, OsStr, OsString}; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use nix::unistd; use super::errors::*; #[test] fn test_privdrop() { if unistd::geteuid().is_root() { PrivDrop::default() .chroot("/var/empty") .user("nobody") .apply() .unwrap_or_else(|e| panic!("Failed to drop privileges: {}", e)); } else { eprintln!("Test was skipped because it needs to be run as root."); } } /// `PrivDrop` structure /// /// # Example /// ```ignore /// privdrop::PrivDrop::default() /// .chroot("/var/empty") /// .user("nobody") /// .apply() /// .unwrap_or_else(|e| { panic!("Failed to drop privileges: {}", e) }); /// ``` #[derive(Default, Clone, Debug)] pub struct PrivDrop { chroot: Option, user: Option, group: Option, group_list: Option>, include_default_supplementary_groups: bool, fallback_to_ids_if_names_are_numeric: bool, } #[derive(Default, Clone, Debug)] struct UserIds { uid: Option, gid: Option, group_list: Option>, } impl PrivDrop { /// chroot() to a specific directory before switching to a non-root user pub fn chroot>(mut self, path: T) -> Self { self.chroot = Some(path.as_ref().to_owned()); self } /// Set the name of a user to switch to pub fn user>(mut self, user: S) -> Self { self.user = Some(user.as_ref().to_owned()); self } /// Set a group name to switch to, if different from the primary group of the user pub fn group>(mut self, group: S) -> Self { self.group = Some(group.as_ref().to_owned()); self } /// Include default supplementary groups pub fn include_default_supplementary_groups(mut self) -> Self { self.include_default_supplementary_groups = true; self } /// If a name is not found, try to parse it as a numeric identifier pub fn fallback_to_ids_if_names_are_numeric(mut self) -> Self { self.fallback_to_ids_if_names_are_numeric = true; self } /// Set the full list of groups to switch to pub fn group_list>(mut self, group_list: &[S]) -> Self { self.group_list = Some(group_list.iter().map(|x| x.as_ref().to_owned()).collect()); self } /// Apply the changes pub fn apply(self) -> Result<(), PrivDropError> { Self::preload()?; let ids = self.lookup_ids()?; self.do_chroot()?.do_idchange(ids)?; Ok(()) } fn preload() -> Result<(), PrivDropError> { let c_locale = CString::new("C").unwrap(); unsafe { libc::strerror(1); libc::setlocale(libc::LC_CTYPE, c_locale.as_ptr()); libc::setlocale(libc::LC_COLLATE, c_locale.as_ptr()); let mut now: libc::time_t = 0; libc::time(&mut now); libc::localtime(&now); } Ok(()) } fn uidcheck() -> Result<(), PrivDropError> { if !unistd::geteuid().is_root() { Err(PrivDropError::from(( ErrorKind::SysError, "Starting this application requires root privileges", ))) } else { Ok(()) } } fn do_chroot(mut self) -> Result { if let Some(chroot) = self.chroot.take() { Self::uidcheck()?; unistd::chdir(&chroot)?; unistd::chroot(&chroot)?; unistd::chdir("/")? } Ok(self) } fn lookup_user( user: &OsStr, fallback_to_ids_if_names_are_numeric: bool, ) -> Result { let username = CString::new(user.as_bytes()) .map_err(|_| PrivDropError::from((ErrorKind::SysError, "Invalid username")))?; let mut pwd = unsafe { std::mem::zeroed::() }; let mut pwbuf = vec![0; 4096]; let mut pwent = std::ptr::null_mut::(); let ret = unsafe { libc::getpwnam_r( username.as_ptr(), &mut pwd, pwbuf.as_mut_ptr(), pwbuf.len(), &mut pwent, ) }; if ret != 0 || pwent.is_null() { if !fallback_to_ids_if_names_are_numeric { return Err(PrivDropError::from((ErrorKind::SysError, "User not found"))); } let user_str = user.to_str().ok_or_else(|| { PrivDropError::from(( ErrorKind::SysError, "User not found and username is not a valid number", )) })?; let uid = user_str.parse().map_err(|_| { PrivDropError::from(( ErrorKind::SysError, "User not found and username is not a valid number", )) })?; return Ok(UserIds { uid: Some(uid), gid: None, group_list: None, }); } let uid = unsafe { *pwent }.pw_uid; let gid = unsafe { *pwent }.pw_gid; Ok(UserIds { uid: Some(uid), gid: Some(gid), group_list: None, }) } fn default_group_list( user: &OsStr, gid: libc::gid_t, ) -> Result>, PrivDropError> { let username = CString::new(user.as_bytes()) .map_err(|_| PrivDropError::from((ErrorKind::SysError, "Invalid username")))?; let mut groups = vec![0; 256]; let mut ngroups = groups.len() as _; let ret = unsafe { libc::getgrouplist( username.as_ptr(), gid as _, groups.as_mut_ptr(), &mut ngroups, ) }; if ret == -1 { return Ok(None); } groups.truncate(ngroups as _); let mut groups_ = Vec::with_capacity(groups.len()); for group in groups { groups_.push(group as _); } Ok(Some(groups_)) } fn lookup_group( group: &OsStr, fallback_to_ids_if_names_are_numeric: bool, ) -> Result { let groupname = CString::new(group.as_bytes()) .map_err(|_| PrivDropError::from((ErrorKind::SysError, "Invalid group name")))?; let mut grp = unsafe { std::mem::zeroed::() }; let mut grbuf = vec![0; 4096]; let mut grent = std::ptr::null_mut::(); let ret = unsafe { libc::getgrnam_r( groupname.as_ptr(), &mut grp, grbuf.as_mut_ptr(), grbuf.len(), &mut grent, ) }; if ret != 0 || grent.is_null() { if !fallback_to_ids_if_names_are_numeric { return Err(PrivDropError::from(( ErrorKind::SysError, "Group not found", ))); } let group_str = group.to_str().ok_or_else(|| { PrivDropError::from(( ErrorKind::SysError, "Group not found and group is not a valid number", )) })?; let gid: libc::gid_t = group_str.parse().map_err(|_| { PrivDropError::from(( ErrorKind::SysError, "Group not found and group is not a valid number", )) })?; return Ok(gid); } Ok(unsafe { *grent }.gr_gid) } fn lookup_ids(&self) -> Result { let mut ids = UserIds::default(); if let Some(ref user) = self.user { ids = PrivDrop::lookup_user(user, self.fallback_to_ids_if_names_are_numeric)?; } if let Some(ref group) = self.group { ids.gid = Some(PrivDrop::lookup_group( group, self.fallback_to_ids_if_names_are_numeric, )?); } if let Some(ref group_list) = self.group_list { let mut groups = Vec::with_capacity(group_list.len()); for group in group_list { groups.push(PrivDrop::lookup_group( group, self.fallback_to_ids_if_names_are_numeric, )?); } ids.group_list = Some(groups); } Ok(ids) } fn do_idchange(&self, ids: UserIds) -> Result<(), PrivDropError> { Self::uidcheck()?; let mut groups = vec![]; if self.include_default_supplementary_groups { if let (Some(user), Some(gid)) = (&self.user, ids.gid) { if let Some(group_list) = Self::default_group_list(user, gid)? { groups.extend(group_list); } } else { return Err(PrivDropError::from(( ErrorKind::SysError, "Unable to determine default supplementary groups without a user name and a base gid", ))); } } if let Some(ref group_list) = ids.group_list { groups.extend(group_list.iter().cloned()); } if let Some(gid) = ids.gid { groups.push(gid); let mut unique_groups = vec![]; for group in groups { if !unique_groups.contains(&group) { unique_groups.push(group); } } if unsafe { libc::setgroups(unique_groups.len() as _, unique_groups.as_ptr()) } != 0 { return Err(PrivDropError::from(( ErrorKind::SysError, "Unable to revoke supplementary groups", ))); } unistd::setgid(unistd::Gid::from_raw(gid))?; } if let Some(uid) = ids.uid { unistd::setuid(unistd::Uid::from_raw(uid))? } Ok(()) } }