// Copyright 2018 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include "gtest/gtest.h" #include "absl/strings/str_split.h" #include "test/util/cleanup.h" #include "test/util/fs_util.h" #include "test/util/linux_capability_util.h" #include "test/util/logging.h" #include "test/util/posix_error.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" namespace gvisor { namespace testing { namespace { // These tests are for both the sched_getaffinity(2) and sched_setaffinity(2) // syscalls. class AffinityTest : public ::testing::Test { protected: void SetUp() override { EXPECT_THAT( // Needs use the raw syscall to get the actual size. cpuset_size_ = syscall(SYS_sched_getaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask_), SyscallSucceeds()); // Lots of tests rely on having more than 1 logical processor available. EXPECT_GT(CPU_COUNT(&mask_), 1); EXPECT_GT(cpuset_size_, 0); EXPECT_LE(cpuset_size_, sizeof(cpu_set_t)); } static PosixError ClearLowestBit(cpu_set_t* mask, size_t cpus) { const size_t mask_size = CPU_ALLOC_SIZE(cpus); for (size_t n = 0; n < cpus; ++n) { if (CPU_ISSET_S(n, mask_size, mask)) { CPU_CLR_S(n, mask_size, mask); return NoError(); } } return PosixError(EINVAL, "No bit to clear, mask is empty"); } PosixError ClearLowestBit() { return ClearLowestBit(&mask_, CPU_SETSIZE); } // Stores the initial cpu mask for this process. cpu_set_t mask_ = {}; int cpuset_size_ = 0; }; // sched_getaffinity(2) is implemented. TEST_F(AffinityTest, SchedGetAffinityImplemented) { EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), SyscallSucceeds()); } // PID is not found. TEST_F(AffinityTest, SchedGetAffinityInvalidPID) { // Flaky, but it's tough to avoid a race condition when finding an unused pid EXPECT_THAT(sched_getaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_), SyscallFailsWithErrno(ESRCH)); } // PID is not found. TEST_F(AffinityTest, SchedSetAffinityInvalidPID) { // Flaky, but it's tough to avoid a race condition when finding an unused pid EXPECT_THAT(sched_setaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_), SyscallFailsWithErrno(ESRCH)); } TEST_F(AffinityTest, SchedSetAffinityZeroMask) { CPU_ZERO(&mask_); EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), SyscallFailsWithErrno(EINVAL)); } // N.B. This test case relies on cpuset_size_ larger than the actual number of // of all existing CPUs. Check your machine if the test fails. TEST_F(AffinityTest, SchedSetAffinityNonexistentCPUDropped) { // sched_setaffinity() is a no-op on platform/KVM SKIP_IF(GvisorPlatform() == Platform::kKVM); cpu_set_t mask = mask_; // Add a nonexistent CPU. // // The number needs to be larger than the possible number of CPU available, // but smaller than the number of the CPU that the kernel claims to support -- // it's implicitly returned by raw sched_getaffinity syscall. CPU_SET(cpuset_size_ * 8 - 1, &mask); EXPECT_THAT( // Use raw syscall because it will be rejected by the libc wrapper // otherwise. syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask), SyscallSucceeds()) << "failed with cpumask : " << CPUSetToString(mask) << ", cpuset_size_ : " << cpuset_size_; cpu_set_t newmask; EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask), SyscallSucceeds()); EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask)) << "got: " << CPUSetToString(newmask) << " != expected: " << CPUSetToString(mask_); } TEST_F(AffinityTest, SchedSetAffinityOnlyNonexistentCPUFails) { // sched_setaffinity() is a no-op on platform/KVM SKIP_IF(GvisorPlatform() == Platform::kKVM); // Make an empty cpu set. CPU_ZERO(&mask_); // Add a nonexistent CPU. // // The number needs to be larger than the possible number of CPU available, // but smaller than the number of the CPU that the kernel claims to support -- // it's implicitly returned by raw sched_getaffinity syscall. int cpu = cpuset_size_ * 8 - 1; if (cpu <= NumCPUs()) { GTEST_SKIP() << "Skipping test: cpu " << cpu << " exists"; } CPU_SET(cpu, &mask_); EXPECT_THAT( // Use raw syscall because it will be rejected by the libc wrapper // otherwise. syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask_), SyscallFailsWithErrno(EINVAL)); } TEST_F(AffinityTest, SchedSetAffinityInvalidSize) { EXPECT_GT(cpuset_size_, 0); // Not big enough. EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ - 1, &mask_), SyscallFailsWithErrno(EINVAL)); // Not a multiple of word size. EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ + 1, &mask_), SyscallFailsWithErrno(EINVAL)); } TEST_F(AffinityTest, Sanity) { // sched_setaffinity() is a no-op on platform/KVM SKIP_IF(GvisorPlatform() == Platform::kKVM); ASSERT_NO_ERRNO(ClearLowestBit()); EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), SyscallSucceeds()); cpu_set_t newmask; EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask), SyscallSucceeds()); EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask)) << "got: " << CPUSetToString(newmask) << " != expected: " << CPUSetToString(mask_); } TEST_F(AffinityTest, NewThread) { // sched_setaffinity() is a no-op on platform/KVM SKIP_IF(GvisorPlatform() == Platform::kKVM); SKIP_IF(CPU_COUNT(&mask_) < 3); ASSERT_NO_ERRNO(ClearLowestBit()); ASSERT_NO_ERRNO(ClearLowestBit()); EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), SyscallSucceeds()); ScopedThread([this]() { cpu_set_t child_mask; ASSERT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &child_mask), SyscallSucceeds()); ASSERT_TRUE(CPU_EQUAL(&child_mask, &mask_)) << "child cpu mask: " << CPUSetToString(child_mask) << " != parent cpu mask: " << CPUSetToString(mask_); }); } TEST_F(AffinityTest, ConsistentWithProcCpuInfo) { // Count how many cpus are shown in /proc/cpuinfo. std::string cpuinfo = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/cpuinfo")); int count = 0; for (auto const& line : absl::StrSplit(cpuinfo, '\n')) { if (absl::StartsWith(line, "processor")) { count++; } } EXPECT_GE(count, CPU_COUNT(&mask_)); } TEST_F(AffinityTest, ConsistentWithProcStat) { // Count how many cpus are shown in /proc/stat. std::string stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat")); int count = 0; for (auto const& line : absl::StrSplit(stat, '\n')) { if (absl::StartsWith(line, "cpu") && !absl::StartsWith(line, "cpu ")) { count++; } } EXPECT_GE(count, CPU_COUNT(&mask_)); } TEST_F(AffinityTest, SmallCpuMask) { const int num_cpus = NumCPUs(); const size_t mask_size = CPU_ALLOC_SIZE(num_cpus); cpu_set_t* mask = CPU_ALLOC(num_cpus); ASSERT_NE(mask, nullptr); const auto free_mask = Cleanup([&] { CPU_FREE(mask); }); CPU_ZERO_S(mask_size, mask); ASSERT_THAT(sched_getaffinity(0, mask_size, mask), SyscallSucceeds()); } #ifndef __Fuchsia__ // Fuchsia doesn't support caps. // Test that sched_setaffinity on another task owned by a different UID // requires CAP_SYS_NICE. TEST_F(AffinityTest, SetAffinityOtherUidRequiresCapSysNice) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE))); SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); int child_ready[2]; ASSERT_THAT(pipe(child_ready), SyscallSucceeds()); pid_t child = fork(); ASSERT_THAT(child, SyscallSucceeds()); if (child == 0) { constexpr int kUnprivilegedUid = 12345; close(child_ready[0]); TEST_PCHECK( setresuid(kUnprivilegedUid, kUnprivilegedUid, kUnprivilegedUid) == 0); char ready = 'r'; write(child_ready[1], &ready, 1); close(child_ready[1]); _exit(0); } close(child_ready[1]); char ready; ASSERT_THAT(read(child_ready[0], &ready, 1), SyscallSucceedsWithValue(1)); close(child_ready[0]); EXPECT_THAT(sched_setaffinity(child, sizeof(mask_), &mask_), SyscallSucceeds()); AutoCapability cap(CAP_SYS_NICE, false); EXPECT_THAT(sched_setaffinity(child, sizeof(mask_), &mask_), SyscallFailsWithErrno(EPERM)); int status; EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceedsWithValue(child)); EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << "status = " << status; } #endif // __Fuchsia__ TEST_F(AffinityTest, LargeCpuMask) { // sched_setaffinity() is a no-op on platform/KVM SKIP_IF(GvisorPlatform() == Platform::kKVM); // Allocate mask bigger than cpu_set_t normally allocates. const size_t cpus = CPU_SETSIZE * 8; const size_t mask_size = CPU_ALLOC_SIZE(cpus); cpu_set_t* large_mask = CPU_ALLOC(cpus); auto free_mask = Cleanup([large_mask] { CPU_FREE(large_mask); }); CPU_ZERO_S(mask_size, large_mask); // Check that get affinity with large mask works as expected. ASSERT_THAT(sched_getaffinity(/*pid=*/0, mask_size, large_mask), SyscallSucceeds()); EXPECT_TRUE(CPU_EQUAL(&mask_, large_mask)) << "got: " << CPUSetToString(*large_mask, cpus) << " != expected: " << CPUSetToString(mask_); // Check that set affinity with large mask works as expected. ASSERT_NO_ERRNO(ClearLowestBit(large_mask, cpus)); EXPECT_THAT(sched_setaffinity(/*pid=*/0, mask_size, large_mask), SyscallSucceeds()); cpu_set_t* new_mask = CPU_ALLOC(cpus); auto free_new_mask = Cleanup([new_mask] { CPU_FREE(new_mask); }); CPU_ZERO_S(mask_size, new_mask); EXPECT_THAT(sched_getaffinity(/*pid=*/0, mask_size, new_mask), SyscallSucceeds()); EXPECT_TRUE(CPU_EQUAL_S(mask_size, large_mask, new_mask)) << "got: " << CPUSetToString(*new_mask, cpus) << " != expected: " << CPUSetToString(*large_mask, cpus); } } // namespace } // namespace testing } // namespace gvisor