1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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 extern "C" { 32 #include <fcntl.h> 33 #include <sys/socket.h> 34 #include <sys/un.h> 35 #include <semaphore.h> 36 } 37 38 #include "mockfs.hh" 39 #include "utils.hh" 40 41 using namespace testing; 42 43 #ifndef VNOVAL 44 #define VNOVAL (-1) /* Defined in sys/vnode.h */ 45 #endif 46 47 class Mknod: public FuseTest { 48 49 mode_t m_oldmask; 50 const static mode_t c_umask = 022; 51 52 public: 53 54 Mknod() { 55 m_oldmask = umask(c_umask); 56 } 57 58 virtual void SetUp() { 59 if (geteuid() != 0) { 60 GTEST_SKIP() << "Only root may use most mknod(2) variations"; 61 } 62 FuseTest::SetUp(); 63 } 64 65 virtual void TearDown() { 66 FuseTest::TearDown(); 67 (void)umask(m_oldmask); 68 } 69 70 /* Test an OK creation of a file with the given mode and device number */ 71 void expect_mknod(uint64_t parent_ino, const char* relpath, uint64_t ino, 72 mode_t mode, dev_t dev) 73 { 74 EXPECT_CALL(*m_mock, process( 75 ResultOf([=](auto in) { 76 const char *name = (const char*)in.body.bytes + 77 sizeof(fuse_mknod_in); 78 return (in.header.nodeid == parent_ino && 79 in.header.opcode == FUSE_MKNOD && 80 in.body.mknod.mode == mode && 81 in.body.mknod.rdev == (uint32_t)dev && 82 in.body.mknod.umask == c_umask && 83 (0 == strcmp(relpath, name))); 84 }, Eq(true)), 85 _) 86 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 87 SET_OUT_HEADER_LEN(out, entry); 88 out.body.entry.attr.mode = mode; 89 out.body.entry.nodeid = ino; 90 out.body.entry.entry_valid = UINT64_MAX; 91 out.body.entry.attr_valid = UINT64_MAX; 92 out.body.entry.attr.rdev = dev; 93 }))); 94 } 95 96 }; 97 98 class Mknod_7_11: public FuseTest { 99 public: 100 virtual void SetUp() { 101 m_kernel_minor_version = 11; 102 if (geteuid() != 0) { 103 GTEST_SKIP() << "Only root may use most mknod(2) variations"; 104 } 105 FuseTest::SetUp(); 106 } 107 108 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size) 109 { 110 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1); 111 } 112 113 /* Test an OK creation of a file with the given mode and device number */ 114 void expect_mknod(uint64_t parent_ino, const char* relpath, uint64_t ino, 115 mode_t mode, dev_t dev) 116 { 117 EXPECT_CALL(*m_mock, process( 118 ResultOf([=](auto in) { 119 const char *name = (const char*)in.body.bytes + 120 FUSE_COMPAT_MKNOD_IN_SIZE; 121 return (in.header.nodeid == parent_ino && 122 in.header.opcode == FUSE_MKNOD && 123 in.body.mknod.mode == mode && 124 in.body.mknod.rdev == (uint32_t)dev && 125 (0 == strcmp(relpath, name))); 126 }, Eq(true)), 127 _) 128 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 129 SET_OUT_HEADER_LEN(out, entry); 130 out.body.entry.attr.mode = mode; 131 out.body.entry.nodeid = ino; 132 out.body.entry.entry_valid = UINT64_MAX; 133 out.body.entry.attr_valid = UINT64_MAX; 134 out.body.entry.attr.rdev = dev; 135 }))); 136 } 137 138 }; 139 140 /* 141 * mknod(2) should be able to create block devices on a FUSE filesystem. Even 142 * though FreeBSD doesn't use block devices, this is useful when copying media 143 * from or preparing media for other operating systems. 144 */ 145 TEST_F(Mknod, blk) 146 { 147 const char FULLPATH[] = "mountpoint/some_node"; 148 const char RELPATH[] = "some_node"; 149 mode_t mode = S_IFBLK | 0755; 150 dev_t rdev = 0xfe00; /* /dev/vda's device number on Linux */ 151 uint64_t ino = 42; 152 153 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 154 .WillOnce(Invoke(ReturnErrno(ENOENT))); 155 expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev); 156 157 EXPECT_EQ(0, mknod(FULLPATH, mode, rdev)) << strerror(errno); 158 } 159 160 TEST_F(Mknod, chr) 161 { 162 const char FULLPATH[] = "mountpoint/some_node"; 163 const char RELPATH[] = "some_node"; 164 mode_t mode = S_IFCHR | 0755; 165 dev_t rdev = 54; /* /dev/fuse's device number */ 166 uint64_t ino = 42; 167 168 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 169 .WillOnce(Invoke(ReturnErrno(ENOENT))); 170 expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev); 171 172 EXPECT_EQ(0, mknod(FULLPATH, mode, rdev)) << strerror(errno); 173 } 174 175 /* 176 * The daemon is responsible for checking file permissions (unless the 177 * default_permissions mount option was used) 178 */ 179 TEST_F(Mknod, eperm) 180 { 181 const char FULLPATH[] = "mountpoint/some_node"; 182 const char RELPATH[] = "some_node"; 183 mode_t mode = S_IFIFO | 0755; 184 185 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 186 .WillOnce(Invoke(ReturnErrno(ENOENT))); 187 188 EXPECT_CALL(*m_mock, process( 189 ResultOf([=](auto in) { 190 const char *name = (const char*)in.body.bytes + 191 sizeof(fuse_mknod_in); 192 return (in.header.opcode == FUSE_MKNOD && 193 in.body.mknod.mode == mode && 194 (0 == strcmp(RELPATH, name))); 195 }, Eq(true)), 196 _) 197 ).WillOnce(Invoke(ReturnErrno(EPERM))); 198 EXPECT_NE(0, mkfifo(FULLPATH, mode)); 199 EXPECT_EQ(EPERM, errno); 200 } 201 202 TEST_F(Mknod, fifo) 203 { 204 const char FULLPATH[] = "mountpoint/some_node"; 205 const char RELPATH[] = "some_node"; 206 mode_t mode = S_IFIFO | 0755; 207 dev_t rdev = VNOVAL; /* Fifos don't have device numbers */ 208 uint64_t ino = 42; 209 210 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 211 .WillOnce(Invoke(ReturnErrno(ENOENT))); 212 expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev); 213 214 EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno); 215 } 216 217 /* 218 * Create a unix-domain socket. 219 * 220 * This test case doesn't actually need root privileges. 221 */ 222 TEST_F(Mknod, socket) 223 { 224 const char FULLPATH[] = "mountpoint/some_node"; 225 const char RELPATH[] = "some_node"; 226 mode_t mode = S_IFSOCK | 0755; 227 struct sockaddr_un sa; 228 int fd; 229 dev_t rdev = -1; /* Really it's a don't care */ 230 uint64_t ino = 42; 231 232 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 233 .WillOnce(Invoke(ReturnErrno(ENOENT))); 234 expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev); 235 236 fd = socket(AF_UNIX, SOCK_STREAM, 0); 237 ASSERT_LE(0, fd) << strerror(errno); 238 sa.sun_family = AF_UNIX; 239 strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path)); 240 sa.sun_len = sizeof(FULLPATH); 241 ASSERT_EQ(0, bind(fd, (struct sockaddr*)&sa, sizeof(sa))) 242 << strerror(errno); 243 244 leak(fd); 245 } 246 247 /* 248 * Nothing bad should happen if the server returns the parent's inode number 249 * for the newly created file. Regression test for bug 263662. 250 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263662 251 */ 252 TEST_F(Mknod, parent_inode) 253 { 254 const char FULLPATH[] = "mountpoint/parent/some_node"; 255 const char PPATH[] = "parent"; 256 const char RELPATH[] = "some_node"; 257 mode_t mode = S_IFSOCK | 0755; 258 struct sockaddr_un sa; 259 sem_t sem; 260 int fd; 261 dev_t rdev = -1; /* Really it's a don't care */ 262 uint64_t ino = 42; 263 264 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 265 266 expect_lookup(PPATH, ino, S_IFDIR | 0755, 0, 1); 267 EXPECT_LOOKUP(ino, RELPATH) 268 .WillOnce(Invoke(ReturnErrno(ENOENT))); 269 expect_mknod(ino, RELPATH, ino, mode, rdev); 270 expect_forget(ino, 1, &sem); 271 272 fd = socket(AF_UNIX, SOCK_STREAM, 0); 273 ASSERT_LE(0, fd) << strerror(errno); 274 sa.sun_family = AF_UNIX; 275 strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path)); 276 sa.sun_len = sizeof(FULLPATH); 277 ASSERT_EQ(-1, bind(fd, (struct sockaddr*)&sa, sizeof(sa))); 278 ASSERT_EQ(EIO, errno); 279 280 leak(fd); 281 sem_wait(&sem); 282 sem_destroy(&sem); 283 } 284 285 /* 286 * fusefs(5) lacks VOP_WHITEOUT support. No bugzilla entry, because that's a 287 * feature, not a bug 288 */ 289 TEST_F(Mknod, DISABLED_whiteout) 290 { 291 const char FULLPATH[] = "mountpoint/some_node"; 292 const char RELPATH[] = "some_node"; 293 mode_t mode = S_IFWHT | 0755; 294 dev_t rdev = VNOVAL; /* whiteouts don't have device numbers */ 295 uint64_t ino = 42; 296 297 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 298 .WillOnce(Invoke(ReturnErrno(ENOENT))); 299 expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev); 300 301 EXPECT_EQ(0, mknod(FULLPATH, mode, 0)) << strerror(errno); 302 } 303 304 /* A server built at protocol version 7.11 or earlier can still use mknod */ 305 TEST_F(Mknod_7_11, fifo) 306 { 307 const char FULLPATH[] = "mountpoint/some_node"; 308 const char RELPATH[] = "some_node"; 309 mode_t mode = S_IFIFO | 0755; 310 dev_t rdev = VNOVAL; 311 uint64_t ino = 42; 312 313 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 314 .WillOnce(Invoke(ReturnErrno(ENOENT))); 315 expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev); 316 317 EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno); 318 } 319