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