Skip to content
Merged
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
14 changes: 13 additions & 1 deletion crates/fuse-backend-core/src/api/filesystem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ impl ZeroCopyWriter for File {
}

/// Additional context associated with requests.
#[derive(Default, Clone, Copy, Debug)]
#[derive(Default, Clone, Debug)]
pub struct Context {
/// The user ID of the calling process.
pub uid: libc::uid_t,
Expand All @@ -579,6 +579,17 @@ pub struct Context {
/// this group in their supplementary group list so that objects created
/// in setgid directories get the correct group ownership.
pub supp_gid: Option<libc::gid_t>,

/// Supplementary groups of the calling process.
///
/// When set, these groups are used directly instead of reading from
/// /proc/<pid>/status. This is essential for remote filesystems where
/// the PID doesn't exist on the server. The ordinary request path
/// leaves it `None`: the FUSE device only reports uid, primary gid and
/// -- for create-family requests -- `supp_gid` above. A VFS with an id
/// mapping translates the list to internal (host) ids together with
/// uid/gid, dropping groups outside the mapped range.
pub supplementary_groups: Option<Vec<libc::gid_t>>,
}

impl Context {
Expand All @@ -595,6 +606,7 @@ impl From<&fuse::InHeader> for Context {
gid: source.gid,
pid: source.pid as i32,
supp_gid: None,
supplementary_groups: None,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/fuse-backend-passthrough/src/async_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ macro_rules! async_relay {
($self:expr, $ctx:expr, [$($capture:tt)*], $inline_call:expr, $pool_fn:expr) => {
match $self.shared_ref.upgrade() {
Some(fs) if $self.async_thread_pool_enabled.load(Ordering::Relaxed) => {
let ctx = *$ctx;
let ctx = $ctx.clone();
$($capture)*
join_blocking(Runtime::spawn_blocking(move || ($pool_fn)(fs, ctx))).await
}
Expand Down
3 changes: 2 additions & 1 deletion crates/fuse-backend-passthrough/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1786,6 +1786,7 @@ mod tests {
gid: uid,
pid: 1,
supp_gid: Some(100000 + gid),
..Default::default()
};
let parent = vfs
.lookup(
Expand All @@ -1800,7 +1801,7 @@ mod tests {
// switches to the caller's credentials, which lack `gid`.
let ctx_no_group = Context {
supp_gid: None,
..ctx
..ctx.clone()
};
let err = vfs
.mkdir(
Expand Down
3 changes: 2 additions & 1 deletion crates/fuse-backend-vfs/src/async_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl AsyncFileSystem for Vfs {

// The supp gid is parsed after the request-wide id remap, so
// translate it here, where the target mount is known.
let mut ctx = *ctx;
let mut ctx = ctx.clone();
self.remap_ctx_supp_gid(&mut ctx, parent.fs_idx());

match self.get_real_rootfs(parent)? {
Expand Down Expand Up @@ -232,6 +232,7 @@ mod tests {
gid: 0,
pid: 0,
supp_gid: None,
..Default::default()
};

assert!(vfs.mount(Box::new(fs), "/x/y").is_ok());
Expand Down
14 changes: 14 additions & 0 deletions crates/fuse-backend-vfs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,20 @@ impl Vfs {
if let Some((internal_id, external_id, range)) = mapping {
ctx.uid = remap_id(ctx.uid, external_id, internal_id, range);
ctx.gid = remap_id(ctx.gid, external_id, internal_id, range);
// Translate the supplementary group list with the same security
// policy as remap_ctx_supp_gid(): groups outside the mapped range
// have no host counterpart and are dropped rather than adopted
// untranslated.
if let Some(groups) = ctx.supplementary_groups.as_mut() {
groups.retain_mut(|gid| {
if *gid >= external_id && *gid - external_id < range {
*gid = *gid - external_id + internal_id;
true
} else {
false
}
});
}
}
Ok(())
}
Expand Down
74 changes: 70 additions & 4 deletions crates/fuse-backend-vfs/src/sync_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ impl FileSystem for Vfs {

// The supp gid is parsed after the request-wide id remap, so
// translate it here, where the target mount is known.
let mut ctx = *ctx;
let mut ctx = ctx.clone();
self.remap_ctx_supp_gid(&mut ctx, parent.fs_idx());

match self.get_real_rootfs(parent)? {
Expand All @@ -184,7 +184,7 @@ impl FileSystem for Vfs {

// The supp gid is parsed after the request-wide id remap, so
// translate it here, where the target mount is known.
let mut ctx = *ctx;
let mut ctx = ctx.clone();
self.remap_ctx_supp_gid(&mut ctx, inode.fs_idx());

match self.get_real_rootfs(inode)? {
Expand All @@ -207,7 +207,7 @@ impl FileSystem for Vfs {

// The supp gid is parsed after the request-wide id remap, so
// translate it here, where the target mount is known.
let mut ctx = *ctx;
let mut ctx = ctx.clone();
self.remap_ctx_supp_gid(&mut ctx, parent.fs_idx());

match self.get_real_rootfs(parent)? {
Expand Down Expand Up @@ -327,7 +327,7 @@ impl FileSystem for Vfs {

// The supp gid is parsed after the request-wide id remap, so
// translate it here, where the target mount is known.
let mut ctx = *ctx;
let mut ctx = ctx.clone();
self.remap_ctx_supp_gid(&mut ctx, parent.fs_idx());

match self.get_real_rootfs(parent)? {
Expand Down Expand Up @@ -721,6 +721,7 @@ mod tests {
gid: 100123,
pid: 1,
supp_gid: None,
..Default::default()
};

vfs.id_remap(&mut ctx).unwrap();
Expand All @@ -741,6 +742,7 @@ mod tests {
gid: 100123,
pid: 1,
supp_gid: None,
..Default::default()
};

// fs_idx == 0 (pseudo fs) falls back to global mapping
Expand All @@ -766,6 +768,7 @@ mod tests {
gid: 100123,
pid: 1,
supp_gid: Some(100123),
..Default::default()
};
vfs.remap_ctx_supp_gid(&mut ctx, 0);
assert_eq!(ctx.supp_gid, Some(123));
Expand All @@ -777,6 +780,7 @@ mod tests {
gid: 100123,
pid: 1,
supp_gid: Some(5),
..Default::default()
};
vfs.remap_ctx_supp_gid(&mut ctx, 0);
assert_eq!(ctx.supp_gid, None);
Expand All @@ -788,8 +792,70 @@ mod tests {
gid: 0,
pid: 1,
supp_gid: Some(123),
..Default::default()
};
vfs.remap_ctx_supp_gid(&mut ctx, 0);
assert_eq!(ctx.supp_gid, Some(123));
}

#[test]
fn test_remap_ctx_supplementary_groups() {
let vfs = Vfs::new(VfsOptions {
id_mapping: (0, 100000, 65536),
..Default::default()
});

// Groups inside the mapped range are translated to internal (host)
// ids in order; groups outside the range have no host counterpart
// and are dropped instead of being adopted untranslated.
let mut ctx = Context {
uid: 100000,
gid: 100123,
pid: 1,
supplementary_groups: Some(vec![100123, 5, 165535, 100124]),
..Default::default()
};
vfs.id_remap(&mut ctx).unwrap();
assert_eq!(ctx.supplementary_groups, Some(vec![123, 65535, 124]),);
assert_eq!(ctx.uid, 0);
assert_eq!(ctx.gid, 123);

// An empty list stays empty, and a reversed mapping direction
// (internal base above the external base) translates without
// underflow.
let vfs = Vfs::new(VfsOptions {
id_mapping: (200000, 100000, 65536),
..Default::default()
});
let mut ctx = Context {
uid: 100000,
gid: 100500,
pid: 1,
supplementary_groups: Some(Vec::new()),
..Default::default()
};
vfs.id_remap(&mut ctx).unwrap();
assert_eq!(ctx.supplementary_groups, Some(Vec::new()));

ctx.supplementary_groups = Some(vec![100500, 99999]);
vfs.id_remap(&mut ctx).unwrap();
assert_eq!(ctx.supplementary_groups, Some(vec![200500]));

// Without an id mapping the groups pass through unchanged, and
// `None` never allocates a list.
let vfs = Vfs::new(VfsOptions::default());
let mut ctx = Context {
uid: 0,
gid: 0,
pid: 1,
supplementary_groups: Some(vec![123]),
..Default::default()
};
vfs.id_remap(&mut ctx).unwrap();
assert_eq!(ctx.supplementary_groups, Some(vec![123]));

ctx.supplementary_groups = None;
vfs.id_remap(&mut ctx).unwrap();
assert_eq!(ctx.supplementary_groups, None);
}
}
Loading