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