1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <sys/ioctl.h> 5 6 #include <string> 7 8 #include "capsicum.h" 9 #include "capsicum-test.h" 10 #include "syscalls.h" 11 12 // Check an open call works and close the resulting fd. 13 #define EXPECT_OPEN_OK(f) do { \ 14 SCOPED_TRACE(#f); \ 15 int _fd = f; \ 16 EXPECT_OK(_fd); \ 17 close(_fd); \ 18 } while (0) 19 20 static void CreateFile(const char *filename, const char *contents) { 21 int fd = open(filename, O_CREAT|O_RDWR, 0644); 22 EXPECT_OK(fd); 23 EXPECT_OK(write(fd, contents, strlen(contents))); 24 close(fd); 25 } 26 27 // Test openat(2) in a variety of sitations to ensure that it obeys Capsicum 28 // "strict relative" rules: 29 // 30 // 1. Use strict relative lookups in capability mode or when operating 31 // relative to a capability. 32 // 2. When performing strict relative lookups, absolute paths (including 33 // symlinks to absolute paths) are not allowed, nor are paths containing 34 // '..' components. 35 // 36 // These rules apply when: 37 // - the directory FD is a Capsicum capability 38 // - the process is in capability mode 39 // - the openat(2) operation includes the O_BENEATH flag. 40 FORK_TEST(Openat, Relative) { 41 int etc = open("/etc/", O_RDONLY); 42 EXPECT_OK(etc); 43 44 cap_rights_t r_base; 45 cap_rights_init(&r_base, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP, CAP_FCNTL, CAP_IOCTL); 46 cap_rights_t r_ro; 47 cap_rights_init(&r_ro, CAP_READ); 48 cap_rights_t r_rl; 49 cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP); 50 51 int etc_cap = dup(etc); 52 EXPECT_OK(etc_cap); 53 EXPECT_OK(cap_rights_limit(etc_cap, &r_ro)); 54 int etc_cap_ro = dup(etc); 55 EXPECT_OK(etc_cap_ro); 56 EXPECT_OK(cap_rights_limit(etc_cap_ro, &r_rl)); 57 int etc_cap_base = dup(etc); 58 EXPECT_OK(etc_cap_base); 59 EXPECT_OK(cap_rights_limit(etc_cap_base, &r_base)); 60 #ifdef HAVE_CAP_FCNTLS_LIMIT 61 // Also limit fcntl(2) subrights. 62 EXPECT_OK(cap_fcntls_limit(etc_cap_base, CAP_FCNTL_GETFL)); 63 #endif 64 #ifdef HAVE_CAP_IOCTLS_LIMIT 65 // Also limit ioctl(2) subrights. 66 cap_ioctl_t ioctl_nread = FIONREAD; 67 EXPECT_OK(cap_ioctls_limit(etc_cap_base, &ioctl_nread, 1)); 68 #endif 69 70 // openat(2) with regular file descriptors in non-capability mode 71 // Should Just Work (tm). 72 EXPECT_OPEN_OK(openat(etc, "/etc/passwd", O_RDONLY)); 73 EXPECT_OPEN_OK(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); 74 EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY)); 75 EXPECT_OPEN_OK(openat(etc, "../etc/passwd", O_RDONLY)); 76 77 // Lookups relative to capabilities should be strictly relative. 78 // When not in capability mode, we don't actually require CAP_LOOKUP. 79 EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY)); 80 EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY)); 81 82 // Performing openat(2) on a path with leading slash ignores 83 // the provided directory FD. 84 EXPECT_OPEN_OK(openat(etc_cap_ro, "/etc/passwd", O_RDONLY)); 85 EXPECT_OPEN_OK(openat(etc_cap_base, "/etc/passwd", O_RDONLY)); 86 // Relative lookups that go upward are not allowed. 87 EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY); 88 EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY); 89 90 // A file opened relative to a capability should itself be a capability. 91 int fd = openat(etc_cap_base, "passwd", O_RDONLY); 92 EXPECT_OK(fd); 93 cap_rights_t rights; 94 EXPECT_OK(cap_rights_get(fd, &rights)); 95 EXPECT_RIGHTS_IN(&rights, &r_base); 96 #ifdef HAVE_CAP_FCNTLS_LIMIT 97 cap_fcntl_t fcntls; 98 EXPECT_OK(cap_fcntls_get(fd, &fcntls)); 99 EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls); 100 #endif 101 #ifdef HAVE_CAP_IOCTLS_LIMIT 102 cap_ioctl_t ioctls[16]; 103 ssize_t nioctls; 104 memset(ioctls, 0, sizeof(ioctls)); 105 nioctls = cap_ioctls_get(fd, ioctls, 16); 106 EXPECT_OK(nioctls); 107 EXPECT_EQ(1, nioctls); 108 EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]); 109 #endif 110 close(fd); 111 112 // Enter capability mode; now ALL lookups are strictly relative. 113 EXPECT_OK(cap_enter()); 114 115 // Relative lookups on regular files or capabilities with CAP_LOOKUP 116 // ought to succeed. 117 EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY)); 118 EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY)); 119 EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY)); 120 121 // Lookup relative to capabilities without CAP_LOOKUP should fail. 122 EXPECT_NOTCAPABLE(openat(etc_cap, "passwd", O_RDONLY)); 123 124 // Absolute lookups should fail. 125 EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); 126 EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "/etc/passwd", O_RDONLY); 127 EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "/etc/passwd", O_RDONLY); 128 129 // Lookups containing '..' should fail in capability mode. 130 EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "../etc/passwd", O_RDONLY); 131 EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY); 132 EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY); 133 134 fd = openat(etc, "passwd", O_RDONLY); 135 EXPECT_OK(fd); 136 137 // A file opened relative to a capability should itself be a capability. 138 fd = openat(etc_cap_base, "passwd", O_RDONLY); 139 EXPECT_OK(fd); 140 EXPECT_OK(cap_rights_get(fd, &rights)); 141 EXPECT_RIGHTS_IN(&rights, &r_base); 142 close(fd); 143 144 fd = openat(etc_cap_ro, "passwd", O_RDONLY); 145 EXPECT_OK(fd); 146 EXPECT_OK(cap_rights_get(fd, &rights)); 147 EXPECT_RIGHTS_IN(&rights, &r_rl); 148 close(fd); 149 } 150 151 #define TOPDIR "cap_topdir" 152 #define SUBDIR TOPDIR "/subdir" 153 class OpenatTest : public ::testing::Test { 154 public: 155 // Build a collection of files, subdirs and symlinks: 156 // /tmp/cap_topdir/ 157 // /topfile 158 // /subdir/ 159 // /subdir/bottomfile 160 // /symlink.samedir -> topfile 161 // /dsymlink.samedir -> ./ 162 // /symlink.down -> subdir/bottomfile 163 // /dsymlink.down -> subdir/ 164 // /symlink.absolute_out -> /etc/passwd 165 // /dsymlink.absolute_out -> /etc/ 166 // /symlink.relative_in -> ../../tmp/cap_topdir/topfile 167 // /dsymlink.relative_in -> ../../tmp/cap_topdir/ 168 // /symlink.relative_out -> ../../etc/passwd 169 // /dsymlink.relative_out -> ../../etc/ 170 // /subdir/dsymlink.absolute_in -> /tmp/cap_topdir/ 171 // /subdir/dsymlink.up -> ../ 172 // /subdir/symlink.absolute_in -> /tmp/cap_topdir/topfile 173 // /subdir/symlink.up -> ../topfile 174 // (In practice, this is a little more complicated because tmpdir might 175 // not be "/tmp".) 176 OpenatTest() { 177 // Create a couple of nested directories 178 int rc = mkdir(TmpFile(TOPDIR), 0755); 179 EXPECT_OK(rc); 180 if (rc < 0) { 181 EXPECT_EQ(EEXIST, errno); 182 } 183 rc = mkdir(TmpFile(SUBDIR), 0755); 184 EXPECT_OK(rc); 185 if (rc < 0) { 186 EXPECT_EQ(EEXIST, errno); 187 } 188 189 // Figure out a path prefix (like "../..") that gets us to the root 190 // directory from TmpFile(TOPDIR). 191 const char *p = TmpFile(TOPDIR); // maybe "/tmp/somewhere/cap_topdir" 192 std::string dots2root = ".."; 193 while (*p++ != '\0') { 194 if (*p == '/') { 195 dots2root += "/.."; 196 } 197 } 198 199 // Create normal files in each. 200 CreateFile(TmpFile(TOPDIR "/topfile"), "Top-level file"); 201 CreateFile(TmpFile(SUBDIR "/bottomfile"), "File in subdirectory"); 202 203 // Create various symlinks to files. 204 EXPECT_OK(symlink("topfile", TmpFile(TOPDIR "/symlink.samedir"))); 205 EXPECT_OK(symlink("subdir/bottomfile", TmpFile(TOPDIR "/symlink.down"))); 206 EXPECT_OK(symlink(TmpFile(TOPDIR "/topfile"), TmpFile(SUBDIR "/symlink.absolute_in"))); 207 EXPECT_OK(symlink("/etc/passwd", TmpFile(TOPDIR "/symlink.absolute_out"))); 208 std::string dots2top = dots2root + TmpFile(TOPDIR "/topfile"); 209 EXPECT_OK(symlink(dots2top.c_str(), TmpFile(TOPDIR "/symlink.relative_in"))); 210 std::string dots2passwd = dots2root + "/etc/passwd"; 211 EXPECT_OK(symlink(dots2passwd.c_str(), TmpFile(TOPDIR "/symlink.relative_out"))); 212 EXPECT_OK(symlink("../topfile", TmpFile(SUBDIR "/symlink.up"))); 213 214 // Create various symlinks to directories. 215 EXPECT_OK(symlink("./", TmpFile(TOPDIR "/dsymlink.samedir"))); 216 EXPECT_OK(symlink("subdir/", TmpFile(TOPDIR "/dsymlink.down"))); 217 EXPECT_OK(symlink(TmpFile(TOPDIR "/"), TmpFile(SUBDIR "/dsymlink.absolute_in"))); 218 EXPECT_OK(symlink("/etc/", TmpFile(TOPDIR "/dsymlink.absolute_out"))); 219 std::string dots2cwd = dots2root + tmpdir + "/"; 220 EXPECT_OK(symlink(dots2cwd.c_str(), TmpFile(TOPDIR "/dsymlink.relative_in"))); 221 std::string dots2etc = dots2root + "/etc/"; 222 EXPECT_OK(symlink(dots2etc.c_str(), TmpFile(TOPDIR "/dsymlink.relative_out"))); 223 EXPECT_OK(symlink("../", TmpFile(SUBDIR "/dsymlink.up"))); 224 225 // Open directory FDs for those directories and for cwd. 226 dir_fd_ = open(TmpFile(TOPDIR), O_RDONLY); 227 EXPECT_OK(dir_fd_); 228 sub_fd_ = open(TmpFile(SUBDIR), O_RDONLY); 229 EXPECT_OK(sub_fd_); 230 cwd_ = openat(AT_FDCWD, ".", O_RDONLY); 231 EXPECT_OK(cwd_); 232 // Move into the directory for the test. 233 EXPECT_OK(fchdir(dir_fd_)); 234 } 235 ~OpenatTest() { 236 fchdir(cwd_); 237 close(cwd_); 238 close(sub_fd_); 239 close(dir_fd_); 240 unlink(TmpFile(SUBDIR "/symlink.up")); 241 unlink(TmpFile(SUBDIR "/symlink.absolute_in")); 242 unlink(TmpFile(TOPDIR "/symlink.absolute_out")); 243 unlink(TmpFile(TOPDIR "/symlink.relative_in")); 244 unlink(TmpFile(TOPDIR "/symlink.relative_out")); 245 unlink(TmpFile(TOPDIR "/symlink.down")); 246 unlink(TmpFile(TOPDIR "/symlink.samedir")); 247 unlink(TmpFile(SUBDIR "/dsymlink.up")); 248 unlink(TmpFile(SUBDIR "/dsymlink.absolute_in")); 249 unlink(TmpFile(TOPDIR "/dsymlink.absolute_out")); 250 unlink(TmpFile(TOPDIR "/dsymlink.relative_in")); 251 unlink(TmpFile(TOPDIR "/dsymlink.relative_out")); 252 unlink(TmpFile(TOPDIR "/dsymlink.down")); 253 unlink(TmpFile(TOPDIR "/dsymlink.samedir")); 254 unlink(TmpFile(SUBDIR "/bottomfile")); 255 unlink(TmpFile(TOPDIR "/topfile")); 256 rmdir(TmpFile(SUBDIR)); 257 rmdir(TmpFile(TOPDIR)); 258 } 259 260 // Check openat(2) policing that is common across capabilities, capability mode and O_BENEATH. 261 void CheckPolicing(int oflag) { 262 // OK for normal access. 263 EXPECT_OPEN_OK(openat(dir_fd_, "topfile", O_RDONLY|oflag)); 264 EXPECT_OPEN_OK(openat(dir_fd_, "subdir/bottomfile", O_RDONLY|oflag)); 265 EXPECT_OPEN_OK(openat(sub_fd_, "bottomfile", O_RDONLY|oflag)); 266 EXPECT_OPEN_OK(openat(sub_fd_, ".", O_RDONLY|oflag)); 267 268 // Can't open paths with ".." in them. 269 EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../topfile", O_RDONLY|oflag); 270 EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../subdir/bottomfile", O_RDONLY|oflag); 271 EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "..", O_RDONLY|oflag); 272 273 #ifdef HAVE_OPENAT_INTERMEDIATE_DOTDOT 274 // OK for dotdot lookups that don't escape the top directory 275 EXPECT_OPEN_OK(openat(dir_fd_, "subdir/../topfile", O_RDONLY|oflag)); 276 #endif 277 278 // Check that we can't escape the top directory by the cunning 279 // ruse of going via a subdirectory. 280 EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "subdir/../../etc/passwd", O_RDONLY|oflag); 281 282 // Should only be able to open symlinks that stay within the directory. 283 EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY|oflag)); 284 EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY|oflag)); 285 EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.absolute_out", O_RDONLY|oflag); 286 EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_in", O_RDONLY|oflag); 287 EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_out", O_RDONLY|oflag); 288 EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.absolute_in", O_RDONLY|oflag); 289 EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.up", O_RDONLY|oflag); 290 291 EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.samedir/topfile", O_RDONLY|oflag)); 292 EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.down/bottomfile", O_RDONLY|oflag)); 293 EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.absolute_out/passwd", O_RDONLY|oflag); 294 EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_in/topfile", O_RDONLY|oflag); 295 EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_out/passwd", O_RDONLY|oflag); 296 EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.absolute_in/topfile", O_RDONLY|oflag); 297 EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.up/topfile", O_RDONLY|oflag); 298 299 // Although recall that O_NOFOLLOW prevents symlink following in final component. 300 EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.samedir", O_RDONLY|O_NOFOLLOW|oflag)); 301 EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.down", O_RDONLY|O_NOFOLLOW|oflag)); 302 } 303 304 protected: 305 int dir_fd_; 306 int sub_fd_; 307 int cwd_; 308 }; 309 310 TEST_F(OpenatTest, WithCapability) { 311 // Any kind of symlink can be opened relative to an ordinary directory FD. 312 EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY)); 313 EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY)); 314 EXPECT_OPEN_OK(openat(dir_fd_, "symlink.absolute_out", O_RDONLY)); 315 EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_in", O_RDONLY)); 316 EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_out", O_RDONLY)); 317 EXPECT_OPEN_OK(openat(sub_fd_, "symlink.absolute_in", O_RDONLY)); 318 EXPECT_OPEN_OK(openat(sub_fd_, "symlink.up", O_RDONLY)); 319 320 // Now make both DFDs into Capsicum capabilities. 321 cap_rights_t r_rl; 322 cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP, CAP_FCHDIR); 323 EXPECT_OK(cap_rights_limit(dir_fd_, &r_rl)); 324 EXPECT_OK(cap_rights_limit(sub_fd_, &r_rl)); 325 CheckPolicing(0); 326 // Use of AT_FDCWD is independent of use of a capability. 327 // Can open paths starting with "/" against a capability dfd, because the dfd is ignored. 328 } 329 330 FORK_TEST_F(OpenatTest, InCapabilityMode) { 331 EXPECT_OK(cap_enter()); // Enter capability mode 332 CheckPolicing(0); 333 334 // Use of AT_FDCWD is banned in capability mode. 335 EXPECT_CAPMODE(openat(AT_FDCWD, "topfile", O_RDONLY)); 336 EXPECT_CAPMODE(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY)); 337 EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY)); 338 339 // Can't open paths starting with "/" in capability mode. 340 EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY); 341 EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY); 342 } 343 344 #if !defined(O_RESOLVE_BENEATH) && defined(O_BENEATH) 345 #define O_RESOLVE_BENEATH O_BENEATH 346 #endif 347 348 #ifdef O_RESOLVE_BENEATH 349 TEST_F(OpenatTest, WithFlag) { 350 CheckPolicing(O_RESOLVE_BENEATH); 351 352 // Check with AT_FDCWD. 353 EXPECT_OPEN_OK(openat(AT_FDCWD, "topfile", O_RDONLY|O_RESOLVE_BENEATH)); 354 EXPECT_OPEN_OK(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY|O_RESOLVE_BENEATH)); 355 356 // Can't open paths starting with "/" with O_RESOLVE_BENEATH specified. 357 EXPECT_OPENAT_FAIL_TRAVERSAL(AT_FDCWD, "/etc/passwd", O_RDONLY|O_RESOLVE_BENEATH); 358 EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY|O_RESOLVE_BENEATH); 359 EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY|O_RESOLVE_BENEATH); 360 } 361 362 FORK_TEST_F(OpenatTest, WithFlagInCapabilityMode) { 363 EXPECT_OK(cap_enter()); // Enter capability mode 364 CheckPolicing(O_RESOLVE_BENEATH); 365 } 366 #endif 367