Skip to content

Commit

Permalink
ptfs: switch to new implementation of unix credentials
Browse files Browse the repository at this point in the history
Switch to new implementation of unix credentials, with support of
supplemental group ids.

Signed-off-by: Jiang Liu <[email protected]>
  • Loading branch information
jiangliu committed Nov 14, 2023
1 parent cc0a191 commit 6023cff
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 166 deletions.
149 changes: 84 additions & 65 deletions src/passthrough/credentials.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: BSD-3-Clause

use crate::oslib;
use crate::passthrough::util::einval;
use std::io;

use super::util::{dropsupgroups, seteffgid, seteffuid, setsupgroup};

pub struct UnixCredentials {
uid: libc::uid_t,
gid: libc::gid_t,
Expand All @@ -24,6 +24,7 @@ impl UnixCredentials {
/// Set a supplementary group. Set `supported_extension` to `false` to signal that a
/// supplementary group maybe required, but the guest was not able to tell us which,
/// so we have to rely on keeping the DAC_OVERRIDE capability.
#[allow(dead_code)]
pub fn supplementary_gid(self, supported_extension: bool, sup_gid: Option<u32>) -> Self {
UnixCredentials {
uid: self.uid,
Expand All @@ -33,8 +34,9 @@ impl UnixCredentials {
}
}

/// Changes the effective uid/gid of the current thread to `val`. Changes
/// the thread's credentials back to root when the returned struct is dropped.
/// Changes the effective uid/gid of the current thread to `val`.
///
/// Changes the thread's credentials back to root when the returned struct is dropped.
pub fn set(self) -> io::Result<Option<UnixCredentialsGuard>> {
let change_uid = self.uid != 0;
let change_gid = self.gid != 0;
Expand All @@ -43,15 +45,15 @@ impl UnixCredentials {
// change the uid first then we lose the capability to change the gid.
// However changing back can happen in any order.
if let Some(sup_gid) = self.sup_gid {
oslib::setsupgroup(sup_gid)?;
setsupgroup(sup_gid)?;
}

if change_gid {
oslib::seteffgid(self.gid)?;
seteffgid(self.gid)?;
}

if change_uid {
oslib::seteffuid(self.uid)?;
seteffuid(self.uid)?;
}

if change_uid && self.keep_capability {
Expand All @@ -61,7 +63,7 @@ impl UnixCredentials {
// user ID, so we still have the 'DAC_OVERRIDE' in the permitted set.
// After switching back to root the permitted set is copied to the effective set,
// so no additional steps are required.
if let Err(e) = crate::util::add_cap_to_eff("DAC_OVERRIDE") {
if let Err(e) = add_cap_to_eff(caps::Capability::CAP_DAC_OVERRIDE) {
warn!("failed to add 'DAC_OVERRIDE' to the effective set of capabilities: {e}");
}
}
Expand All @@ -87,88 +89,105 @@ pub struct UnixCredentialsGuard {
impl Drop for UnixCredentialsGuard {
fn drop(&mut self) {
if self.reset_uid {
oslib::seteffuid(0).unwrap_or_else(|e| {
seteffuid(0).unwrap_or_else(|e| {
error!("failed to change uid back to root: {e}");
});
}

if self.reset_gid {
oslib::seteffgid(0).unwrap_or_else(|e| {
seteffgid(0).unwrap_or_else(|e| {
error!("failed to change gid back to root: {e}");
});
}

if self.drop_sup_gid {
oslib::dropsupgroups().unwrap_or_else(|e| {
dropsupgroups().unwrap_or_else(|e| {
error!("failed to drop supplementary groups: {e}");
});
}
}
}

pub struct ScopedCaps {
cap: capng::Capability,
capability: caps::Capability,
}

impl ScopedCaps {
fn new(cap_name: &str) -> io::Result<Option<Self>> {
use capng::{Action, CUpdate, Set, Type};

let cap = capng::name_to_capability(cap_name).map_err(|_| {
let err = io::Error::last_os_error();
error!(
"couldn't get the capability id for name {}: {:?}",
cap_name, err
);
err
})?;

if capng::have_capability(Type::EFFECTIVE, cap) {
let req = vec![CUpdate {
action: Action::DROP,
cap_type: Type::EFFECTIVE,
capability: cap,
}];
capng::update(req).map_err(|e| {
error!("couldn't drop {} capability: {:?}", cap, e);
einval()
})?;
capng::apply(Set::CAPS).map_err(|e| {
error!(
"couldn't apply capabilities after dropping {}: {:?}",
cap, e
);
einval()
})?;
Ok(Some(Self { cap }))
} else {
Ok(None)
}
impl Drop for ScopedCaps {
fn drop(&mut self) {
if let Err(e) = caps::raise(None, caps::CapSet::Effective, self.capability) {
error!("fail to restore thread cap_fsetid: {}", e);
};
}
}

impl Drop for ScopedCaps {
fn drop(&mut self) {
use capng::{Action, CUpdate, Set, Type};
pub fn scoped_drop_capability(capability: caps::Capability) -> io::Result<Option<ScopedCaps>> {
if !caps::has_cap(None, caps::CapSet::Effective, capability)
.map_err(|_e| io::Error::new(io::ErrorKind::PermissionDenied, "no capability"))?
{
return Ok(None);
}
caps::drop(None, caps::CapSet::Effective, capability).map_err(|_e| {
io::Error::new(io::ErrorKind::PermissionDenied, "failed to drop capability")
})?;
Ok(Some(ScopedCaps { capability }))
}

let req = vec![CUpdate {
action: Action::ADD,
cap_type: Type::EFFECTIVE,
capability: self.cap,
}];
pub fn drop_cap_fssetid() -> io::Result<Option<ScopedCaps>> {
scoped_drop_capability(caps::Capability::CAP_FSETID)
}

if let Err(e) = capng::update(req) {
panic!("couldn't restore {} capability: {:?}", self.cap, e);
}
if let Err(e) = capng::apply(Set::CAPS) {
panic!(
"couldn't apply capabilities after restoring {}: {:?}",
self.cap, e
);
/// 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(capability: caps::Capability) -> io::Result<()> {
caps::raise(None, caps::CapSet::Effective, capability).map_err(|_e| {
io::Error::new(
io::ErrorKind::PermissionDenied,
"failed to raise capability",
)
})
}

#[cfg(test)]
mod tests {
use super::*;
use nix::unistd::getuid;

#[test]
fn test_unix_credentials_set() {
if getuid() == 0 {
let cred = UnixCredentials::new(0, 0).set().unwrap();
assert!(cred.is_none());
drop(cred);

let cred = UnixCredentials::new(1, 1);
let cred = cred.supplementary_gid(false, Some(2));
let guard = cred.set().unwrap();
assert!(guard.is_some());
drop(guard);
}
}
}

pub fn drop_effective_cap(cap_name: &str) -> io::Result<Option<ScopedCaps>> {
ScopedCaps::new(cap_name)
#[test]
fn test_drop_cap_fssetid() {
let cap = drop_cap_fssetid().unwrap();
let has_cap =
caps::has_cap(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID).unwrap();
assert_eq!(has_cap, false);
drop(cap);
}

#[test]
fn test_add_cap_to_eff() {
if getuid() == 0 {
add_cap_to_eff(caps::Capability::CAP_DAC_OVERRIDE).unwrap();
}
}
}
2 changes: 0 additions & 2 deletions src/passthrough/file_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,7 @@ impl OpenableFileHandle {
#[cfg(test)]
mod tests {
use super::*;
use nix::unistd::getuid;
use std::ffi::CString;
use std::io::Read;

fn generate_c_file_handle(
handle_bytes: usize,
Expand Down
88 changes: 1 addition & 87 deletions src/passthrough/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ use crate::api::{
#[cfg(feature = "async-io")]
mod async_io;
mod config;
mod credentials;
mod file_handle;
mod inode_store;
mod mount_fd;
Expand Down Expand Up @@ -888,93 +889,6 @@ impl<S: BitmapSlice + Send + Sync + 'static> BackendFileSystem for PassthroughFs
}
}

macro_rules! scoped_cred {
($name:ident, $ty:ty, $syscall_nr:expr) => {
#[derive(Debug)]
pub(crate) struct $name;

impl $name {
// Changes the effective uid/gid of the current thread to `val`. Changes
// the thread's credentials back to root when the returned struct is dropped.
fn new(val: $ty) -> io::Result<Option<$name>> {
if val == 0 {
// Nothing to do since we are already uid 0.
return Ok(None);
}

// We want credential changes to be per-thread because otherwise
// we might interfere with operations being carried out on other
// threads with different uids/gids. However, posix requires that
// all threads in a process share the same credentials. To do this
// libc uses signals to ensure that when one thread changes its
// credentials the other threads do the same thing.
//
// So instead we invoke the syscall directly in order to get around
// this limitation. Another option is to use the setfsuid and
// setfsgid systems calls. However since those calls have no way to
// return an error, it's preferable to do this instead.

// This call is safe because it doesn't modify any memory and we
// check the return value.
let res = unsafe { libc::syscall($syscall_nr, -1, val, -1) };
if res == 0 {
Ok(Some($name))
} else {
Err(io::Error::last_os_error())
}
}
}

impl Drop for $name {
fn drop(&mut self) {
let res = unsafe { libc::syscall($syscall_nr, -1, 0, -1) };
if res < 0 {
error!(
"fuse: failed to change credentials back to root: {}",
io::Error::last_os_error(),
);
}
}
}
};
}
scoped_cred!(ScopedUid, libc::uid_t, libc::SYS_setresuid);
scoped_cred!(ScopedGid, libc::gid_t, libc::SYS_setresgid);

fn set_creds(
uid: libc::uid_t,
gid: libc::gid_t,
) -> io::Result<(Option<ScopedUid>, Option<ScopedGid>)> {
// We have to change the gid before we change the uid because if we change the uid first then we
// lose the capability to change the gid. However changing back can happen in any order.
ScopedGid::new(gid).and_then(|gid| Ok((ScopedUid::new(uid)?, gid)))
}

struct CapFsetid {}

impl Drop for CapFsetid {
fn drop(&mut self) {
if let Err(e) = caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) {
error!("fail to restore thread cap_fsetid: {}", e);
};
}
}

fn drop_cap_fsetid() -> io::Result<Option<CapFsetid>> {
if !caps::has_cap(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID)
.map_err(|_e| io::Error::new(io::ErrorKind::PermissionDenied, "no CAP_FSETID capability"))?
{
return Ok(None);
}
caps::drop(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID).map_err(|_e| {
io::Error::new(
io::ErrorKind::PermissionDenied,
"failed to drop CAP_FSETID capability",
)
})?;
Ok(Some(CapFsetid {}))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading

0 comments on commit 6023cff

Please sign in to comment.