Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ptfs: switch to new implementation of unix credentials #175

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
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().is_root() {
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().is_root() {
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
95 changes: 7 additions & 88 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 @@ -748,7 +749,7 @@ impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
})
}

fn forget_one(&self, inodes: &mut InodeStore, inode: Inode, count: u64) {
fn forget_one_locked(&self, inodes: &mut InodeStore, inode: Inode, count: u64) {
// ROOT_ID should not be forgotten, or we're not able to access to files any more.
if inode == fuse::ROOT_ID {
return;
Expand Down Expand Up @@ -785,6 +786,11 @@ impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
}
}

fn forget_one(&self, inode: Inode, count: u64) {
let mut inodes = self.inode_map.get_map_mut();
self.forget_one_locked(&mut inodes, inode, count)
}

fn do_release(&self, inode: Inode, handle: Handle) -> io::Result<()> {
self.handle_map.release(handle, inode)
}
Expand Down Expand Up @@ -888,93 +894,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
Loading