]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/allow_other.cc
fusefs: add a test case for the allow_other mount option
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / allow_other.cc
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 The FreeBSD Foundation
5  *
6  * This software was developed by BFF Storage Systems, LLC under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30
31 /*
32  * Tests for the "allow_other" mount option.  They must be in their own
33  * file so they can be run as root
34  */
35
36 extern "C" {
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #include <fcntl.h>
40 #include <pwd.h>
41 #include <signal.h>
42 }
43
44 #include "mockfs.hh"
45 #include "utils.hh"
46
47 using namespace testing;
48
49 void sighandler(int __unused sig) {}
50
51 static void
52 get_unprivileged_uid(int *uid)
53 {
54         struct passwd *pw;
55
56         /* 
57          * First try "tests", Kyua's default unprivileged user.  XXX after
58          * GoogleTest gains a proper Kyua wrapper, get this with the Kyua API
59          */
60         pw = getpwnam("tests");
61         if (pw == NULL) {
62                 /* Fall back to "nobody" */
63                 pw = getpwnam("nobody");
64         }
65         if (pw == NULL)
66                 GTEST_SKIP() << "Test requires an unprivileged user";
67         *uid = pw->pw_uid;
68 }
69
70 class NoAllowOther: public FuseTest {
71
72 public:
73 /* Unprivileged user id */
74 int m_uid;
75
76 virtual void SetUp() {
77         if (geteuid() != 0) {
78                 GTEST_SKIP() << "This test must be run as root";
79         }
80         get_unprivileged_uid(&m_uid);
81         if (IsSkipped())
82                 return;
83
84         FuseTest::SetUp();
85 }
86 };
87
88 class AllowOther: public NoAllowOther {
89
90 public:
91 virtual void SetUp() {
92         m_allow_other = true;
93         NoAllowOther::SetUp();
94 }
95 };
96
97 TEST_F(AllowOther, allowed)
98 {
99         const char FULLPATH[] = "mountpoint/some_file.txt";
100         const char RELPATH[] = "some_file.txt";
101         uint64_t ino = 42;
102         int fd;
103         pid_t child;
104         
105         signal(SIGUSR2, sighandler);
106
107         if ((child = fork()) == 0) {
108                 /* In child */
109                 pause();
110
111                 /* Drop privileges before accessing */
112                 if (0 != setreuid(-1, m_uid)) {
113                         perror("setreuid");
114                         _exit(1);
115                 }
116                 fd = open(FULLPATH, O_RDONLY);
117                 if (fd < 0) {
118                         perror("open");
119                         _exit(1);
120                 }
121                 _exit(0);
122
123                 /* Deliberately leak fd */
124         } else if (child > 0) {
125                 /* 
126                  * In parent.  Cleanup must happen here, because it's still
127                  * privileged.
128                  */
129                 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
130                 expect_open(ino, 0, 1);
131                 expect_release(ino, 1, 0, 0);
132                 /* Until the attr cache is working, we may send an additional
133                  * GETATTR */
134                 expect_getattr(ino, 0);
135                 m_mock->m_child_pid = child;
136                 /* Signal the child process to go */
137                 kill(child, SIGUSR2);
138                 int child_status;
139
140                 wait(&child_status);
141                 ASSERT_EQ(0, WEXITSTATUS(child_status));
142         } else {
143                 FAIL() << strerror(errno);
144         }
145 }
146
147 TEST_F(NoAllowOther, disallowed)
148 {
149         const char FULLPATH[] = "mountpoint/some_file.txt";
150         int fd;
151         pid_t child;
152         
153         signal(SIGUSR2, sighandler);
154
155         if ((child = fork()) == 0) {
156                 /* In child */
157                 pause();
158
159                 /* Drop privileges before accessing */
160                 if (0 != setreuid(-1, m_uid)) {
161                         perror("setreuid");
162                         _exit(1);
163                 }
164                 fd = open(FULLPATH, O_RDONLY);
165                 if (fd >= 0) {
166                         fprintf(stderr, "open should've failed\n");
167                         _exit(1);
168                 } else if (errno != EPERM) {
169                         fprintf(stderr,
170                                 "Unexpected error: %s\n", strerror(errno));
171                         _exit(1);
172                 }
173                 _exit(0);
174
175                 /* Deliberately leak fd */
176         } else if (child > 0) {
177                 /* 
178                  * In parent.  Cleanup must happen here, because it's still
179                  * privileged.
180                  */
181                 m_mock->m_child_pid = child;
182                 /* Signal the child process to go */
183                 kill(child, SIGUSR2);
184                 int child_status;
185
186                 wait(&child_status);
187                 ASSERT_EQ(0, WEXITSTATUS(child_status));
188         } else {
189                 FAIL() << strerror(errno);
190         }
191 }