From 09c01e67de16fcff9f5d2d41992fcbdba03478a4 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 28 Mar 2019 01:12:44 +0000 Subject: [PATCH] fusefs: deduplicate code in the allow_other test Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fusefs/allow_other.cc | 165 ++++++----------------------- tests/sys/fs/fusefs/utils.cc | 89 +++++++++++++++- tests/sys/fs/fusefs/utils.hh | 18 ++++ 3 files changed, 138 insertions(+), 134 deletions(-) diff --git a/tests/sys/fs/fusefs/allow_other.cc b/tests/sys/fs/fusefs/allow_other.cc index f8dfbb557d55..122463d7172e 100644 --- a/tests/sys/fs/fusefs/allow_other.cc +++ b/tests/sys/fs/fusefs/allow_other.cc @@ -35,11 +35,8 @@ extern "C" { #include -#include -#include #include -#include -#include +#include } #include "mockfs.hh" @@ -47,26 +44,8 @@ extern "C" { 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; -} +const static char FULLPATH[] = "mountpoint/some_file.txt"; +const static char RELPATH[] = "some_file.txt"; class NoAllowOther: public FuseTest { @@ -78,9 +57,6 @@ 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(); } @@ -97,119 +73,42 @@ virtual void 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; - sem_t *sem; - int mprot = PROT_READ | PROT_WRITE; - int mflags = MAP_ANON | MAP_SHARED; - - sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0); - ASSERT_NE(MAP_FAILED, sem) << strerror(errno); - ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno); + fork(true, [&] { + uint64_t ino = 42; - if ((child = fork()) == 0) { - /* In child */ - int err = 0; + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); + expect_open(ino, 0, 1); + expect_release(ino); + expect_getattr(ino, 0); + }, []() { + int fd; - ASSERT_EQ(0, sem_wait(sem)) << strerror(errno); - - /* Drop privileges before accessing */ - if (0 != setreuid(-1, m_uid)) { - perror("setreuid"); - err = 1; - goto out; + fd = open(FULLPATH, O_RDONLY); + if (fd < 0) { + perror("open"); + return(1); + } + return 0; } - fd = open(FULLPATH, O_RDONLY); - if (fd < 0) { - perror("open"); - err = 1; - } - -out: - sem_destroy(sem); - _exit(err); - /* Deliberately leak fd */ - } else if (child > 0) { - int child_status; - - /* - * 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); - /* 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 */ - ASSERT_EQ(0, sem_post(sem)) << strerror(errno); - - wait(&child_status); - ASSERT_EQ(0, WEXITSTATUS(child_status)); - } else { - FAIL() << strerror(errno); - } - munmap(sem, sizeof(*sem)); + ); } TEST_F(NoAllowOther, disallowed) { - const char FULLPATH[] = "mountpoint/some_file.txt"; - int fd; - pid_t child; - sem_t *sem; - int mprot = PROT_READ | PROT_WRITE; - int mflags = MAP_ANON | MAP_SHARED; + fork(true, [] { + }, []() { + int fd; - sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0); - ASSERT_NE(MAP_FAILED, sem) << strerror(errno); - ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno); - - if ((child = fork()) == 0) { - /* In child */ - int err = 0; - - ASSERT_EQ(0, sem_wait(sem)) << strerror(errno); - - /* Drop privileges before accessing */ - if (0 != setreuid(-1, m_uid)) { - perror("setreuid"); - err = 1; - goto out; + fd = open(FULLPATH, O_RDONLY); + if (fd >= 0) { + fprintf(stderr, "open should've failed\n"); + return(1); + } else if (errno != EPERM) { + fprintf(stderr, "Unexpected error: %s\n", + strerror(errno)); + return(1); + } + return 0; } - fd = open(FULLPATH, O_RDONLY); - if (fd >= 0) { - fprintf(stderr, "open should've failed\n"); - err = 1; - } else if (errno != EPERM) { - fprintf(stderr, - "Unexpected error: %s\n", strerror(errno)); - err = 1; - } - -out: - sem_destroy(sem); - _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 */ - ASSERT_EQ(0, sem_post(sem)) << strerror(errno); - int child_status; - - wait(&child_status); - ASSERT_EQ(0, WEXITSTATUS(child_status)); - } else { - FAIL() << strerror(errno); - } - munmap(sem, sizeof(*sem)); + ); } diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc index c6fc6ee105b4..e12ad80ba03a 100644 --- a/tests/sys/fs/fusefs/utils.cc +++ b/tests/sys/fs/fusefs/utils.cc @@ -28,12 +28,19 @@ * SUCH DAMAGE. */ +extern "C" { #include +#include #include #include +#include + +#include +#include +#include +} #include -#include #include "mockfs.hh" #include "utils.hh" @@ -241,6 +248,86 @@ void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize, }))); } +static void +get_unprivileged_uid(uid_t *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; +} + +void +FuseTest::fork(bool drop_privs, std::function parent_func, + std::function child_func) +{ + sem_t *sem; + int mprot = PROT_READ | PROT_WRITE; + int mflags = MAP_ANON | MAP_SHARED; + pid_t child; + uid_t uid; + + if (drop_privs) { + get_unprivileged_uid(&uid); + if (IsSkipped()) + return; + } + + sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0); + ASSERT_NE(MAP_FAILED, sem) << strerror(errno); + ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno); + + if ((child = ::fork()) == 0) { + /* In child */ + int err = 0; + + if (sem_wait(sem)) { + perror("sem_wait"); + err = 1; + goto out; + } + + if (drop_privs && 0 != setreuid(-1, uid)) { + perror("setreuid"); + err = 1; + goto out; + } + err = child_func(); + +out: + sem_destroy(sem); + _exit(err); + } else if (child > 0) { + int child_status; + + /* + * In parent. Cleanup must happen here, because it's still + * privileged. + */ + m_mock->m_child_pid = child; + ASSERT_NO_FATAL_FAILURE(parent_func()); + + /* Signal the child process to go */ + ASSERT_EQ(0, sem_post(sem)) << strerror(errno); + + wait(&child_status); + ASSERT_EQ(0, WEXITSTATUS(child_status)); + } else { + FAIL() << strerror(errno); + } + munmap(sem, sizeof(*sem)); +} + static void usage(char* progname) { fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); exit(2); diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh index 0669f34a051a..9b5e95cb0877 100644 --- a/tests/sys/fs/fusefs/utils.hh +++ b/tests/sys/fs/fusefs/utils.hh @@ -124,4 +124,22 @@ class FuseTest : public ::testing::Test { */ void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, uint32_t flags, const void *contents); + + /* + * Helper that runs code in a child process. + * + * First, parent_func runs in the parent process. + * Then, child_func runs in the child process, dropping privileges if + * desired. + * Finally, fusetest_fork returns. + * + * # Returns + * + * fusetest_fork will FAIL the test if child_func returns nonzero. + * It may SKIP the test, which the caller should detect with the + * IsSkipped() method. + */ + void fork(bool drop_privs, + std::function parent_func, + std::function child_func); };