Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 21 Mar 2019 19:56:33 +0000 (UTC)
From:      Alan Somers <asomers@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r345383 - projects/fuse2/tests/sys/fs/fusefs
Message-ID:  <201903211956.x2LJuXmC069207@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: asomers
Date: Thu Mar 21 19:56:33 2019
New Revision: 345383
URL: https://svnweb.freebsd.org/changeset/base/345383

Log:
  fusefs: add a test case for the allow_other mount option
  
  Also, fix one of the default_permissions test cases.  I forgot the
  expectation for FUSE_ACCESS, because that doesn't work right now.
  
  Sponsored by:	The FreeBSD Foundation

Added:
  projects/fuse2/tests/sys/fs/fusefs/allow_other.cc   (contents, props changed)
Modified:
  projects/fuse2/tests/sys/fs/fusefs/Makefile
  projects/fuse2/tests/sys/fs/fusefs/access.cc
  projects/fuse2/tests/sys/fs/fusefs/default_permissions.cc
  projects/fuse2/tests/sys/fs/fusefs/mockfs.cc
  projects/fuse2/tests/sys/fs/fusefs/mockfs.hh
  projects/fuse2/tests/sys/fs/fusefs/utils.cc
  projects/fuse2/tests/sys/fs/fusefs/utils.hh

Modified: projects/fuse2/tests/sys/fs/fusefs/Makefile
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/Makefile	Thu Mar 21 19:52:50 2019	(r345382)
+++ projects/fuse2/tests/sys/fs/fusefs/Makefile	Thu Mar 21 19:56:33 2019	(r345383)
@@ -8,6 +8,7 @@ TESTSDIR=	${TESTSBASE}/sys/fs/fusefs
 # Kyua treats googletest programs as plain tests, it's better to separate them
 # out, so we get more granular reporting.
 GTESTS+=	access
+GTESTS+=	allow_other
 GTESTS+=	create
 GTESTS+=	default_permissions
 GTESTS+=	destroy
@@ -41,6 +42,11 @@ SRCS.access+=	access.cc
 SRCS.access+=	getmntopts.c
 SRCS.access+=	mockfs.cc
 SRCS.access+=	utils.cc
+
+SRCS.allow_other+=	allow_other.cc
+SRCS.allow_other+=	getmntopts.c
+SRCS.allow_other+=	mockfs.cc
+SRCS.allow_other+=	utils.cc
 
 SRCS.create+=	create.cc
 SRCS.create+=	getmntopts.c

Modified: projects/fuse2/tests/sys/fs/fusefs/access.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/access.cc	Thu Mar 21 19:52:50 2019	(r345382)
+++ projects/fuse2/tests/sys/fs/fusefs/access.cc	Thu Mar 21 19:56:33 2019	(r345383)
@@ -40,18 +40,6 @@ using namespace testing;
 
 class Access: public FuseTest {
 public:
-void expect_access(uint64_t ino, mode_t access_mode, int error)
-{
-	EXPECT_CALL(*m_mock, process(
-		ResultOf([=](auto in) {
-			return (in->header.opcode == FUSE_ACCESS &&
-				in->header.nodeid == ino &&
-				in->body.access.mask == access_mode);
-		}, Eq(true)),
-		_)
-	).WillOnce(Invoke(ReturnErrno(error)));
-}
-
 void expect_lookup(const char *relpath, uint64_t ino)
 {
 	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);

Added: projects/fuse2/tests/sys/fs/fusefs/allow_other.cc
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/fuse2/tests/sys/fs/fusefs/allow_other.cc	Thu Mar 21 19:56:33 2019	(r345383)
@@ -0,0 +1,191 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2019 The FreeBSD Foundation
+ *
+ * This software was developed by BFF Storage Systems, LLC under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Tests for the "allow_other" mount option.  They must be in their own
+ * file so they can be run as root
+ */
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+void sighandler(int __unused sig) {}
+
+static void
+get_unprivileged_uid(int *uid)
+{
+	struct passwd *pw;
+
+	/* 
+	 * First try "tests", Kyua's default unprivileged user.  XXX after
+	 * GoogleTest gains a proper Kyua wrapper, get this with the Kyua API
+	 */
+	pw = getpwnam("tests");
+	if (pw == NULL) {
+		/* Fall back to "nobody" */
+		pw = getpwnam("nobody");
+	}
+	if (pw == NULL)
+		GTEST_SKIP() << "Test requires an unprivileged user";
+	*uid = pw->pw_uid;
+}
+
+class NoAllowOther: public FuseTest {
+
+public:
+/* Unprivileged user id */
+int m_uid;
+
+virtual void SetUp() {
+	if (geteuid() != 0) {
+		GTEST_SKIP() << "This test must be run as root";
+	}
+	get_unprivileged_uid(&m_uid);
+	if (IsSkipped())
+		return;
+
+	FuseTest::SetUp();
+}
+};
+
+class AllowOther: public NoAllowOther {
+
+public:
+virtual void SetUp() {
+	m_allow_other = true;
+	NoAllowOther::SetUp();
+}
+};
+
+TEST_F(AllowOther, allowed)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	uint64_t ino = 42;
+	int fd;
+	pid_t child;
+	
+	signal(SIGUSR2, sighandler);
+
+	if ((child = fork()) == 0) {
+		/* In child */
+		pause();
+
+		/* Drop privileges before accessing */
+		if (0 != setreuid(-1, m_uid)) {
+			perror("setreuid");
+			_exit(1);
+		}
+		fd = open(FULLPATH, O_RDONLY);
+		if (fd < 0) {
+			perror("open");
+			_exit(1);
+		}
+		_exit(0);
+
+		/* Deliberately leak fd */
+	} else if (child > 0) {
+		/* 
+		 * In parent.  Cleanup must happen here, because it's still
+		 * privileged.
+		 */
+		expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
+		expect_open(ino, 0, 1);
+		expect_release(ino, 1, 0, 0);
+		/* Until the attr cache is working, we may send an additional
+		 * GETATTR */
+		expect_getattr(ino, 0);
+		m_mock->m_child_pid = child;
+		/* Signal the child process to go */
+		kill(child, SIGUSR2);
+		int child_status;
+
+		wait(&child_status);
+		ASSERT_EQ(0, WEXITSTATUS(child_status));
+	} else {
+		FAIL() << strerror(errno);
+	}
+}
+
+TEST_F(NoAllowOther, disallowed)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	int fd;
+	pid_t child;
+	
+	signal(SIGUSR2, sighandler);
+
+	if ((child = fork()) == 0) {
+		/* In child */
+		pause();
+
+		/* Drop privileges before accessing */
+		if (0 != setreuid(-1, m_uid)) {
+			perror("setreuid");
+			_exit(1);
+		}
+		fd = open(FULLPATH, O_RDONLY);
+		if (fd >= 0) {
+			fprintf(stderr, "open should've failed\n");
+			_exit(1);
+		} else if (errno != EPERM) {
+			fprintf(stderr,
+				"Unexpected error: %s\n", strerror(errno));
+			_exit(1);
+		}
+		_exit(0);
+
+		/* Deliberately leak fd */
+	} else if (child > 0) {
+		/* 
+		 * In parent.  Cleanup must happen here, because it's still
+		 * privileged.
+		 */
+		m_mock->m_child_pid = child;
+		/* Signal the child process to go */
+		kill(child, SIGUSR2);
+		int child_status;
+
+		wait(&child_status);
+		ASSERT_EQ(0, WEXITSTATUS(child_status));
+	} else {
+		FAIL() << strerror(errno);
+	}
+}

Modified: projects/fuse2/tests/sys/fs/fusefs/default_permissions.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/default_permissions.cc	Thu Mar 21 19:52:50 2019	(r345382)
+++ projects/fuse2/tests/sys/fs/fusefs/default_permissions.cc	Thu Mar 21 19:56:33 2019	(r345383)
@@ -84,7 +84,8 @@ TEST_F(Access, DISABLED_eaccess)
 	ASSERT_EQ(EACCES, errno);
 }
 
-TEST_F(Access, ok)
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236291 */
+TEST_F(Access, DISABLED_ok)
 {
 	const char FULLPATH[] = "mountpoint/some_file.txt";
 	const char RELPATH[] = "some_file.txt";
@@ -92,10 +93,10 @@ TEST_F(Access, ok)
 	mode_t	access_mode = R_OK;
 
 	expect_lookup(RELPATH, ino, S_IFREG | 0644);
+	expect_access(ino, access_mode, 0);
 	/* 
 	 * Once default_permissions is properly implemented, there might be
-	 * another FUSE_GETATTR or something in here.  But there should not be
-	 * a FUSE_ACCESS
+	 * another FUSE_GETATTR or something in here.
 	 */
 
 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);

Modified: projects/fuse2/tests/sys/fs/fusefs/mockfs.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/mockfs.cc	Thu Mar 21 19:52:50 2019	(r345382)
+++ projects/fuse2/tests/sys/fs/fusefs/mockfs.cc	Thu Mar 21 19:56:33 2019	(r345383)
@@ -243,12 +243,13 @@ void debug_fuseop(const mockfs_buf_in *in)
 	printf("\n");
 }
 
-MockFS::MockFS(int max_readahead, bool push_symlinks_in,
-	bool default_permissions, uint32_t flags)
+MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
+	bool push_symlinks_in, uint32_t flags)
 {
 	struct iovec *iov = NULL;
 	int iovlen = 0;
 	char fdstr[15];
+	const bool trueval = true;
 
 	m_daemon_id = NULL;
 	m_maxreadahead = max_readahead;
@@ -262,33 +263,36 @@ MockFS::MockFS(int max_readahead, bool push_symlinks_i
 	 * googletest doesn't allow ASSERT_ in constructors, so we must throw
 	 * instead.
 	 */
-	if (mkdir("mountpoint" , 0644) && errno != EEXIST)
+	if (mkdir("mountpoint" , 0755) && errno != EEXIST)
 		throw(std::system_error(errno, std::system_category(),
 			"Couldn't make mountpoint directory"));
 
-	m_fuse_fd = open("/dev/fuse", O_RDWR);
+	m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
 	if (m_fuse_fd < 0)
 		throw(std::system_error(errno, std::system_category(),
 			"Couldn't open /dev/fuse"));
 	sprintf(fdstr, "%d", m_fuse_fd);
 
 	m_pid = getpid();
+	m_child_pid = -1;
 
 	build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
 	build_iovec(&iov, &iovlen, "fspath",
 		    __DECONST(void *, "mountpoint"), -1);
 	build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
 	build_iovec(&iov, &iovlen, "fd", fdstr, -1);
-	if (push_symlinks_in) {
-		const bool trueval = true;
-		build_iovec(&iov, &iovlen, "push_symlinks_in",
+	if (allow_other) {
+		build_iovec(&iov, &iovlen, "allow_other",
 			__DECONST(void*, &trueval), sizeof(bool));
 	}
 	if (default_permissions) {
-		const bool trueval = true;
 		build_iovec(&iov, &iovlen, "default_permissions",
 			__DECONST(void*, &trueval), sizeof(bool));
 	}
+	if (push_symlinks_in) {
+		build_iovec(&iov, &iovlen, "push_symlinks_in",
+			__DECONST(void*, &trueval), sizeof(bool));
+	}
 	if (nmount(iov, iovlen, 0))
 		throw(std::system_error(errno, std::system_category(),
 			"Couldn't mount filesystem"));
@@ -396,6 +400,8 @@ void MockFS::loop() {
 
 bool MockFS::pid_ok(pid_t pid) {
 	if (pid == m_pid) {
+		return (true);
+	} else if (pid == m_child_pid) {
 		return (true);
 	} else {
 		struct kinfo_proc *ki;

Modified: projects/fuse2/tests/sys/fs/fusefs/mockfs.hh
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/mockfs.hh	Thu Mar 21 19:52:50 2019	(r345382)
+++ projects/fuse2/tests/sys/fs/fusefs/mockfs.hh	Thu Mar 21 19:56:33 2019	(r345383)
@@ -208,12 +208,16 @@ class MockFS {
 	void read_request(mockfs_buf_in*);
 
 	public:
+	/* pid of child process, for two-process test cases */
+	pid_t m_child_pid;
+
 	/* Maximum size of a FUSE_WRITE write */
 	uint32_t m_max_write;
 
 	/* Create a new mockfs and mount it to a tempdir */
-	MockFS(int max_readahead, bool push_symlinks_in,
-		bool default_permissions, uint32_t flags);
+	MockFS(int max_readahead, bool allow_other,
+		bool default_permissions, bool push_symlinks_in,
+		uint32_t flags);
 	virtual ~MockFS();
 
 	/* Kill the filesystem daemon without unmounting the filesystem */

Modified: projects/fuse2/tests/sys/fs/fusefs/utils.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/utils.cc	Thu Mar 21 19:52:50 2019	(r345382)
+++ projects/fuse2/tests/sys/fs/fusefs/utils.cc	Thu Mar 21 19:56:33 2019	(r345383)
@@ -91,11 +91,25 @@ void FuseTest::SetUp() {
 	m_maxbcachebuf = val;
 
 	try {
-		m_mock = new MockFS(m_maxreadahead, m_push_symlinks_in,
-			m_default_permissions, m_init_flags);
+		m_mock = new MockFS(m_maxreadahead, m_allow_other,
+			m_default_permissions, m_push_symlinks_in,
+			m_init_flags);
 	} catch (std::system_error err) {
 		FAIL() << err.what();
 	}
+}
+
+void
+FuseTest::expect_access(uint64_t ino, mode_t access_mode, int error)
+{
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_ACCESS &&
+				in->header.nodeid == ino &&
+				in->body.access.mask == access_mode);
+		}, Eq(true)),
+		_)
+	).WillOnce(Invoke(ReturnErrno(error)));
 }
 
 void FuseTest::expect_getattr(uint64_t ino, uint64_t size)

Modified: projects/fuse2/tests/sys/fs/fusefs/utils.hh
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/utils.hh	Thu Mar 21 19:52:50 2019	(r345382)
+++ projects/fuse2/tests/sys/fs/fusefs/utils.hh	Thu Mar 21 19:56:33 2019	(r345383)
@@ -41,6 +41,7 @@ class FuseTest : public ::testing::Test {
 	protected:
 	uint32_t m_maxreadahead;
 	uint32_t m_init_flags;
+	bool m_allow_other;
 	bool m_default_permissions;
 	bool m_push_symlinks_in;
 	MockFS *m_mock = NULL;
@@ -56,6 +57,7 @@ class FuseTest : public ::testing::Test {
 		 */
 		m_maxreadahead(UINT_MAX),
 		m_init_flags(0),
+		m_allow_other(false),
 		m_default_permissions(false),
 		m_push_symlinks_in(false)
 	{}
@@ -66,6 +68,12 @@ class FuseTest : public ::testing::Test {
 		if (m_mock)
 			delete m_mock;
 	}
+
+	/*
+	 * Create an expectation that FUSE_ACCESS will be called oncde for the
+	 * given inode with the given access_mode, returning the given errno
+	 */
+	void expect_access(uint64_t ino, mode_t access_mode, int error);
 
 	/*
 	 * Create an expectation that FUSE_GETATTR will be called for the given



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