From b4f4a9363800ca11da4028b29ee138bd00888af6 Mon Sep 17 00:00:00 2001 From: Jiri Kyjovsky Date: Wed, 6 May 2026 22:42:45 +0200 Subject: [PATCH] devices/fs: fix set_creds not switching credentials when CAP_SET{U,G}ID is available When the VMM process runs as non-root but retains CAP_SETUID/CAP_SETGID (e.g. in a user namespace with --userns=keep-id), set_creds() skipped the credential switch for uid/gid 0 because the `uid == 0` check came before the capability check. This caused all guest root file operations to execute with the VMM's actual uid/gid, leading to EPERM on writes to root-owned directories. Reorder the conditions so that when we have the capability to switch credentials, we always do so... including for uid/gid 0. --- .../src/virtio/fs/linux/passthrough.rs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/devices/src/virtio/fs/linux/passthrough.rs b/src/devices/src/virtio/fs/linux/passthrough.rs index e5ca21a03..e11ee6835 100644 --- a/src/devices/src/virtio/fs/linux/passthrough.rs +++ b/src/devices/src/virtio/fs/linux/passthrough.rs @@ -836,26 +836,33 @@ impl PassthroughFs { gid: libc::gid_t, ) -> io::Result<(Option, Option)> { // Change the gid first, since once we change the uid we lose the capability to change the gid. - let scoped_gid = if gid == 0 || self.my_gid == Some(gid) { + let scoped_gid = if self.my_gid == Some(gid) { + None + } else if self.my_gid.is_none() { + // We can switch gids, so actually do it. Without this, + // operations from guest root would run with the VMM's + // current gid instead of gid 0. + ScopedGid::new(gid)? + } else if gid == 0 { // Always allow "root" accesses even if we don't have root powers. // This means guest processes running as root can use /tmp (though // the files will not be actually owned by root), which is desirable. None - } else if self.my_gid.is_some() { + } else { // Reject writes as any other gid if we do not have setgid // privileges. return Err(io::Error::from_raw_os_error(libc::EPERM)); - } else { - ScopedGid::new(gid)? }; // Same logic as above, for uid. - let scoped_uid = if uid == 0 || self.my_uid == Some(uid) { + let scoped_uid = if self.my_uid == Some(uid) { None - } else if self.my_uid.is_some() { - return Err(io::Error::from_raw_os_error(libc::EPERM)); - } else { + } else if self.my_uid.is_none() { ScopedUid::new(uid)? + } else if uid == 0 { + None + } else { + return Err(io::Error::from_raw_os_error(libc::EPERM)); }; Ok((scoped_uid, scoped_gid))