svn commit: r345383 - projects/fuse2/tests/sys/fs/fusefs
Alan Somers
asomers at FreeBSD.org
Thu Mar 21 19:56:35 UTC 2019
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
More information about the svn-src-projects
mailing list