Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 1 May 2019 00:00:49 +0000 (UTC)
From:      Alan Somers <asomers@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r346979 - in projects/fuse2: sys/fs/fuse tests/sys/fs/fusefs
Message-ID:  <201905010000.x4100nVs085078@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: asomers
Date: Wed May  1 00:00:49 2019
New Revision: 346979
URL: https://svnweb.freebsd.org/changeset/base/346979

Log:
  fusefs: fix some permission checks with -o default_permissions
  
  When mounted with -o default_permissions fusefs is supposed to validate all
  permissions in the kernel, not the file system.  This commit fixes two
  permissions that I had previously overlooked.
  
  * Only root may chown a file
  * Non-root users may only chgrp a file to a group to which they belong
  
  PR:		216391
  Sponsored by:	The FreeBSD Foundation

Modified:
  projects/fuse2/sys/fs/fuse/fuse_vnops.c
  projects/fuse2/tests/sys/fs/fusefs/default_permissions.cc
  projects/fuse2/tests/sys/fs/fusefs/utils.cc
  projects/fuse2/tests/sys/fs/fusefs/utils.hh

Modified: projects/fuse2/sys/fs/fuse/fuse_vnops.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_vnops.c	Tue Apr 30 23:53:54 2019	(r346978)
+++ projects/fuse2/sys/fs/fuse/fuse_vnops.c	Wed May  1 00:00:49 2019	(r346979)
@@ -1518,14 +1518,20 @@ fuse_vnop_setattr(struct vop_setattr_args *ap)
 	struct thread *td = curthread;
 	struct fuse_dispatcher fdi;
 	struct fuse_setattr_in *fsai;
+	struct mount *mp;
 	pid_t pid = td->td_proc->p_pid;
-
+	struct fuse_data *data;
+	int dataflags;
 	int err = 0;
 	enum vtype vtyp;
 	int sizechanged = 0;
 	uint64_t newsize = 0;
 	accmode_t accmode = 0;
 
+	mp = vnode_mount(vp);
+	data = fuse_get_mpdata(mp);
+	dataflags = data->dataflags;
+
 	if (fuse_isdeadfs(vp)) {
 		return ENXIO;
 	}
@@ -1535,11 +1541,28 @@ fuse_vnop_setattr(struct vop_setattr_args *ap)
 	fsai->valid = 0;
 
 	if (vap->va_uid != (uid_t)VNOVAL) {
+		if (dataflags & FSESS_DEFAULT_PERMISSIONS) {
+			/* Only root may change a file's owner */
+			err = priv_check_cred(cred, PRIV_VFS_CHOWN);
+			if (err)
+				return err;
+		}
 		fsai->uid = vap->va_uid;
 		fsai->valid |= FATTR_UID;
 		accmode |= VADMIN;
 	}
 	if (vap->va_gid != (gid_t)VNOVAL) {
+		if (dataflags & FSESS_DEFAULT_PERMISSIONS &&
+		    !groupmember(vap->va_gid, cred))
+		{
+			/*
+			 * Non-root users may only chgrp to one of their own
+			 * groups 
+			 */
+			err = priv_check_cred(cred, PRIV_VFS_CHOWN);
+			if (err)
+				return err;
+		}
 		fsai->gid = vap->va_gid;
 		fsai->valid |= FATTR_GID;
 		accmode |= VADMIN;

Modified: projects/fuse2/tests/sys/fs/fusefs/default_permissions.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/default_permissions.cc	Tue Apr 30 23:53:54 2019	(r346978)
+++ projects/fuse2/tests/sys/fs/fusefs/default_permissions.cc	Wed May  1 00:00:49 2019	(r346979)
@@ -69,7 +69,7 @@ virtual void SetUp() {
 
 public:
 void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
-	uid_t uid = 0)
+	uid_t uid = 0, gid_t gid = 0)
 {
 	EXPECT_CALL(*m_mock, process(
 		ResultOf([=](auto in) {
@@ -84,19 +84,22 @@ void expect_getattr(uint64_t ino, mode_t mode, uint64_
 		out->body.attr.attr.mode = mode;
 		out->body.attr.attr.size = 0;
 		out->body.attr.attr.uid = uid;
+		out->body.attr.attr.uid = gid;
 		out->body.attr.attr_valid = attr_valid;
 	})));
 }
 
 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
-	uint64_t attr_valid, uid_t uid = 0)
+	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
 {
-	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid);
+	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
 }
 
 };
 
 class Access: public DefaultPermissions {};
+class Chown: public DefaultPermissions {};
+class Chgrp: public DefaultPermissions {};
 class Lookup: public DefaultPermissions {};
 class Open: public DefaultPermissions {};
 class Setattr: public DefaultPermissions {};
@@ -252,6 +255,105 @@ TEST_F(Access, ok)
 	 */
 
 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
+}
+
+/* Only root may change a file's owner */
+TEST_F(Chown, eperm)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	const uint64_t ino = 42;
+	const mode_t mode = 0755;
+
+	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
+	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_SETATTR);
+		}, Eq(true)),
+		_)
+	).Times(0);
+
+	EXPECT_NE(0, chown(FULLPATH, 0, -1));
+	EXPECT_EQ(EPERM, errno);
+}
+
+/* non-root users may only chgrp a file to a group they belong to */
+TEST_F(Chgrp, eperm)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	const uint64_t ino = 42;
+	const mode_t mode = 0755;
+	int ngroups = 64;
+	gid_t groups[ngroups];
+	uid_t uid;
+	gid_t gid, newgid;
+	int i;
+
+	uid = geteuid();
+	gid = getegid();
+	getgrouplist(getlogin(), getegid(), groups, &ngroups);
+	for (newgid = 0; newgid >= 0; newgid++) {
+		bool belongs = false;
+
+		for (i = 0; i < ngroups; i++) {
+			if (groups[i] == newgid)
+				belongs = true;
+		}
+		if (!belongs)
+			break;
+	}
+	/* newgid is now a group to which the current user does not belong */
+
+	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
+	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_SETATTR);
+		}, Eq(true)),
+		_)
+	).Times(0);
+
+	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
+	EXPECT_EQ(EPERM, errno);
+}
+
+TEST_F(Chgrp, ok)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	const uint64_t ino = 42;
+	const mode_t mode = 0755;
+	uid_t uid;
+	gid_t gid, newgid;
+
+	uid = geteuid();
+	gid = 0;
+	newgid = getegid();
+
+	expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
+	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_SETATTR);
+		}, Eq(true)),
+		_)
+	).Times(0);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in->header.opcode == FUSE_SETATTR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+		SET_OUT_HEADER_LEN(out, attr);
+		out->body.attr.attr.mode = S_IFREG | mode;
+		out->body.attr.attr.uid = uid;
+		out->body.attr.attr.gid = newgid;
+	})));
+
+	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
 }
 
 TEST_F(Create, ok)

Modified: projects/fuse2/tests/sys/fs/fusefs/utils.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/utils.cc	Tue Apr 30 23:53:54 2019	(r346978)
+++ projects/fuse2/tests/sys/fs/fusefs/utils.cc	Wed May  1 00:00:49 2019	(r346979)
@@ -175,7 +175,7 @@ void FuseTest::expect_getattr(uint64_t ino, uint64_t s
 }
 
 void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
-	uint64_t size, int times, uint64_t attr_valid, uid_t uid)
+	uint64_t size, int times, uint64_t attr_valid, uid_t uid, gid_t gid)
 {
 	EXPECT_LOOKUP(1, relpath)
 	.Times(times)
@@ -187,6 +187,7 @@ void FuseTest::expect_lookup(const char *relpath, uint
 		out->body.entry.attr_valid = attr_valid;
 		out->body.entry.attr.size = size;
 		out->body.entry.attr.uid = uid;
+		out->body.entry.attr.gid = gid;
 	})));
 }
 

Modified: projects/fuse2/tests/sys/fs/fusefs/utils.hh
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/utils.hh	Tue Apr 30 23:53:54 2019	(r346978)
+++ projects/fuse2/tests/sys/fs/fusefs/utils.hh	Wed May  1 00:00:49 2019	(r346979)
@@ -103,7 +103,7 @@ class FuseTest : public ::testing::Test {
 	 */
 	void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
 		uint64_t size, int times, uint64_t attr_valid = UINT64_MAX,
-		uid_t uid = 0);
+		uid_t uid = 0, gid_t gid = 0);
 
 	/*
 	 * Create an expectation that FUSE_OPEN will be called for the given



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201905010000.x4100nVs085078>