]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/allow_other.cc
fusefs: correctly set fuse_release_in.flags in an error path
[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/mman.h>
39 #include <sys/wait.h>
40 #include <fcntl.h>
41 #include <pwd.h>
42 #include <semaphore.h>
43 }
44
45 #include "mockfs.hh"
46 #include "utils.hh"
47
48 using namespace testing;
49
50 void sighandler(int __unused sig) {}
51
52 static void
53 get_unprivileged_uid(int *uid)
54 {
55         struct passwd *pw;
56
57         /* 
58          * First try "tests", Kyua's default unprivileged user.  XXX after
59          * GoogleTest gains a proper Kyua wrapper, get this with the Kyua API
60          */
61         pw = getpwnam("tests");
62         if (pw == NULL) {
63                 /* Fall back to "nobody" */
64                 pw = getpwnam("nobody");
65         }
66         if (pw == NULL)
67                 GTEST_SKIP() << "Test requires an unprivileged user";
68         *uid = pw->pw_uid;
69 }
70
71 class NoAllowOther: public FuseTest {
72
73 public:
74 /* Unprivileged user id */
75 int m_uid;
76
77 virtual void SetUp() {
78         if (geteuid() != 0) {
79                 GTEST_SKIP() << "This test must be run as root";
80         }
81         get_unprivileged_uid(&m_uid);
82         if (IsSkipped())
83                 return;
84
85         FuseTest::SetUp();
86 }
87 };
88
89 class AllowOther: public NoAllowOther {
90
91 public:
92 virtual void SetUp() {
93         m_allow_other = true;
94         NoAllowOther::SetUp();
95 }
96 };
97
98 TEST_F(AllowOther, allowed)
99 {
100         const char FULLPATH[] = "mountpoint/some_file.txt";
101         const char RELPATH[] = "some_file.txt";
102         uint64_t ino = 42;
103         int fd;
104         pid_t child;
105         sem_t *sem;
106         int mprot = PROT_READ | PROT_WRITE;
107         int mflags = MAP_ANON | MAP_SHARED;
108         
109         sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0);
110         ASSERT_NE(NULL, sem) << strerror(errno);
111         ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno);
112
113         if ((child = fork()) == 0) {
114                 /* In child */
115                 ASSERT_EQ(0, sem_wait(sem)) << strerror(errno);
116
117                 /* Drop privileges before accessing */
118                 if (0 != setreuid(-1, m_uid)) {
119                         perror("setreuid");
120                         _exit(1);
121                 }
122                 fd = open(FULLPATH, O_RDONLY);
123                 if (fd < 0) {
124                         perror("open");
125                         _exit(1);
126                 }
127                 _exit(0);
128
129                 /* Deliberately leak fd */
130         } else if (child > 0) {
131                 int child_status;
132
133                 /* 
134                  * In parent.  Cleanup must happen here, because it's still
135                  * privileged.
136                  */
137                 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
138                 expect_open(ino, 0, 1);
139                 expect_release(ino);
140                 /* Until the attr cache is working, we may send an additional
141                  * GETATTR */
142                 expect_getattr(ino, 0);
143                 m_mock->m_child_pid = child;
144                 /* Signal the child process to go */
145                 ASSERT_EQ(0, sem_post(sem)) << strerror(errno);
146
147                 wait(&child_status);
148                 ASSERT_EQ(0, WEXITSTATUS(child_status));
149         } else {
150                 FAIL() << strerror(errno);
151         }
152 }
153
154 TEST_F(NoAllowOther, disallowed)
155 {
156         const char FULLPATH[] = "mountpoint/some_file.txt";
157         int fd;
158         pid_t child;
159         sem_t *sem;
160         int mprot = PROT_READ | PROT_WRITE;
161         int mflags = MAP_ANON | MAP_SHARED;
162
163         sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0);
164         ASSERT_NE(NULL, sem) << strerror(errno);
165         ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno);
166
167         if ((child = fork()) == 0) {
168                 /* In child */
169                 ASSERT_EQ(0, sem_wait(sem)) << strerror(errno);
170
171                 /* Drop privileges before accessing */
172                 if (0 != setreuid(-1, m_uid)) {
173                         perror("setreuid");
174                         _exit(1);
175                 }
176                 fd = open(FULLPATH, O_RDONLY);
177                 if (fd >= 0) {
178                         fprintf(stderr, "open should've failed\n");
179                         _exit(1);
180                 } else if (errno != EPERM) {
181                         fprintf(stderr,
182                                 "Unexpected error: %s\n", strerror(errno));
183                         _exit(1);
184                 }
185                 _exit(0);
186
187                 /* Deliberately leak fd */
188         } else if (child > 0) {
189                 /* 
190                  * In parent.  Cleanup must happen here, because it's still
191                  * privileged.
192                  */
193                 m_mock->m_child_pid = child;
194                 /* Signal the child process to go */
195                 ASSERT_EQ(0, sem_post(sem)) << strerror(errno);
196                 int child_status;
197
198                 wait(&child_status);
199                 ASSERT_EQ(0, WEXITSTATUS(child_status));
200         } else {
201                 FAIL() << strerror(errno);
202         }
203 }