gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/sticky.cc (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include <fcntl.h> 16 #include <grp.h> 17 #include <sys/prctl.h> 18 #include <sys/types.h> 19 #include <unistd.h> 20 21 #include <vector> 22 23 #include "gtest/gtest.h" 24 #include "absl/flags/flag.h" 25 #include "test/util/capability_util.h" 26 #include "test/util/file_descriptor.h" 27 #include "test/util/fs_util.h" 28 #include "test/util/temp_path.h" 29 #include "test/util/test_util.h" 30 #include "test/util/thread_util.h" 31 32 ABSL_FLAG(int32_t, scratch_uid, 65534, "first scratch UID"); 33 ABSL_FLAG(int32_t, scratch_gid, 65534, "first scratch GID"); 34 35 namespace gvisor { 36 namespace testing { 37 38 namespace { 39 40 TEST(StickyTest, StickyBitPermDenied) { 41 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); 42 43 const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 44 EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds()); 45 46 // After changing credentials below, we need to use an open fd to make 47 // modifications in the parent dir, because there is no guarantee that we will 48 // still have the ability to open it. 49 const FileDescriptor parent_fd = 50 ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY)); 51 ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds()); 52 ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds()); 53 ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds()); 54 55 // Drop privileges and change IDs only in child thread, or else this parent 56 // thread won't be able to open some log files after the test ends. 57 ScopedThread([&] { 58 // Drop privileges. 59 AutoCapability cap(CAP_FOWNER, false); 60 61 // Change EUID and EGID. 62 EXPECT_THAT( 63 syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1), 64 SyscallSucceeds()); 65 EXPECT_THAT( 66 syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid), -1), 67 SyscallSucceeds()); 68 69 EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"), 70 SyscallFailsWithErrno(EPERM)); 71 EXPECT_THAT(unlinkat(parent_fd.get(), "file", 0), 72 SyscallFailsWithErrno(EPERM)); 73 EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR), 74 SyscallFailsWithErrno(EPERM)); 75 EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), 76 SyscallFailsWithErrno(EPERM)); 77 }); 78 } 79 80 TEST(StickyTest, StickyBitSameUID) { 81 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); 82 83 const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 84 EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds()); 85 86 // After changing credentials below, we need to use an open fd to make 87 // modifications in the parent dir, because there is no guarantee that we will 88 // still have the ability to open it. 89 const FileDescriptor parent_fd = 90 ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY)); 91 ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds()); 92 ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds()); 93 ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds()); 94 95 // Drop privileges and change IDs only in child thread, or else this parent 96 // thread won't be able to open some log files after the test ends. 97 ScopedThread([&] { 98 // Drop privileges. 99 AutoCapability cap(CAP_FOWNER, false); 100 101 // Change EGID. 102 EXPECT_THAT( 103 syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1), 104 SyscallSucceeds()); 105 106 // We still have the same EUID. 107 EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"), 108 SyscallSucceeds()); 109 EXPECT_THAT(unlinkat(parent_fd.get(), "file2", 0), SyscallSucceeds()); 110 EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR), 111 SyscallSucceeds()); 112 EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), SyscallSucceeds()); 113 }); 114 } 115 116 TEST(StickyTest, StickyBitCapFOWNER) { 117 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); 118 119 const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 120 EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds()); 121 122 // After changing credentials below, we need to use an open fd to make 123 // modifications in the parent dir, because there is no guarantee that we will 124 // still have the ability to open it. 125 const FileDescriptor parent_fd = 126 ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY)); 127 ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds()); 128 ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds()); 129 ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds()); 130 131 // Drop privileges and change IDs only in child thread, or else this parent 132 // thread won't be able to open some log files after the test ends. 133 ScopedThread([&] { 134 // Set PR_SET_KEEPCAPS. 135 EXPECT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds()); 136 137 // Change EUID and EGID. 138 EXPECT_THAT( 139 syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1), 140 SyscallSucceeds()); 141 EXPECT_THAT( 142 syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid), -1), 143 SyscallSucceeds()); 144 145 EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, true)); 146 EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"), 147 SyscallSucceeds()); 148 EXPECT_THAT(unlinkat(parent_fd.get(), "file2", 0), SyscallSucceeds()); 149 EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR), 150 SyscallSucceeds()); 151 EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), SyscallSucceeds()); 152 }); 153 } 154 } // namespace 155 156 } // namespace testing 157 } // namespace gvisor