Skip to content

Commit 2445475

Browse files
committed
Add suid/sgid support to simplefs
1 parent 9b65602 commit 2445475

File tree

5 files changed

+172
-36
lines changed

5 files changed

+172
-36
lines changed

Diff for: Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pjdfs_tests_fuse2:
2828
-v "$(shell pwd)/logs:/code/logs" fuser:pjdfs bash -c "cd /code/fuser && ./pjdfs.sh"
2929

3030
pjdfs_tests_fuse3:
31-
docker build --build-arg BUILD_FEATURES='--features=abi-7-21' -t fuser:pjdfs -f pjdfs.Dockerfile .
31+
docker build --build-arg BUILD_FEATURES='--features=abi-7-31' -t fuser:pjdfs -f pjdfs.Dockerfile .
3232
# Additional permissions are needed to be able to mount FUSE
3333
docker run --rm -$(INTERACTIVE)t --cap-add SYS_ADMIN --device /dev/fuse --security-opt apparmor:unconfined \
3434
-v "$(shell pwd)/logs:/code/logs" fuser:pjdfs bash -c "cd /code/fuser && ./pjdfs.sh"

Diff for: examples/simple.rs

+167-26
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22

33
use clap::{crate_version, App, Arg};
44
use fuser::consts::FOPEN_DIRECT_IO;
5+
#[cfg(feature = "abi-7-26")]
6+
use fuser::consts::FUSE_HANDLE_KILLPRIV;
7+
#[cfg(feature = "abi-7-31")]
8+
use fuser::consts::FUSE_WRITE_KILL_PRIV;
59
use fuser::TimeOrNow::Now;
610
use fuser::{
711
Filesystem, KernelConfig, MountOption, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory,
812
ReplyEmpty, ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow,
913
FUSE_ROOT_ID,
1014
};
15+
#[cfg(feature = "abi-7-26")]
16+
use log::info;
1117
use log::LevelFilter;
1218
use log::{debug, warn};
1319
use serde::{Deserialize, Serialize};
@@ -19,6 +25,7 @@ use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
1925
use std::os::raw::c_int;
2026
use std::os::unix::ffi::OsStrExt;
2127
use std::os::unix::fs::FileExt;
28+
#[cfg(target_os = "linux")]
2229
use std::os::unix::io::IntoRawFd;
2330
use std::path::{Path, PathBuf};
2431
use std::sync::atomic::{AtomicU64, Ordering};
@@ -102,6 +109,22 @@ fn parse_xattr_namespace(key: &[u8]) -> Result<XattrNamespace, c_int> {
102109
return Err(libc::ENOTSUP);
103110
}
104111

112+
fn clear_suid_sgid(attr: &mut InodeAttributes) {
113+
attr.mode &= !libc::S_ISUID as u16;
114+
// SGID is only suppose to be cleared if XGRP is set
115+
if attr.mode & libc::S_IXGRP as u16 != 0 {
116+
attr.mode &= !libc::S_ISGID as u16;
117+
}
118+
}
119+
120+
fn creation_gid(parent: &InodeAttributes, gid: u32) -> u32 {
121+
if parent.mode & libc::S_ISGID as u16 != 0 {
122+
return parent.gid;
123+
}
124+
125+
gid
126+
}
127+
105128
fn xattr_access_check(
106129
key: &[u8],
107130
access_mask: i32,
@@ -223,14 +246,40 @@ struct SimpleFS {
223246
data_dir: String,
224247
next_file_handle: AtomicU64,
225248
direct_io: bool,
249+
suid_support: bool,
226250
}
227251

228252
impl SimpleFS {
229-
fn new(data_dir: String, direct_io: bool) -> SimpleFS {
230-
SimpleFS {
231-
data_dir,
232-
next_file_handle: AtomicU64::new(1),
233-
direct_io,
253+
fn new(
254+
data_dir: String,
255+
direct_io: bool,
256+
#[allow(unused_variables)] suid_support: bool,
257+
) -> SimpleFS {
258+
#[cfg(feature = "abi-7-26")]
259+
{
260+
SimpleFS {
261+
data_dir,
262+
next_file_handle: AtomicU64::new(1),
263+
direct_io,
264+
suid_support,
265+
}
266+
}
267+
#[cfg(not(feature = "abi-7-26"))]
268+
{
269+
SimpleFS {
270+
data_dir,
271+
next_file_handle: AtomicU64::new(1),
272+
direct_io,
273+
suid_support: false,
274+
}
275+
}
276+
}
277+
278+
fn creation_mode(&self, mode: u32) -> u16 {
279+
if !self.suid_support {
280+
(mode & !(libc::S_ISUID | libc::S_ISGID) as u32) as u16
281+
} else {
282+
mode as u16
234283
}
235284
}
236285

@@ -373,6 +422,9 @@ impl SimpleFS {
373422
attrs.last_metadata_changed = time_now();
374423
attrs.last_modified = time_now();
375424

425+
// Clear SETUID & SETGID on truncate
426+
clear_suid_sgid(&mut attrs);
427+
376428
self.write_inode(&attrs);
377429

378430
Ok(attrs)
@@ -424,7 +476,14 @@ impl SimpleFS {
424476
}
425477

426478
impl Filesystem for SimpleFS {
427-
fn init(&mut self, _req: &Request, _config: &mut KernelConfig) -> Result<(), c_int> {
479+
fn init(
480+
&mut self,
481+
_req: &Request,
482+
#[allow(unused_variables)] config: &mut KernelConfig,
483+
) -> Result<(), c_int> {
484+
#[cfg(feature = "abi-7-26")]
485+
config.add_capabilities(FUSE_HANDLE_KILLPRIV).unwrap();
486+
428487
fs::create_dir_all(Path::new(&self.data_dir).join("inodes")).unwrap();
429488
fs::create_dir_all(Path::new(&self.data_dir).join("contents")).unwrap();
430489
if self.get_inode(FUSE_ROOT_ID).is_err() {
@@ -516,7 +575,16 @@ impl Filesystem for SimpleFS {
516575
reply.error(libc::EPERM);
517576
return;
518577
}
519-
attrs.mode = mode as u16;
578+
if req.uid() != 0
579+
&& req.gid() != attrs.gid
580+
&& !get_groups(req.pid()).contains(&attrs.gid)
581+
{
582+
// If SGID is set and the file belongs to a group that the caller is not part of
583+
// then the SGID bit is suppose to be cleared during chmod
584+
attrs.mode = (mode & !libc::S_ISGID as u32) as u16;
585+
} else {
586+
attrs.mode = mode as u16;
587+
}
520588
attrs.last_metadata_changed = time_now();
521589
self.write_inode(&attrs);
522590
reply.attr(&Duration::new(0, 0), &attrs.into());
@@ -547,11 +615,22 @@ impl Filesystem for SimpleFS {
547615
return;
548616
}
549617

618+
if attrs.mode & (libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH) as u16 != 0 {
619+
// SUID & SGID are suppose to be cleared when chown'ing an executable file
620+
clear_suid_sgid(&mut attrs);
621+
}
622+
550623
if let Some(uid) = uid {
551624
attrs.uid = uid;
625+
// Clear SETUID on owner change
626+
attrs.mode &= !libc::S_ISUID as u16;
552627
}
553628
if let Some(gid) = gid {
554629
attrs.gid = gid;
630+
// Clear SETGID unless user is root
631+
if req.uid() != 0 {
632+
attrs.mode &= !libc::S_ISGID as u16;
633+
}
555634
}
556635
attrs.last_metadata_changed = time_now();
557636
self.write_inode(&attrs);
@@ -664,7 +743,7 @@ impl Filesystem for SimpleFS {
664743
req: &Request,
665744
parent: u64,
666745
name: &OsStr,
667-
mode: u32,
746+
mut mode: u32,
668747
_umask: u32,
669748
_rdev: u32,
670749
reply: ReplyEntry,
@@ -709,6 +788,10 @@ impl Filesystem for SimpleFS {
709788
parent_attrs.last_metadata_changed = time_now();
710789
self.write_inode(&parent_attrs);
711790

791+
if req.uid() != 0 {
792+
mode &= !(libc::S_ISUID | libc::S_ISGID) as u32;
793+
}
794+
712795
let inode = self.allocate_next_inode();
713796
let attrs = InodeAttributes {
714797
inode,
@@ -718,11 +801,10 @@ impl Filesystem for SimpleFS {
718801
last_modified: time_now(),
719802
last_metadata_changed: time_now(),
720803
kind: as_file_kind(mode),
721-
// TODO: suid/sgid not supported
722-
mode: (mode & !(libc::S_ISUID | libc::S_ISGID) as u32) as u16,
804+
mode: self.creation_mode(mode),
723805
hardlinks: 1,
724806
uid: req.uid(),
725-
gid: req.gid(),
807+
gid: creation_gid(&parent_attrs, req.gid()),
726808
xattrs: Default::default(),
727809
};
728810
self.write_inode(&attrs);
@@ -748,7 +830,7 @@ impl Filesystem for SimpleFS {
748830
req: &Request,
749831
parent: u64,
750832
name: &OsStr,
751-
mode: u32,
833+
mut mode: u32,
752834
_umask: u32,
753835
reply: ReplyEntry,
754836
) {
@@ -781,6 +863,13 @@ impl Filesystem for SimpleFS {
781863
parent_attrs.last_metadata_changed = time_now();
782864
self.write_inode(&parent_attrs);
783865

866+
if req.uid() != 0 {
867+
mode &= !(libc::S_ISUID | libc::S_ISGID) as u32;
868+
}
869+
if parent_attrs.mode & libc::S_ISGID as u16 != 0 {
870+
mode |= libc::S_ISGID as u32;
871+
}
872+
784873
let inode = self.allocate_next_inode();
785874
let attrs = InodeAttributes {
786875
inode,
@@ -790,11 +879,10 @@ impl Filesystem for SimpleFS {
790879
last_modified: time_now(),
791880
last_metadata_changed: time_now(),
792881
kind: FileKind::Directory,
793-
// TODO: suid/sgid not supported
794-
mode: (mode & !(libc::S_ISUID | libc::S_ISGID) as u32) as u16,
882+
mode: self.creation_mode(mode),
795883
hardlinks: 2, // Directories start with link count of 2, since they have a self link
796884
uid: req.uid(),
797-
gid: req.gid(),
885+
gid: creation_gid(&parent_attrs, req.gid()),
798886
xattrs: Default::default(),
799887
};
800888
self.write_inode(&attrs);
@@ -938,6 +1026,29 @@ impl Filesystem for SimpleFS {
9381026
reply: ReplyEntry,
9391027
) {
9401028
debug!("symlink() called with {:?} {:?} {:?}", parent, name, link);
1029+
let mut parent_attrs = match self.get_inode(parent) {
1030+
Ok(attrs) => attrs,
1031+
Err(error_code) => {
1032+
reply.error(error_code);
1033+
return;
1034+
}
1035+
};
1036+
1037+
if !check_access(
1038+
parent_attrs.uid,
1039+
parent_attrs.gid,
1040+
parent_attrs.mode,
1041+
req.uid(),
1042+
req.gid(),
1043+
libc::W_OK,
1044+
) {
1045+
reply.error(libc::EACCES);
1046+
return;
1047+
}
1048+
parent_attrs.last_modified = time_now();
1049+
parent_attrs.last_metadata_changed = time_now();
1050+
self.write_inode(&parent_attrs);
1051+
9411052
let inode = self.allocate_next_inode();
9421053
let attrs = InodeAttributes {
9431054
inode,
@@ -950,7 +1061,7 @@ impl Filesystem for SimpleFS {
9501061
mode: 0o777,
9511062
hardlinks: 1,
9521063
uid: req.uid(),
953-
gid: req.gid(),
1064+
gid: creation_gid(&parent_attrs, req.gid()),
9541065
xattrs: Default::default(),
9551066
};
9561067

@@ -1300,7 +1411,7 @@ impl Filesystem for SimpleFS {
13001411
offset: i64,
13011412
data: &[u8],
13021413
_write_flags: u32,
1303-
_flags: i32,
1414+
#[allow(unused_variables)] flags: i32,
13041415
_lock_owner: Option<u64>,
13051416
reply: ReplyWrite,
13061417
) {
@@ -1322,6 +1433,13 @@ impl Filesystem for SimpleFS {
13221433
if data.len() + offset as usize > attrs.size as usize {
13231434
attrs.size = (data.len() + offset as usize) as u64;
13241435
}
1436+
// #[cfg(feature = "abi-7-31")]
1437+
// if flags & FUSE_WRITE_KILL_PRIV as i32 != 0 {
1438+
// clear_suid_sgid(&mut attrs);
1439+
// }
1440+
// XXX: In theory we should only need to do this when WRITE_KILL_PRIV is set for 7.31+
1441+
// However, xfstests fail in that case
1442+
clear_suid_sgid(&mut attrs);
13251443
self.write_inode(&attrs);
13261444

13271445
reply.written(data.len() as u32);
@@ -1574,7 +1692,7 @@ impl Filesystem for SimpleFS {
15741692
req: &Request,
15751693
parent: u64,
15761694
name: &OsStr,
1577-
mode: u32,
1695+
mut mode: u32,
15781696
_umask: u32,
15791697
flags: i32,
15801698
reply: ReplyCreate,
@@ -1619,6 +1737,10 @@ impl Filesystem for SimpleFS {
16191737
parent_attrs.last_metadata_changed = time_now();
16201738
self.write_inode(&parent_attrs);
16211739

1740+
if req.uid() != 0 {
1741+
mode &= !(libc::S_ISUID | libc::S_ISGID) as u32;
1742+
}
1743+
16221744
let inode = self.allocate_next_inode();
16231745
let attrs = InodeAttributes {
16241746
inode,
@@ -1628,11 +1750,10 @@ impl Filesystem for SimpleFS {
16281750
last_modified: time_now(),
16291751
last_metadata_changed: time_now(),
16301752
kind: as_file_kind(mode),
1631-
// TODO: suid/sgid not supported
1632-
mode: (mode & !(libc::S_ISUID | libc::S_ISGID) as u32) as u16,
1753+
mode: self.creation_mode(mode),
16331754
hardlinks: 1,
16341755
uid: req.uid(),
1635-
gid: req.gid(),
1756+
gid: creation_gid(&parent_attrs, req.gid()),
16361757
xattrs: Default::default(),
16371758
};
16381759
self.write_inode(&attrs);
@@ -1855,6 +1976,11 @@ fn main() {
18551976
.long("fsck")
18561977
.help("Run a filesystem check"),
18571978
)
1979+
.arg(
1980+
Arg::with_name("suid")
1981+
.long("suid")
1982+
.help("Enable setuid support when run as root"),
1983+
)
18581984
.arg(
18591985
Arg::with_name("v")
18601986
.short("v")
@@ -1876,10 +2002,21 @@ fn main() {
18762002
.filter_level(log_level)
18772003
.init();
18782004

1879-
let mut options = vec![
1880-
MountOption::FSName("fuser".to_string()),
1881-
MountOption::AutoUnmount,
1882-
];
2005+
let mut options = vec![MountOption::FSName("fuser".to_string())];
2006+
2007+
#[cfg(feature = "abi-7-26")]
2008+
{
2009+
if matches.is_present("suid") {
2010+
info!("setuid bit support enabled");
2011+
options.push(MountOption::Suid);
2012+
} else {
2013+
options.push(MountOption::AutoUnmount);
2014+
}
2015+
}
2016+
#[cfg(not(feature = "abi-7-26"))]
2017+
{
2018+
options.push(MountOption::AutoUnmount);
2019+
}
18832020
if let Ok(enabled) = fuse_allow_other_enabled() {
18842021
if enabled {
18852022
options.push(MountOption::AllowOther);
@@ -1896,7 +2033,11 @@ fn main() {
18962033
.to_string();
18972034

18982035
fuser::mount2(
1899-
SimpleFS::new(data_dir, matches.is_present("direct-io")),
2036+
SimpleFS::new(
2037+
data_dir,
2038+
matches.is_present("direct-io"),
2039+
matches.is_present("suid"),
2040+
),
19002041
mountpoint,
19012042
&options,
19022043
)

Diff for: pjdfs.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export RUST_BACKTRACE=1
1313
DATA_DIR=$(mktemp --directory)
1414
DIR=$(mktemp --directory)
1515

16-
fuser -vvv --data-dir $DATA_DIR --mount-point $DIR > /code/logs/mount.log 2>&1 &
16+
fuser -vvv --suid --data-dir $DATA_DIR --mount-point $DIR > /code/logs/mount.log 2>&1 &
1717
FUSE_PID=$!
1818
sleep 0.5
1919

0 commit comments

Comments
 (0)