diff options
Diffstat (limited to 'test')
33 files changed, 1072 insertions, 430 deletions
diff --git a/test/benchmarks/base/startup_test.go b/test/benchmarks/base/startup_test.go index 05a43ad17..197241622 100644 --- a/test/benchmarks/base/startup_test.go +++ b/test/benchmarks/base/startup_test.go @@ -34,15 +34,19 @@ func BenchmarkStartupEmpty(b *testing.B) { defer machine.CleanUp() ctx := context.Background() + b.StopTimer() + b.ResetTimer() for i := 0; i < b.N; i++ { harness.DebugLog(b, "Running container: %d", i) container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) - if _, err := container.Run(ctx, dockerutil.RunOpts{ + b.StartTimer() + if err := container.Spawn(ctx, dockerutil.RunOpts{ Image: "benchmarks/alpine", - }, "true"); err != nil { - b.Fatalf("failed to run container: %v", err) + }, "sleep", "100"); err != nil { + b.Fatalf("failed to start container: %v", err) } + b.StopTimer() + container.CleanUp(ctx) harness.DebugLog(b, "Ran container: %d", i) } } diff --git a/test/benchmarks/tools/fio_test.go b/test/benchmarks/tools/fio_test.go index a98277150..3b1f852ce 100644 --- a/test/benchmarks/tools/fio_test.go +++ b/test/benchmarks/tools/fio_test.go @@ -86,7 +86,7 @@ func TestFio(t *testing.T) { fio := Fio{} // WriteBandwidth. got, err := fio.parseBandwidth(sampleData, false) - var want float64 = 1753471.0 * 1024 + want := 1753471.0 * 1024 if err != nil { t.Fatalf("parse failed with err: %v", err) } else if got != want { diff --git a/test/perf/BUILD b/test/perf/BUILD index e43c5eff9..59923da47 100644 --- a/test/perf/BUILD +++ b/test/perf/BUILD @@ -164,6 +164,20 @@ syscall_test( syscall_test( size = "large", debug = False, + test = "//test/perf/linux:verity_randread_benchmark", + vfs1 = False, +) + +syscall_test( + size = "large", + debug = False, test = "//test/perf/linux:verity_open_read_close_benchmark", vfs1 = False, ) + +syscall_test( + size = "large", + debug = False, + test = "//test/perf/linux:verity_stat_benchmark", + vfs1 = False, +) diff --git a/test/perf/linux/BUILD b/test/perf/linux/BUILD index fed5778df..020a69ab5 100644 --- a/test/perf/linux/BUILD +++ b/test/perf/linux/BUILD @@ -426,6 +426,25 @@ cc_binary( ) cc_binary( + name = "verity_randread_benchmark", + testonly = 1, + srcs = [ + "verity_randread_benchmark.cc", + ], + deps = [ + gbenchmark, + gtest, + "//test/util:capability_util", + "//test/util:fs_util", + "//test/util:logging", + "//test/util:temp_path", + "//test/util:test_main", + "//test/util:test_util", + "//test/util:verity_util", + ], +) + +cc_binary( name = "verity_open_read_close_benchmark", testonly = 1, srcs = [ @@ -443,3 +462,23 @@ cc_binary( "//test/util:verity_util", ], ) + +cc_binary( + name = "verity_stat_benchmark", + testonly = 1, + srcs = [ + "verity_stat_benchmark.cc", + ], + deps = [ + gbenchmark, + gtest, + "//test/util:capability_util", + "//test/util:fs_util", + "//test/util:logging", + "//test/util:temp_path", + "//test/util:test_main", + "//test/util:test_util", + "//test/util:verity_util", + "@com_google_absl//absl/strings", + ], +) diff --git a/test/perf/linux/randread_benchmark.cc b/test/perf/linux/randread_benchmark.cc index b0eb8c24e..11b56a8cb 100644 --- a/test/perf/linux/randread_benchmark.cc +++ b/test/perf/linux/randread_benchmark.cc @@ -85,7 +85,7 @@ void BM_RandRead(benchmark::State& state) { unsigned int seed = 1; for (auto _ : state) { TEST_CHECK(PreadFd(fd.get(), buf.data(), buf.size(), - rand_r(&seed) % kFileSize) == size); + rand_r(&seed) % (kFileSize - buf.size())) == size); } state.SetBytesProcessed(static_cast<int64_t>(size) * diff --git a/test/perf/linux/verity_randread_benchmark.cc b/test/perf/linux/verity_randread_benchmark.cc new file mode 100644 index 000000000..4178cfad8 --- /dev/null +++ b/test/perf/linux/verity_randread_benchmark.cc @@ -0,0 +1,108 @@ +// Copyright 2021 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 <fcntl.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <unistd.h> + +#include "gtest/gtest.h" +#include "benchmark/benchmark.h" +#include "test/util/logging.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" +#include "test/util/verity_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +// Create a 1GB file that will be read from at random positions. This should +// invalid any performance gains from caching. +const uint64_t kFileSize = Megabytes(1024); + +// How many bytes to write at once to initialize the file used to read from. +const uint32_t kWriteSize = 65536; + +// Largest benchmarked read unit. +const uint32_t kMaxRead = Megabytes(64); + +// Global test state, initialized once per process lifetime. +struct GlobalState { + explicit GlobalState() { + // Mount a tmpfs file system to be wrapped by a verity fs. + tmp_dir_ = TempPath::CreateDir().ValueOrDie(); + TEST_CHECK(mount("", tmp_dir_.path().c_str(), "tmpfs", 0, "") == 0); + file_ = TempPath::CreateFileIn(tmp_dir_.path()).ValueOrDie(); + filename_ = std::string(Basename(file_.path())); + + FileDescriptor fd = Open(file_.path(), O_WRONLY).ValueOrDie(); + + // Try to minimize syscalls by using maximum size writev() requests. + std::vector<char> buffer(kWriteSize); + RandomizeBuffer(buffer.data(), buffer.size()); + const std::vector<std::vector<struct iovec>> iovecs_list = + GenerateIovecs(kFileSize + kMaxRead, buffer.data(), buffer.size()); + for (const auto& iovecs : iovecs_list) { + TEST_CHECK(writev(fd.get(), iovecs.data(), iovecs.size()) >= 0); + } + verity_dir_ = + MountVerity(tmp_dir_.path(), {EnableTarget(filename_, O_RDONLY)}) + .ValueOrDie(); + } + TempPath tmp_dir_; + TempPath file_; + std::string verity_dir_; + std::string filename_; +}; + +GlobalState& GetGlobalState() { + // This gets created only once throughout the lifetime of the process. + // Use a dynamically allocated object (that is never deleted) to avoid order + // of destruction of static storage variables issues. + static GlobalState* const state = + // The actual file size is the maximum random seek range (kFileSize) + the + // maximum read size so we can read that number of bytes at the end of the + // file. + new GlobalState(); + return *state; +} + +void BM_VerityRandRead(benchmark::State& state) { + const int size = state.range(0); + + GlobalState& global_state = GetGlobalState(); + FileDescriptor verity_fd = ASSERT_NO_ERRNO_AND_VALUE(Open( + JoinPath(global_state.verity_dir_, global_state.filename_), O_RDONLY)); + std::vector<char> buf(size); + + unsigned int seed = 1; + for (auto _ : state) { + TEST_CHECK(PreadFd(verity_fd.get(), buf.data(), buf.size(), + rand_r(&seed) % kFileSize) == size); + } + + state.SetBytesProcessed(static_cast<int64_t>(size) * + static_cast<int64_t>(state.iterations())); +} + +BENCHMARK(BM_VerityRandRead)->Range(1, kMaxRead)->UseRealTime(); + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/perf/linux/verity_stat_benchmark.cc b/test/perf/linux/verity_stat_benchmark.cc new file mode 100644 index 000000000..b43a9e266 --- /dev/null +++ b/test/perf/linux/verity_stat_benchmark.cc @@ -0,0 +1,84 @@ +// Copyright 2021 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 <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <algorithm> +#include <vector> + +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "benchmark/benchmark.h" +#include "test/util/fs_util.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" +#include "test/util/verity_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +// Creates a file in a nested directory hierarchy at least `depth` directories +// deep, and stats that file multiple times. +void BM_VerityStat(benchmark::State& state) { + // Create nested directories with given depth. + int depth = state.range(0); + + // Mount a tmpfs file system to be wrapped by a verity fs. + TempPath top_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + TEST_CHECK(mount("", top_dir.path().c_str(), "tmpfs", 0, "") == 0); + std::string dir_path = top_dir.path(); + std::string child_path = ""; + std::vector<EnableTarget> targets; + + while (depth-- > 0) { + // Don't use TempPath because it will make paths too long to use. + // + // The top_dir destructor will clean up this whole tree. + dir_path = JoinPath(dir_path, absl::StrCat(depth)); + ASSERT_NO_ERRNO(Mkdir(dir_path, 0755)); + child_path = JoinPath(child_path, Basename(dir_path)); + targets.emplace_back(EnableTarget(child_path, O_RDONLY)); + } + + // Create the file that will be stat'd. + const TempPath file = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir_path)); + + targets.emplace_back( + EnableTarget(JoinPath(child_path, Basename(file.path())), O_RDONLY)); + + // Reverse the targets because verity should be enabled from the lowest level. + std::reverse(targets.begin(), targets.end()); + + std::string verity_dir = + TEST_CHECK_NO_ERRNO_AND_VALUE(MountVerity(top_dir.path(), targets)); + + struct stat st; + for (auto _ : state) { + ASSERT_THAT(stat(JoinPath(verity_dir, targets[0].path).c_str(), &st), + SyscallSucceeds()); + } +} + +BENCHMARK(BM_VerityStat)->Range(1, 100)->UseRealTime(); + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 7129a797b..01ee432cb 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -7,6 +7,11 @@ package( exports_files( [ + "packet_socket.cc", + "packet_socket_raw.cc", + "raw_socket.cc", + "raw_socket_hdrincl.cc", + "raw_socket_icmp.cc", "socket.cc", "socket_inet_loopback.cc", "socket_inet_loopback_isolated.cc", @@ -1443,6 +1448,7 @@ cc_binary( deps = [ ":unix_domain_socket_test_util", "//test/util:capability_util", + "//test/util:cleanup", "//test/util:file_descriptor", "//test/util:socket_util", "@com_google_absl//absl/base:core_headers", @@ -1461,6 +1467,7 @@ cc_binary( deps = [ ":unix_domain_socket_test_util", "//test/util:capability_util", + "//test/util:cleanup", "//test/util:file_descriptor", "//test/util:socket_util", "@com_google_absl//absl/base:core_headers", @@ -4173,9 +4180,11 @@ cc_binary( linkstatic = 1, deps = [ "//test/util:capability_util", + "//test/util:signal_util", "//test/util:temp_path", - "//test/util:test_main", "//test/util:test_util", + "//test/util:thread_util", + "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", ], ) diff --git a/test/syscalls/linux/cgroup.cc b/test/syscalls/linux/cgroup.cc index f29891571..ca23dfeee 100644 --- a/test/syscalls/linux/cgroup.cc +++ b/test/syscalls/linux/cgroup.cc @@ -279,6 +279,23 @@ TEST(Cgroup, UnmountRepeated) { EXPECT_THAT(umount(c.Path().c_str()), SyscallFailsWithErrno(EINVAL)); } +TEST(Cgroup, Create) { + SKIP_IF(!CgroupsAvailable()); + Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); + Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); + ASSERT_NO_ERRNO(c.CreateChild("child1")); + EXPECT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(Exists(c.Path()))); +} + +TEST(Cgroup, SubcontainerInitiallyEmpty) { + SKIP_IF(!CgroupsAvailable()); + Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); + Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); + Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child1")); + auto procs = ASSERT_NO_ERRNO_AND_VALUE(child.Procs()); + EXPECT_TRUE(procs.empty()); +} + TEST(MemoryCgroup, MemoryUsageInBytes) { SKIP_IF(!CgroupsAvailable()); diff --git a/test/syscalls/linux/link.cc b/test/syscalls/linux/link.cc index 4f9ca1a65..8b208f99a 100644 --- a/test/syscalls/linux/link.cc +++ b/test/syscalls/linux/link.cc @@ -142,7 +142,8 @@ TEST(LinkTest, OldnameIsEmpty) { TEST(LinkTest, OldnameDoesNotExist) { const std::string oldname = NewTempAbsPath(); const std::string newname = NewTempAbsPath(); - EXPECT_THAT(link("", newname.c_str()), SyscallFailsWithErrno(ENOENT)); + EXPECT_THAT(link(oldname.c_str(), newname.c_str()), + SyscallFailsWithErrno(ENOENT)); } TEST(LinkTest, NewnameCannotExist) { diff --git a/test/syscalls/linux/lseek.cc b/test/syscalls/linux/lseek.cc index d4f89527c..dbc21833f 100644 --- a/test/syscalls/linux/lseek.cc +++ b/test/syscalls/linux/lseek.cc @@ -121,7 +121,8 @@ TEST(LseekTest, InvalidFD) { } TEST(LseekTest, DirCurEnd) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/tmp", O_RDONLY)); + const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(GetAbsoluteTestTmpdir().c_str(), O_RDONLY)); ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); } diff --git a/test/syscalls/linux/memfd.cc b/test/syscalls/linux/memfd.cc index 4a450742b..dbd1c93ae 100644 --- a/test/syscalls/linux/memfd.cc +++ b/test/syscalls/linux/memfd.cc @@ -445,9 +445,10 @@ TEST(MemfdTest, SealsAreInodeLevelProperties) { // Tmpfs files also support seals, but are created with F_SEAL_SEAL. TEST(MemfdTest, TmpfsFilesHaveSealSeal) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs("/tmp"))); + std::string tmpdir = GetAbsoluteTestTmpdir(); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(tmpdir.c_str()))); const TempPath tmpfs_file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn("/tmp")); + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(tmpdir.c_str())); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfs_file.path(), O_RDWR, 0644)); EXPECT_THAT(fcntl(fd.get(), F_GET_SEALS), diff --git a/test/syscalls/linux/mmap.cc b/test/syscalls/linux/mmap.cc index 93a6d9cde..fda176261 100644 --- a/test/syscalls/linux/mmap.cc +++ b/test/syscalls/linux/mmap.cc @@ -793,6 +793,19 @@ class MMapFileTest : public MMapTest { ASSERT_THAT(unlink(filename_.c_str()), SyscallSucceeds()); } + bool FSSupportsMap() const { + bool supported = true; + void* ret = mmap(nullptr, 1, PROT_NONE, MAP_PRIVATE, fd_.get(), 0); + if (ret == MAP_FAILED && errno != ENODEV) { + supported = false; + } + if (ret != MAP_FAILED) { + munmap(ret, 1); + } + + return supported; + } + ssize_t Read(char* buf, size_t count) { ssize_t len = 0; do { @@ -840,12 +853,14 @@ class MMapFileParamTest // MAP_POPULATE allowed. // There isn't a good way to verify it actually did anything. TEST_P(MMapFileParamTest, MapPopulate) { + SKIP_IF(!FSSupportsMap()); ASSERT_THAT(Map(0, kPageSize, prot(), flags() | MAP_POPULATE, fd_.get(), 0), SyscallSucceeds()); } // MAP_POPULATE on a short file. TEST_P(MMapFileParamTest, MapPopulateShort) { + SKIP_IF(!FSSupportsMap()); ASSERT_THAT( Map(0, 2 * kPageSize, prot(), flags() | MAP_POPULATE, fd_.get(), 0), SyscallSucceeds()); @@ -853,6 +868,7 @@ TEST_P(MMapFileParamTest, MapPopulateShort) { // Read contents from mapped file. TEST_F(MMapFileTest, Read) { + SKIP_IF(!FSSupportsMap()); size_t len = strlen(kFileContents); ASSERT_EQ(len, Write(kFileContents, len)); @@ -866,6 +882,7 @@ TEST_F(MMapFileTest, Read) { // Map at an offset. TEST_F(MMapFileTest, MapOffset) { + SKIP_IF(!FSSupportsMap()); ASSERT_THAT(lseek(fd_.get(), kPageSize, SEEK_SET), SyscallSucceeds()); size_t len = strlen(kFileContents); @@ -881,6 +898,7 @@ TEST_F(MMapFileTest, MapOffset) { } TEST_F(MMapFileTest, MapOffsetBeyondEnd) { + SKIP_IF(!FSSupportsMap()); SetupGvisorDeathTest(); uintptr_t addr; @@ -897,6 +915,7 @@ TEST_F(MMapFileTest, MapOffsetBeyondEnd) { // Verify mmap fails when sum of length and offset overflows. TEST_F(MMapFileTest, MapLengthPlusOffsetOverflows) { + SKIP_IF(!FSSupportsMap()); const size_t length = static_cast<size_t>(-kPageSize); const off_t offset = kPageSize; ASSERT_THAT(Map(0, length, PROT_READ, MAP_PRIVATE, fd_.get(), offset), @@ -905,6 +924,7 @@ TEST_F(MMapFileTest, MapLengthPlusOffsetOverflows) { // MAP_PRIVATE PROT_WRITE is allowed on read-only FDs. TEST_F(MMapFileTest, WritePrivateOnReadOnlyFd) { + SKIP_IF(!FSSupportsMap()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_RDONLY)); @@ -921,6 +941,7 @@ TEST_F(MMapFileTest, WritePrivateOnReadOnlyFd) { // MAP_SHARED PROT_WRITE not allowed on read-only FDs. TEST_F(MMapFileTest, WriteSharedOnReadOnlyFd) { + SKIP_IF(!FSSupportsMap()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_RDONLY)); @@ -932,6 +953,7 @@ TEST_F(MMapFileTest, WriteSharedOnReadOnlyFd) { // Mmap not allowed on O_PATH FDs. TEST_F(MMapFileTest, MmapFileWithOpath) { + SKIP_IF(!FSSupportsMap()); SKIP_IF(IsRunningWithVFS1()); const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const FileDescriptor fd = @@ -944,6 +966,7 @@ TEST_F(MMapFileTest, MmapFileWithOpath) { // The FD must be readable. TEST_P(MMapFileParamTest, WriteOnlyFd) { + SKIP_IF(!FSSupportsMap()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_WRONLY)); @@ -955,6 +978,7 @@ TEST_P(MMapFileParamTest, WriteOnlyFd) { // Overwriting the contents of a file mapped MAP_SHARED PROT_READ // should cause the new data to be reflected in the mapping. TEST_F(MMapFileTest, ReadSharedConsistentWithOverwrite) { + SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); @@ -994,6 +1018,7 @@ TEST_F(MMapFileTest, ReadSharedConsistentWithOverwrite) { // Partially overwriting a file mapped MAP_SHARED PROT_READ should be reflected // in the mapping. TEST_F(MMapFileTest, ReadSharedConsistentWithPartialOverwrite) { + SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); @@ -1034,6 +1059,7 @@ TEST_F(MMapFileTest, ReadSharedConsistentWithPartialOverwrite) { // Overwriting a file mapped MAP_SHARED PROT_READ should be reflected in the // mapping and the file. TEST_F(MMapFileTest, ReadSharedConsistentWithWriteAndFile) { + SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); @@ -1077,6 +1103,7 @@ TEST_F(MMapFileTest, ReadSharedConsistentWithWriteAndFile) { // Write data to mapped file. TEST_F(MMapFileTest, WriteShared) { + SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), @@ -1101,6 +1128,7 @@ TEST_F(MMapFileTest, WriteShared) { // Write data to portion of mapped page beyond the end of the file. // These writes are not reflected in the file. TEST_F(MMapFileTest, WriteSharedBeyondEnd) { + SKIP_IF(!FSSupportsMap()); // The file is only half of a page. We map an entire page. Writes to the // end of the mapping must not be reflected in the file. uintptr_t addr; @@ -1137,6 +1165,7 @@ TEST_F(MMapFileTest, WriteSharedBeyondEnd) { // The portion of a mapped page that becomes part of the file after a truncate // is reflected in the file. TEST_F(MMapFileTest, WriteSharedTruncateUp) { + SKIP_IF(!FSSupportsMap()); // The file is only half of a page. We map an entire page. Writes to the // end of the mapping must not be reflected in the file. uintptr_t addr; @@ -1174,6 +1203,7 @@ TEST_F(MMapFileTest, WriteSharedTruncateUp) { } TEST_F(MMapFileTest, ReadSharedTruncateDownThenUp) { + SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); @@ -1213,6 +1243,7 @@ TEST_F(MMapFileTest, ReadSharedTruncateDownThenUp) { } TEST_F(MMapFileTest, WriteSharedTruncateDownThenUp) { + SKIP_IF(!FSSupportsMap()); // The file is only half of a page. We map an entire page. Writes to the // end of the mapping must not be reflected in the file. uintptr_t addr; @@ -1247,6 +1278,7 @@ TEST_F(MMapFileTest, WriteSharedTruncateDownThenUp) { } TEST_F(MMapFileTest, ReadSharedTruncateSIGBUS) { + SKIP_IF(!FSSupportsMap()); SetupGvisorDeathTest(); // Start from scratch. @@ -1277,6 +1309,7 @@ TEST_F(MMapFileTest, ReadSharedTruncateSIGBUS) { } TEST_F(MMapFileTest, WriteSharedTruncateSIGBUS) { + SKIP_IF(!FSSupportsMap()); SetupGvisorDeathTest(); uintptr_t addr; @@ -1298,6 +1331,7 @@ TEST_F(MMapFileTest, WriteSharedTruncateSIGBUS) { } TEST_F(MMapFileTest, ReadSharedTruncatePartialPage) { + SKIP_IF(!FSSupportsMap()); // Start from scratch. EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); @@ -1327,6 +1361,7 @@ TEST_F(MMapFileTest, ReadSharedTruncatePartialPage) { // Page can still be accessed and contents are intact after truncating a partial // page. TEST_F(MMapFileTest, WriteSharedTruncatePartialPage) { + SKIP_IF(!FSSupportsMap()); // Expand the file to a full page. EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds()); @@ -1354,6 +1389,7 @@ TEST_F(MMapFileTest, WriteSharedTruncatePartialPage) { // MAP_PRIVATE writes are not carried through to the underlying file. TEST_F(MMapFileTest, WritePrivate) { + SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd_.get(), 0), @@ -1378,6 +1414,7 @@ TEST_F(MMapFileTest, WritePrivate) { // SIGBUS raised when reading or writing past end of a mapped file. TEST_P(MMapFileParamTest, SigBusDeath) { + SKIP_IF(!FSSupportsMap()); SetupGvisorDeathTest(); uintptr_t addr; @@ -1406,6 +1443,7 @@ TEST_P(MMapFileParamTest, SigBusDeath) { // // See b/27877699. TEST_P(MMapFileParamTest, NoSigBusOnPagesBeforeEOF) { + SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, prot(), flags(), fd_.get(), 0), SyscallSucceeds()); @@ -1424,6 +1462,7 @@ TEST_P(MMapFileParamTest, NoSigBusOnPagesBeforeEOF) { // Tests that SIGBUS is not raised when reading or writing from a file-mapped // page containing EOF, *after* the EOF. TEST_P(MMapFileParamTest, NoSigBusOnPageContainingEOF) { + SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, prot(), flags(), fd_.get(), 0), SyscallSucceeds()); @@ -1446,6 +1485,7 @@ TEST_P(MMapFileParamTest, NoSigBusOnPageContainingEOF) { // page cache (which does not yet support writing to shared mappings), a bug // caused reads to fail unnecessarily on such mappings. See b/28913513. TEST_F(MMapFileTest, ReadingWritableSharedFilePageSucceeds) { + SKIP_IF(!FSSupportsMap()); uintptr_t addr; size_t len = strlen(kFileContents); @@ -1463,6 +1503,7 @@ TEST_F(MMapFileTest, ReadingWritableSharedFilePageSucceeds) { // read past end of file (resulting in a fault in sentry context in the gVisor // case). See b/28913513. TEST_F(MMapFileTest, InternalSigBus) { + SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd_.get(), 0), @@ -1483,6 +1524,7 @@ TEST_F(MMapFileTest, InternalSigBus) { // /dev/zero to a shared mapping (so that the SIGBUS isn't caught during // copy-on-write breaking). TEST_F(MMapFileTest, InternalSigBusZeroing) { + SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), @@ -1578,6 +1620,7 @@ TEST_F(MMapTest, NoReserve) { // Map more than the gVisor page-cache map unit (64k) and ensure that // it is consistent with reading from the file. TEST_F(MMapFileTest, Bug38498194) { + SKIP_IF(!FSSupportsMap()); // Choose a sufficiently large map unit. constexpr int kSize = 4 * 1024 * 1024; EXPECT_THAT(ftruncate(fd_.get(), kSize), SyscallSucceeds()); @@ -1606,6 +1649,7 @@ TEST_F(MMapFileTest, Bug38498194) { // Tests that reading from a file to a memory mapping of the same file does not // deadlock. See b/34813270. TEST_F(MMapFileTest, SelfRead) { + SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), @@ -1618,6 +1662,7 @@ TEST_F(MMapFileTest, SelfRead) { // Tests that writing to a file from a memory mapping of the same file does not // deadlock. Regression test for b/34813270. TEST_F(MMapFileTest, SelfWrite) { + SKIP_IF(!FSSupportsMap()); uintptr_t addr; ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), SyscallSucceeds()); @@ -1633,8 +1678,12 @@ TEST(MMapDeathTest, TruncateAfterCOWBreak) { auto const temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDWR)); ASSERT_THAT(ftruncate(fd.get(), kPageSize), SyscallSucceeds()); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0)); + + auto maybe_mapping = Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE, fd.get(), 0); + // Does FS support mmap? + SKIP_IF(maybe_mapping.error().errno_value() == ENODEV); + auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(std::move(maybe_mapping)); // Write to this mapping, causing the page to be copied for write. memset(mapping.ptr(), 'a', mapping.len()); @@ -1661,8 +1710,12 @@ TEST(MMapNoFixtureTest, MapReadOnlyAfterCreateWriteOnly) { auto const wo_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_WRONLY)); ASSERT_THAT(ftruncate(wo_fd.get(), kPageSize), SyscallSucceeds()); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, ro_fd.get(), 0)); + auto maybe_mapping = + Mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, ro_fd.get(), 0); + // Does FS support mmap? + SKIP_IF(maybe_mapping.error().errno_value() == ENODEV); + auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(std::move(maybe_mapping)); + std::vector<char> buf(kPageSize); // The test passes if this survives. std::copy(static_cast<char*>(mapping.ptr()), diff --git a/test/syscalls/linux/msgqueue.cc b/test/syscalls/linux/msgqueue.cc index 6804478fd..aaf46625d 100644 --- a/test/syscalls/linux/msgqueue.cc +++ b/test/syscalls/linux/msgqueue.cc @@ -13,14 +13,18 @@ // limitations under the License. #include <errno.h> +#include <signal.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h> +#include "absl/synchronization/notification.h" #include "absl/time/clock.h" #include "test/util/capability_util.h" +#include "test/util/signal_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" +#include "test/util/thread_util.h" namespace gvisor { namespace testing { @@ -36,10 +40,16 @@ constexpr int msgMax = 8192; // Maximum number of bytes in a single message. constexpr int msgSsz = 16; // Message segment size. constexpr int msgTql = msgMnb; // Maximum number of messages on all queues. +constexpr int kInterruptSignal = SIGALRM; + // Queue is a RAII class used to automatically clean message queues. class Queue { public: explicit Queue(int id) : id_(id) {} + Queue(const Queue&) = delete; + Queue& operator=(const Queue&) = delete; + + Queue(Queue&& other) { id_ = other.release(); } ~Queue() { if (id_ >= 0) { @@ -59,6 +69,14 @@ class Queue { int id_ = -1; }; +PosixErrorOr<Queue> Msgget(key_t key, int flags) { + int id = msgget(key, flags); + if (id == -1) { + return PosixError(errno, absl::StrFormat("msgget(%d, %d)", key, flags)); + } + return Queue(id); +} + // Default size for messages. constexpr size_t msgSize = 50; @@ -78,14 +96,19 @@ bool operator==(msgbuf& a, msgbuf& b) { return a.mtype == b.mtype; } +// msgmax represents a buffer for the largest possible single message. +struct msgmax { + int64_t mtype; + char mtext[msgMax]; +}; + // Test simple creation and retrieval for msgget(2). TEST(MsgqueueTest, MsgGet) { const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const key_t key = ftok(keyfile.path().c_str(), 1); ASSERT_THAT(key, SyscallSucceeds()); - Queue queue(msgget(key, IPC_CREAT)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(key, IPC_CREAT)); EXPECT_THAT(msgget(key, 0), SyscallSucceedsWithValue(queue.get())); } @@ -97,27 +120,20 @@ TEST(MsgqueueTest, MsgGetFail) { EXPECT_THAT(msgget(key, 0), SyscallFailsWithErrno(ENOENT)); - Queue queue(msgget(key, IPC_CREAT)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(key, IPC_CREAT)); EXPECT_THAT(msgget(key, IPC_CREAT | IPC_EXCL), SyscallFailsWithErrno(EEXIST)); } // Test using msgget(2) with IPC_PRIVATE option. TEST(MsgqueueTest, MsgGetIpcPrivate) { - Queue queue1(msgget(IPC_PRIVATE, 0)); - ASSERT_THAT(queue1.get(), SyscallSucceeds()); - - Queue queue2(msgget(IPC_PRIVATE, 0)); - ASSERT_THAT(queue2.get(), SyscallSucceeds()); - + Queue queue1 = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0)); + Queue queue2 = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0)); EXPECT_NE(queue1.get(), queue2.get()); } // Test simple msgsnd and msgrcv. TEST(MsgqueueTest, MsgOpSimple) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, "A message."}; msgbuf rcv; @@ -132,8 +148,7 @@ TEST(MsgqueueTest, MsgOpSimple) { // Test msgsnd and msgrcv of an empty message. TEST(MsgqueueTest, MsgOpEmpty) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; msgbuf rcv; @@ -145,8 +160,7 @@ TEST(MsgqueueTest, MsgOpEmpty) { // Test truncation of message with MSG_NOERROR flag. TEST(MsgqueueTest, MsgOpTruncate) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; msgbuf rcv; @@ -160,8 +174,7 @@ TEST(MsgqueueTest, MsgOpTruncate) { // Test msgsnd and msgrcv using invalid arguments. TEST(MsgqueueTest, MsgOpInvalidArgs) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; @@ -178,8 +191,7 @@ TEST(MsgqueueTest, MsgOpInvalidArgs) { // Test non-blocking msgrcv with an empty queue. TEST(MsgqueueTest, MsgOpNoMsg) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf rcv; EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(rcv.mtext) + 1, 0, IPC_NOWAIT), @@ -189,8 +201,7 @@ TEST(MsgqueueTest, MsgOpNoMsg) { // Test non-blocking msgrcv with a non-empty queue, but no messages of wanted // type. TEST(MsgqueueTest, MsgOpNoMsgType) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), @@ -202,8 +213,7 @@ TEST(MsgqueueTest, MsgOpNoMsgType) { // Test msgrcv with a larger size message than wanted, and truncation disabled. TEST(MsgqueueTest, MsgOpTooBig) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), @@ -215,8 +225,7 @@ TEST(MsgqueueTest, MsgOpTooBig) { // Test receiving messages based on type. TEST(MsgqueueTest, MsgRcvType) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); // Send messages in an order and receive them in reverse, based on type, // which shouldn't block. @@ -242,8 +251,7 @@ TEST(MsgqueueTest, MsgRcvType) { // Test using MSG_EXCEPT to receive a different-type message. TEST(MsgqueueTest, MsgExcept) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); std::map<int64_t, msgbuf> typeToBuf = { {1, msgbuf{1, "Message 1."}}, @@ -268,8 +276,7 @@ TEST(MsgqueueTest, MsgExcept) { // Test msgrcv with a negative type. TEST(MsgqueueTest, MsgRcvTypeNegative) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); // When msgtyp is negative, msgrcv returns the first message with mtype less // than or equal to the absolute value. @@ -292,8 +299,7 @@ TEST(MsgqueueTest, MsgRcvTypeNegative) { TEST(MsgqueueTest, MsgOpPermissions) { AutoCapability cap(CAP_IPC_OWNER, false); - Queue queue(msgget(IPC_PRIVATE, 0000)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0000)); msgbuf buf{1, ""}; @@ -305,8 +311,7 @@ TEST(MsgqueueTest, MsgOpPermissions) { // Test limits for messages and queues. TEST(MsgqueueTest, MsgOpLimits) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, "A message."}; @@ -315,13 +320,6 @@ TEST(MsgqueueTest, MsgOpLimits) { SyscallFailsWithErrno(EINVAL)); // Limit for queue. - // Use a buffer with the maximum mount of bytes that can be transformed to - // make it easier to exhaust the queue limit. - struct msgmax { - int64_t mtype; - char mtext[msgMax]; - }; - msgmax limit{1, ""}; for (size_t i = 0, msgCount = msgMnb / msgMax; i < msgCount; i++) { EXPECT_THAT(msgsnd(queue.get(), &limit, sizeof(limit.mtext), 0), @@ -341,11 +339,14 @@ bool MsgCopySupported() { // test if errno == ENOSYS. This means that the test will always run on // gVisor, but may be skipped on native linux. - Queue queue(msgget(IPC_PRIVATE, 0600)); - + auto maybe_id = Msgget(IPC_PRIVATE, 0600); + if (!maybe_id.ok()) { + return false; + } + Queue queue(std::move(maybe_id.ValueOrDie())); msgbuf buf{1, "Test message."}; - msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0); + msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0); return !(msgrcv(queue.get(), &buf, sizeof(buf.mtext) + 1, 0, MSG_COPY | IPC_NOWAIT) == -1 && errno == ENOSYS); @@ -355,9 +356,7 @@ bool MsgCopySupported() { TEST(MsgqueueTest, MsgCopy) { SKIP_IF(!MsgCopySupported()); - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf bufs[5] = { msgbuf{1, "Message 1."}, msgbuf{2, "Message 2."}, msgbuf{3, "Message 3."}, msgbuf{4, "Message 4."}, msgbuf{5, "Message 5."}, @@ -391,9 +390,7 @@ TEST(MsgqueueTest, MsgCopy) { TEST(MsgqueueTest, MsgCopyInvalidArgs) { SKIP_IF(!MsgCopySupported()); - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf rcv; EXPECT_THAT(msgrcv(queue.get(), &rcv, msgSize, 1, MSG_COPY), SyscallFailsWithErrno(EINVAL)); @@ -407,9 +404,7 @@ TEST(MsgqueueTest, MsgCopyInvalidArgs) { TEST(MsgqueueTest, MsgCopyInvalidIndex) { SKIP_IF(!MsgCopySupported()); - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf rcv; EXPECT_THAT(msgrcv(queue.get(), &rcv, msgSize, -3, MSG_COPY | IPC_NOWAIT), SyscallFailsWithErrno(ENOMSG)); @@ -420,52 +415,41 @@ TEST(MsgqueueTest, MsgCopyInvalidIndex) { // Test msgrcv (most probably) blocking on an empty queue. TEST(MsgqueueTest, MsgRcvBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, "A message."}; - const pid_t child_pid = fork(); - if (child_pid == 0) { + ScopedThread t([&] { msgbuf rcv; - TEST_PCHECK(RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1, 0, - 0) == sizeof(buf.mtext) && - buf == rcv); - _exit(0); - } + ASSERT_THAT( + RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1, 0, 0), + SyscallSucceedsWithValue(sizeof(buf.mtext))); + EXPECT_TRUE(rcv == buf); + }); // Sleep to try and make msgrcv block before sending a message. absl::SleepFor(absl::Milliseconds(150)); EXPECT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), SyscallSucceeds()); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); } // Test msgrcv (most probably) waiting for a specific-type message. TEST(MsgqueueTest, MsgRcvTypeBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf bufs[5] = {{1, "A message."}, {1, "A message."}, {1, "A message."}, {1, "A message."}, {2, "A different message."}}; - const pid_t child_pid = fork(); - if (child_pid == 0) { + ScopedThread t([&] { msgbuf buf = bufs[4]; // Buffer that should be received. msgbuf rcv; - TEST_PCHECK(RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1, 2, - 0) == sizeof(buf.mtext) && - buf == rcv); - _exit(0); - } + ASSERT_THAT( + RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1, 2, 0), + SyscallSucceedsWithValue(sizeof(buf.mtext))); + EXPECT_TRUE(rcv == buf); + }); // Sleep to try and make msgrcv block before sending messages. absl::SleepFor(absl::Milliseconds(150)); @@ -475,42 +459,29 @@ TEST(MsgqueueTest, MsgRcvTypeBlocking) { EXPECT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), SyscallSucceeds()); } - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); } // Test msgsnd (most probably) blocking on a full queue. TEST(MsgqueueTest, MsgSndBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - - // Use a buffer with the maximum mount of bytes that can be transformed to - // make it easier to exhaust the queue limit. - struct msgmax { - int64_t mtype; - char mtext[msgMax]; - }; - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgmax buf{1, ""}; // Has max amount of bytes. const size_t msgCount = msgMnb / msgMax; // Number of messages that can be // sent without blocking. - const pid_t child_pid = fork(); - if (child_pid == 0) { + ScopedThread t([&] { // Fill the queue. for (size_t i = 0; i < msgCount; i++) { - TEST_PCHECK(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0) == 0); + ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), + SyscallSucceeds()); } // Next msgsnd should block. - TEST_PCHECK(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0) == - 0); - _exit(0); - } + ASSERT_THAT(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0), + SyscallSucceeds()); + }); + + const DisableSave ds; // Too many syscalls. // To increase the chance of the last msgsnd blocking before doing a msgrcv, // we use MSG_COPY option to copy the last index in the queue. As long as @@ -527,29 +498,16 @@ TEST(MsgqueueTest, MsgSndBlocking) { EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(buf.mtext), 0, 0), SyscallSucceedsWithValue(sizeof(buf.mtext))); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); } // Test removing a queue while a blocking msgsnd is executing. TEST(MsgqueueTest, MsgSndRmWhileBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); - // Use a buffer with the maximum mount of bytes that can be transformed to - // make it easier to exhaust the queue limit. - struct msgmax { - int64_t mtype; - char mtext[msgMax]; - }; + // Number of messages that can be sent without blocking. + const size_t msgCount = msgMnb / msgMax; - const size_t msgCount = msgMnb / msgMax; // Number of messages that can be - // sent without blocking. - const pid_t child_pid = fork(); - if (child_pid == 0) { + ScopedThread t([&] { // Fill the queue. msgmax buf{1, ""}; for (size_t i = 0; i < msgCount; i++) { @@ -559,11 +517,12 @@ TEST(MsgqueueTest, MsgSndRmWhileBlocking) { // Next msgsnd should block. Because we're repeating on EINTR, msgsnd may // race with msgctl(IPC_RMID) and return EINVAL. - TEST_PCHECK(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0) == - -1 && - (errno == EIDRM || errno == EINVAL)); - _exit(0); - } + EXPECT_THAT(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0), + SyscallFails()); + EXPECT_TRUE((errno == EIDRM || errno == EINVAL)); + }); + + const DisableSave ds; // Too many syscalls. // Similar to MsgSndBlocking, we do this to increase the chance of msgsnd // blocking before removing the queue. @@ -575,89 +534,165 @@ TEST(MsgqueueTest, MsgSndRmWhileBlocking) { absl::SleepFor(absl::Milliseconds(100)); EXPECT_THAT(msgctl(queue.release(), IPC_RMID, nullptr), SyscallSucceeds()); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); } // Test removing a queue while a blocking msgrcv is executing. TEST(MsgqueueTest, MsgRcvRmWhileBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); - const pid_t child_pid = fork(); - if (child_pid == 0) { + ScopedThread t([&] { // Because we're repeating on EINTR, msgsnd may race with msgctl(IPC_RMID) // and return EINVAL. msgbuf rcv; - TEST_PCHECK(RetryEINTR(msgrcv)(queue.get(), &rcv, 1, 2, 0) == -1 && - (errno == EIDRM || errno == EINVAL)); - _exit(0); - } + EXPECT_THAT(RetryEINTR(msgrcv)(queue.get(), &rcv, 1, 2, 0), SyscallFails()); + EXPECT_TRUE(errno == EIDRM || errno == EINVAL); + }); // Sleep to try and make msgrcv block before sending messages. absl::SleepFor(absl::Milliseconds(150)); EXPECT_THAT(msgctl(queue.release(), IPC_RMID, nullptr), SyscallSucceeds()); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); } // Test a collection of msgsnd/msgrcv operations in different processes. TEST(MsgqueueTest, MsgOpGeneral) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); + + // Create multiple sending/receiving threads that send messages back and + // forth. There's a matching recv for each send, so by the end of the test, + // all threads should succeed and return. + const std::vector<msgbuf> msgs = { + msgbuf{1, "Message 1."}, msgbuf{2, "Message 2."}, msgbuf{3, "Message 3."}, + msgbuf{4, "Message 4."}, msgbuf{5, "Message 5."}}; - // Create 50 sending, and 50 receiving processes. There are only 5 messages to - // be sent and received, each with a different type. All messages will be sent - // and received equally (10 of each.) By the end of the test all processes - // should unblock and return normally. - const size_t msgCount = 5; - std::map<int64_t, msgbuf> typeToBuf = {{1, msgbuf{1, "Message 1."}}, - {2, msgbuf{2, "Message 2."}}, - {3, msgbuf{3, "Message 3."}}, - {4, msgbuf{4, "Message 4."}}, - {5, msgbuf{5, "Message 5."}}}; - - std::vector<pid_t> children; - - const size_t pCount = 50; - for (size_t i = 1; i <= pCount; i++) { - const pid_t child_pid = fork(); - if (child_pid == 0) { - msgbuf buf = typeToBuf[(i % msgCount) + 1]; + auto receiver = [&](int i) { + return [i, &msgs, &queue]() { + const msgbuf& target = msgs[i]; msgbuf rcv; - TEST_PCHECK(RetryEINTR(msgrcv)(queue.get(), &rcv, sizeof(buf.mtext) + 1, - (i % msgCount) + 1, - 0) == sizeof(buf.mtext) && - buf == rcv); - _exit(0); - } - children.push_back(child_pid); - } + EXPECT_THAT(RetryEINTR(msgrcv)(queue.get(), &rcv, + sizeof(target.mtext) + 1, target.mtype, 0), + SyscallSucceedsWithValue(sizeof(target.mtext))); + EXPECT_EQ(rcv.mtype, target.mtype); + EXPECT_EQ(0, memcmp(rcv.mtext, target.mtext, sizeof(target.mtext))); + }; + }; - for (size_t i = 1; i <= pCount; i++) { - const pid_t child_pid = fork(); - if (child_pid == 0) { - msgbuf buf = typeToBuf[(i % msgCount) + 1]; - TEST_PCHECK(RetryEINTR(msgsnd)(queue.get(), &buf, sizeof(buf.mtext), 0) == - 0); - _exit(0); - } - children.push_back(child_pid); - } + ScopedThread r1(receiver(0)); + ScopedThread r2(receiver(1)); + ScopedThread r3(receiver(2)); + ScopedThread r4(receiver(3)); + ScopedThread r5(receiver(4)); + ScopedThread r6(receiver(0)); + ScopedThread r7(receiver(1)); + ScopedThread r8(receiver(2)); + ScopedThread r9(receiver(3)); + ScopedThread r10(receiver(4)); + + auto sender = [&](int i) { + return [i, &msgs, &queue]() { + const msgbuf& target = msgs[i]; + EXPECT_THAT( + RetryEINTR(msgsnd)(queue.get(), &target, sizeof(target.mtext), 0), + SyscallSucceeds()); + }; + }; - for (auto const& pid : children) { - int status; - ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), - SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + ScopedThread s1(sender(0)); + ScopedThread s2(sender(1)); + ScopedThread s3(sender(2)); + ScopedThread s4(sender(3)); + ScopedThread s5(sender(4)); + ScopedThread s6(sender(0)); + ScopedThread s7(sender(1)); + ScopedThread s8(sender(2)); + ScopedThread s9(sender(3)); + ScopedThread s10(sender(4)); +} + +void empty_sighandler(int sig, siginfo_t* info, void* context) {} + +TEST(MsgqueueTest, InterruptRecv) { + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); + char buf[64]; + + absl::Notification done, exit; + + // Thread calling msgrcv with no corresponding send. It would block forever, + // but we'll interrupt with a signal below. + ScopedThread t([&] { + struct sigaction sa = {}; + sa.sa_sigaction = empty_sighandler; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + auto cleanup_sigaction = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kInterruptSignal, sa)); + auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE( + ScopedSignalMask(SIG_UNBLOCK, kInterruptSignal)); + + EXPECT_THAT(msgrcv(queue.get(), &buf, sizeof(buf), 0, 0), + SyscallFailsWithErrno(EINTR)); + + done.Notify(); + exit.WaitForNotification(); + }); + + const DisableSave ds; // Too many syscalls. + + // We want the signal to arrive while msgrcv is blocking, but not after the + // thread has exited. Signals that arrive before msgrcv are no-ops. + do { + EXPECT_THAT(kill(getpid(), kInterruptSignal), SyscallSucceeds()); + absl::SleepFor(absl::Milliseconds(100)); // Rate limit. + } while (!done.HasBeenNotified()); + + exit.Notify(); + t.Join(); +} + +TEST(MsgqueueTest, InterruptSend) { + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); + msgmax buf{1, ""}; + // Number of messages that can be sent without blocking. + const size_t msgCount = msgMnb / msgMax; + + // Fill the queue. + for (size_t i = 0; i < msgCount; i++) { + ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), + SyscallSucceeds()); } + + absl::Notification done, exit; + + // Thread calling msgsnd on a full queue. It would block forever, but we'll + // interrupt with a signal below. + ScopedThread t([&] { + struct sigaction sa = {}; + sa.sa_sigaction = empty_sighandler; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + auto cleanup_sigaction = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kInterruptSignal, sa)); + auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE( + ScopedSignalMask(SIG_UNBLOCK, kInterruptSignal)); + + EXPECT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), + SyscallFailsWithErrno(EINTR)); + + done.Notify(); + exit.WaitForNotification(); + }); + + const DisableSave ds; // Too many syscalls. + + // We want the signal to arrive while msgsnd is blocking, but not after the + // thread has exited. Signals that arrive before msgsnd are no-ops. + do { + EXPECT_THAT(kill(getpid(), kInterruptSignal), SyscallSucceeds()); + absl::SleepFor(absl::Milliseconds(100)); // Rate limit. + } while (!done.HasBeenNotified()); + + exit.Notify(); + t.Join(); } // Test msgctl with IPC_STAT option. @@ -833,3 +868,16 @@ TEST(MsgqueueTest, MsgCtlMsgInfo) { } // namespace } // namespace testing } // namespace gvisor + +int main(int argc, char** argv) { + // Some tests depend on delivering a signal to the main thread. Block the + // target signal so that any other threads created by TestInit will also have + // the signal blocked. + sigset_t set; + sigemptyset(&set); + sigaddset(&set, gvisor::testing::kInterruptSignal); + TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); + + gvisor::testing::TestInit(&argc, &argv); + return gvisor::testing::RunAllTests(); +} diff --git a/test/syscalls/linux/packet_socket.cc b/test/syscalls/linux/packet_socket.cc index 98339277b..ca4ab0aad 100644 --- a/test/syscalls/linux/packet_socket.cc +++ b/test/syscalls/linux/packet_socket.cc @@ -14,13 +14,13 @@ #include <arpa/inet.h> #include <ifaddrs.h> -#include <linux/capability.h> -#include <linux/if_arp.h> -#include <linux/if_packet.h> #include <net/ethernet.h> +#include <net/if.h> +#include <net/if_arp.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/udp.h> +#include <netpacket/packet.h> #include <poll.h> #include <sys/ioctl.h> #include <sys/socket.h> @@ -31,6 +31,7 @@ #include "absl/base/internal/endian.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" #include "test/util/capability_util.h" +#include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/socket_util.h" #include "test/util/test_util.h" @@ -85,7 +86,7 @@ void SendUDPMessage(int sock) { // Send an IP packet and make sure ETH_P_<something else> doesn't pick it up. TEST(BasicCookedPacketTest, WrongType) { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { ASSERT_THAT(socket(AF_PACKET, SOCK_DGRAM, ETH_P_PUP), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -123,7 +124,7 @@ class CookedPacketTest : public ::testing::TestWithParam<int> { }; void CookedPacketTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { ASSERT_THAT(socket(AF_PACKET, SOCK_DGRAM, htons(GetParam())), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -149,7 +150,7 @@ void CookedPacketTest::SetUp() { void CookedPacketTest::TearDown() { // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { EXPECT_THAT(close(socket_), SyscallSucceeds()); } } diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc index 07beb8ba0..61714d1da 100644 --- a/test/syscalls/linux/packet_socket_raw.cc +++ b/test/syscalls/linux/packet_socket_raw.cc @@ -13,14 +13,13 @@ // limitations under the License. #include <arpa/inet.h> -#include <linux/capability.h> -#include <linux/filter.h> -#include <linux/if_arp.h> -#include <linux/if_packet.h> #include <net/ethernet.h> +#include <net/if.h> +#include <net/if_arp.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/udp.h> +#include <netpacket/packet.h> #include <poll.h> #include <sys/ioctl.h> #include <sys/socket.h> @@ -32,6 +31,7 @@ #include "absl/base/internal/endian.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" #include "test/util/capability_util.h" +#include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/socket_util.h" #include "test/util/test_util.h" @@ -100,7 +100,7 @@ class RawPacketTest : public ::testing::TestWithParam<int> { }; void RawPacketTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, htons(GetParam())), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -150,7 +150,7 @@ void RawPacketTest::SetUp() { void RawPacketTest::TearDown() { // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { EXPECT_THAT(close(s_), SyscallSucceeds()); } } @@ -340,7 +340,7 @@ TEST_P(RawPacketTest, Send) { // Check that setting SO_RCVBUF below min is clamped to the minimum // receive buffer size. TEST_P(RawPacketTest, SetSocketRecvBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); // Discover minimum receive buf size by trying to set it to zero. // See: @@ -373,7 +373,7 @@ TEST_P(RawPacketTest, SetSocketRecvBufBelowMin) { // Check that setting SO_RCVBUF above max is clamped to the maximum // receive buffer size. TEST_P(RawPacketTest, SetSocketRecvBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); // Discover max buf size by trying to set the largest possible buffer size. constexpr int kRcvBufSz = 0xffffffff; @@ -400,7 +400,7 @@ TEST_P(RawPacketTest, SetSocketRecvBufAboveMax) { // Check that setting SO_RCVBUF min <= kRcvBufSz <= max is honored. TEST_P(RawPacketTest, SetSocketRecvBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int max = 0; int min = 0; @@ -449,7 +449,7 @@ TEST_P(RawPacketTest, SetSocketRecvBuf) { // Check that setting SO_SNDBUF below min is clamped to the minimum // receive buffer size. TEST_P(RawPacketTest, SetSocketSendBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); // Discover minimum buffer size by trying to set it to zero. constexpr int kSndBufSz = 0; @@ -480,7 +480,7 @@ TEST_P(RawPacketTest, SetSocketSendBufBelowMin) { // Check that setting SO_SNDBUF above max is clamped to the maximum // send buffer size. TEST_P(RawPacketTest, SetSocketSendBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); // Discover maximum buffer size by trying to set it to a large value. constexpr int kSndBufSz = 0xffffffff; @@ -507,7 +507,7 @@ TEST_P(RawPacketTest, SetSocketSendBufAboveMax) { // Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored. TEST_P(RawPacketTest, SetSocketSendBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int max = 0; int min = 0; @@ -551,7 +551,7 @@ TEST_P(RawPacketTest, SetSocketSendBuf) { } TEST_P(RawPacketTest, GetSocketError) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int val = 0; socklen_t val_len = sizeof(val); @@ -561,7 +561,7 @@ TEST_P(RawPacketTest, GetSocketError) { } TEST_P(RawPacketTest, GetSocketErrorBind) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); { // Bind to the loopback device. @@ -627,7 +627,7 @@ TEST_P(RawPacketTest, SetSocketDetachFilterNoInstalledFilter) { } TEST_P(RawPacketTest, GetSocketDetachFilter) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int val = 0; socklen_t val_len = sizeof(val); @@ -636,7 +636,7 @@ TEST_P(RawPacketTest, GetSocketDetachFilter) { } TEST_P(RawPacketTest, SetAndGetSocketLinger) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int level = SOL_SOCKET; int type = SO_LINGER; @@ -657,7 +657,7 @@ TEST_P(RawPacketTest, SetAndGetSocketLinger) { } TEST_P(RawPacketTest, GetSocketAcceptConn) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int got = -1; socklen_t length = sizeof(got); @@ -673,7 +673,7 @@ INSTANTIATE_TEST_SUITE_P(AllInetTests, RawPacketTest, class RawPacketMsgSizeTest : public ::testing::TestWithParam<TestAddress> {}; TEST_P(RawPacketMsgSizeTest, SendTooLong) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); TestAddress addr = GetParam().WithPort(kPort); @@ -690,8 +690,11 @@ TEST_P(RawPacketMsgSizeTest, SendTooLong) { SyscallFailsWithErrno(EMSGSIZE)); } +// TODO(https://fxbug.dev/76957): Run this test on Fuchsia once splice is +// available. +#ifndef __Fuchsia__ TEST_P(RawPacketMsgSizeTest, SpliceTooLong) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); const char buf[65536] = {}; int fds[2]; @@ -718,6 +721,7 @@ TEST_P(RawPacketMsgSizeTest, SpliceTooLong) { EXPECT_THAT(n, SyscallSucceedsWithValue(sizeof(buf))); } } +#endif // __Fuchsia__ INSTANTIATE_TEST_SUITE_P(AllRawPacketMsgSizeTest, RawPacketMsgSizeTest, ::testing::Values(V4Loopback(), V6Loopback())); diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc index 0bba86846..209801e36 100644 --- a/test/syscalls/linux/pipe.cc +++ b/test/syscalls/linux/pipe.cc @@ -13,11 +13,13 @@ // limitations under the License. #include <fcntl.h> /* Obtain O_* constant definitions */ +#include <linux/futex.h> #include <linux/magic.h> #include <signal.h> #include <sys/ioctl.h> #include <sys/statfs.h> #include <sys/uio.h> +#include <syscall.h> #include <unistd.h> #include <vector> @@ -50,6 +52,9 @@ std::atomic<int> global_num_signals_received = 0; void SigRecordingHandler(int signum, siginfo_t* siginfo, void* unused_ucontext) { global_num_signals_received++; + ASSERT_THAT(syscall(SYS_futex, &global_num_signals_received, + FUTEX_WAKE | FUTEX_PRIVATE_FLAG, INT_MAX, 0, 0, 0), + SyscallSucceeds()); } PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) { @@ -61,11 +66,14 @@ PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) { return ScopedSigaction(signum, handler); } -void WaitForSignalDelivery(absl::Duration timeout, int max_expected) { - absl::Time wait_start = absl::Now(); - while (global_num_signals_received < max_expected && - absl::Now() - wait_start < timeout) { - absl::SleepFor(absl::Milliseconds(10)); +void WaitForSignalDelivery(int expected) { + while (1) { + int v = global_num_signals_received; + if (v >= expected) { + break; + } + RetryEINTR(syscall)(SYS_futex, &global_num_signals_received, + FUTEX_WAIT | FUTEX_PRIVATE_FLAG, v, 0, 0, 0); } } @@ -371,7 +379,7 @@ TEST_P(PipeTest, ReaderSideCloses) { EXPECT_THAT(write(wfd_.get(), &buf, sizeof(buf)), SyscallFailsWithErrno(EPIPE)); - WaitForSignalDelivery(absl::Seconds(1), 1); + WaitForSignalDelivery(1); ASSERT_EQ(global_num_signals_received, 1); } @@ -411,7 +419,7 @@ TEST_P(PipeTest, BlockWriteClosed) { notify.WaitForNotification(); ASSERT_THAT(close(rfd_.release()), SyscallSucceeds()); - WaitForSignalDelivery(absl::Seconds(1), 1); + WaitForSignalDelivery(1); ASSERT_EQ(global_num_signals_received, 1); t.Join(); @@ -443,7 +451,7 @@ TEST_P(PipeTest, BlockPartialWriteClosed) { // Unblock the above. ASSERT_THAT(close(rfd_.release()), SyscallSucceeds()); - WaitForSignalDelivery(absl::Seconds(1), 2); + WaitForSignalDelivery(2); ASSERT_EQ(global_num_signals_received, 2); t.Join(); diff --git a/test/syscalls/linux/prctl.cc b/test/syscalls/linux/prctl.cc index 25b0e63d4..286b3d168 100644 --- a/test/syscalls/linux/prctl.cc +++ b/test/syscalls/linux/prctl.cc @@ -214,6 +214,12 @@ TEST(PrctlTest, RootDumpability) { SyscallFailsWithErrno(EINVAL)); } +TEST(PrctlTest, SetGetSubreaper) { + // Setting subreaper on PID 1 works vacuously because PID 1 is always a + // subreaper. + EXPECT_THAT(prctl(PR_SET_CHILD_SUBREAPER, 1), SyscallSucceeds()); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/proc_net.cc b/test/syscalls/linux/proc_net.cc index 4cbe30fc1..162c0b665 100644 --- a/test/syscalls/linux/proc_net.cc +++ b/test/syscalls/linux/proc_net.cc @@ -152,6 +152,22 @@ TEST(ProcNetDev, Format) { EXPECT_GT(entries.size(), 0); } +// GetMibsAllocationSysctl retuns a value of the net.core.mibs_allocation +// sysctl./proc/sys/net/core/mibs_allocation +// +// When mibs_allocation is unset, a netns creation inherits MIB from init +// network namespace. Otherwise, MIBS is allocated for each namespace. +int GetMibsAllocationSysctl() { + auto ret = GetContents("/proc/sys/net/core/mibs_allocation"); + if (!ret.ok()) { + // The current kernel doesn't support mibs_allocation. + return 1; + } + int32_t val; + EXPECT_TRUE(absl::SimpleAtoi(ret.ValueOrDie(), &val)); + return val; +} + PosixErrorOr<uint64_t> GetSNMPMetricFromProc(const std::string snmp, const std::string& type, const std::string& item) { @@ -226,12 +242,21 @@ TEST(ProcNetSnmp, TcpReset) { newAttemptFails = ASSERT_NO_ERRNO_AND_VALUE( GetSNMPMetricFromProc(snmp, "Tcp", "AttemptFails")); - EXPECT_EQ(oldActiveOpens, newActiveOpens - 1); - EXPECT_EQ(oldOutRsts, newOutRsts - 1); - EXPECT_EQ(oldAttemptFails, newAttemptFails - 1); + if (GetMibsAllocationSysctl()) { + EXPECT_EQ(oldActiveOpens, newActiveOpens - 1); + EXPECT_EQ(oldOutRsts, newOutRsts - 1); + EXPECT_EQ(oldAttemptFails, newAttemptFails - 1); + } else { + // System-wide statistics can have some noise. + EXPECT_LE(oldOutRsts, newOutRsts - 1); + EXPECT_LE(oldAttemptFails, newAttemptFails - 1); + } } TEST(ProcNetSnmp, TcpEstab) { + // System-wide statistics can have some noise. + SKIP_IF(GetMibsAllocationSysctl() == 0); + // TODO(gvisor.dev/issue/866): epsocket metrics are not savable. DisableSave ds; @@ -355,8 +380,14 @@ TEST(ProcNetSnmp, UdpNoPorts) { newNoPorts = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "NoPorts")); - EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1); - EXPECT_EQ(oldNoPorts, newNoPorts - 1); + if (GetMibsAllocationSysctl()) { + EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1); + EXPECT_EQ(oldNoPorts, newNoPorts - 1); + } else { + // System-wide statistics can have some noise. + EXPECT_LE(oldOutDatagrams, newOutDatagrams - 1); + EXPECT_LE(oldNoPorts, newNoPorts - 1); + } } TEST(ProcNetSnmp, UdpIn) { @@ -405,8 +436,14 @@ TEST(ProcNetSnmp, UdpIn) { newInDatagrams = ASSERT_NO_ERRNO_AND_VALUE( GetSNMPMetricFromProc(snmp, "Udp", "InDatagrams")); - EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1); - EXPECT_EQ(oldInDatagrams, newInDatagrams - 1); + if (GetMibsAllocationSysctl()) { + EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1); + EXPECT_EQ(oldInDatagrams, newInDatagrams - 1); + } else { + // System-wide statistics can have some noise. + EXPECT_LE(oldOutDatagrams, newOutDatagrams - 1); + EXPECT_LE(oldInDatagrams, newInDatagrams - 1); + } } TEST(ProcNetSnmp, CheckNetStat) { diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc index e19fe8f6b..66f0e6ca4 100644 --- a/test/syscalls/linux/raw_socket.cc +++ b/test/syscalls/linux/raw_socket.cc @@ -13,8 +13,6 @@ // limitations under the License. #include <arpa/inet.h> -#include <linux/capability.h> -#include <linux/filter.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip6.h> @@ -99,7 +97,7 @@ class RawSocketTest : public ::testing::TestWithParam<std::tuple<int, int>> { }; void RawSocketTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { ASSERT_THAT(socket(Family(), SOCK_RAW, Protocol()), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -123,7 +121,7 @@ void RawSocketTest::SetUp() { void RawSocketTest::TearDown() { // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { EXPECT_THAT(close(s_), SyscallSucceeds()); } } @@ -132,7 +130,7 @@ void RawSocketTest::TearDown() { // BasicRawSocket::Setup creates the first one, so we only have to create one // more here. TEST_P(RawSocketTest, MultipleCreation) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int s2; ASSERT_THAT(s2 = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds()); @@ -142,7 +140,7 @@ TEST_P(RawSocketTest, MultipleCreation) { // Test that shutting down an unconnected socket fails. TEST_P(RawSocketTest, FailShutdownWithoutConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN)); ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN)); @@ -150,7 +148,7 @@ TEST_P(RawSocketTest, FailShutdownWithoutConnect) { // Shutdown is a no-op for raw sockets (and datagram sockets in general). TEST_P(RawSocketTest, ShutdownWriteNoop) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -165,7 +163,7 @@ TEST_P(RawSocketTest, ShutdownWriteNoop) { // Shutdown is a no-op for raw sockets (and datagram sockets in general). TEST_P(RawSocketTest, ShutdownReadNoop) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -182,14 +180,14 @@ TEST_P(RawSocketTest, ShutdownReadNoop) { // Test that listen() fails. TEST_P(RawSocketTest, FailListen) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT(listen(s_, 1), SyscallFailsWithErrno(ENOTSUP)); } // Test that accept() fails. TEST_P(RawSocketTest, FailAccept) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr saddr; socklen_t addrlen; @@ -197,7 +195,7 @@ TEST_P(RawSocketTest, FailAccept) { } TEST_P(RawSocketTest, BindThenGetSockName) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_); ASSERT_THAT(bind(s_, addr, AddrLen()), SyscallSucceeds()); @@ -221,7 +219,7 @@ TEST_P(RawSocketTest, BindThenGetSockName) { } TEST_P(RawSocketTest, ConnectThenGetSockName) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_); ASSERT_THAT(connect(s_, addr, AddrLen()), SyscallSucceeds()); @@ -246,7 +244,7 @@ TEST_P(RawSocketTest, ConnectThenGetSockName) { // Test that getpeername() returns nothing before connect(). TEST_P(RawSocketTest, FailGetPeerNameBeforeConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr saddr; socklen_t addrlen = sizeof(saddr); @@ -256,7 +254,7 @@ TEST_P(RawSocketTest, FailGetPeerNameBeforeConnect) { // Test that getpeername() returns something after connect(). TEST_P(RawSocketTest, GetPeerName) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -270,7 +268,7 @@ TEST_P(RawSocketTest, GetPeerName) { // Test that the socket is writable immediately. TEST_P(RawSocketTest, PollWritableImmediately) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct pollfd pfd = {}; pfd.fd = s_; @@ -280,7 +278,7 @@ TEST_P(RawSocketTest, PollWritableImmediately) { // Test that the socket isn't readable before receiving anything. TEST_P(RawSocketTest, PollNotReadableInitially) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Try to receive data with MSG_DONTWAIT, which returns immediately if there's // nothing to be read. @@ -291,7 +289,7 @@ TEST_P(RawSocketTest, PollNotReadableInitially) { // Test that the socket becomes readable once something is written to it. TEST_P(RawSocketTest, PollTriggeredOnWrite) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Write something so that there's data to be read. // Arbitrary. @@ -306,7 +304,7 @@ TEST_P(RawSocketTest, PollTriggeredOnWrite) { // Test that we can connect() to a valid IP (loopback). TEST_P(RawSocketTest, ConnectToLoopback) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -315,7 +313,7 @@ TEST_P(RawSocketTest, ConnectToLoopback) { // Test that calling send() without connect() fails. TEST_P(RawSocketTest, SendWithoutConnectFails) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Arbitrary. constexpr char kBuf[] = "Endgame was good"; @@ -325,7 +323,7 @@ TEST_P(RawSocketTest, SendWithoutConnectFails) { // Wildcard Bind. TEST_P(RawSocketTest, BindToWildcard) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr_storage addr; addr = {}; @@ -346,16 +344,15 @@ TEST_P(RawSocketTest, BindToWildcard) { // Bind to localhost. TEST_P(RawSocketTest, BindToLocalhost) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); } // Bind to a different address. TEST_P(RawSocketTest, BindToInvalid) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr_storage bind_addr = addr_; if (Family() == AF_INET) { @@ -367,13 +364,14 @@ TEST_P(RawSocketTest, BindToInvalid) { memset(&sin6->sin6_addr.s6_addr, 0, sizeof(sin6->sin6_addr.s6_addr)); sin6->sin6_addr.s6_addr[0] = 1; // 1: - An address that we can't bind to. } - ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr), - AddrLen()), SyscallFailsWithErrno(EADDRNOTAVAIL)); + ASSERT_THAT( + bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr), AddrLen()), + SyscallFailsWithErrno(EADDRNOTAVAIL)); } // Send and receive an packet. TEST_P(RawSocketTest, SendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Arbitrary. constexpr char kBuf[] = "TB12"; @@ -388,7 +386,7 @@ TEST_P(RawSocketTest, SendAndReceive) { // We should be able to create multiple raw sockets for the same protocol and // receive the same packet on both. TEST_P(RawSocketTest, MultipleSocketReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int s2; ASSERT_THAT(s2 = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds()); @@ -403,11 +401,11 @@ TEST_P(RawSocketTest, MultipleSocketReceive) { // Receive it on socket 2. std::vector<char> recv_buf2(sizeof(kBuf) + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBufFrom(s2, recv_buf2.data(), - recv_buf2.size())); + ASSERT_NO_FATAL_FAILURE( + ReceiveBufFrom(s2, recv_buf2.data(), recv_buf2.size())); - EXPECT_EQ(memcmp(recv_buf1.data() + HdrLen(), - recv_buf2.data() + HdrLen(), sizeof(kBuf)), + EXPECT_EQ(memcmp(recv_buf1.data() + HdrLen(), recv_buf2.data() + HdrLen(), + sizeof(kBuf)), 0); ASSERT_THAT(close(s2), SyscallSucceeds()); @@ -415,7 +413,7 @@ TEST_P(RawSocketTest, MultipleSocketReceive) { // Test that connect sends packets to the right place. TEST_P(RawSocketTest, SendAndReceiveViaConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -434,11 +432,10 @@ TEST_P(RawSocketTest, SendAndReceiveViaConnect) { // Bind to localhost, then send and receive packets. TEST_P(RawSocketTest, BindSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); // Arbitrary. constexpr char kBuf[] = "DR16"; @@ -452,11 +449,10 @@ TEST_P(RawSocketTest, BindSendAndReceive) { // Bind and connect to localhost and send/receive packets. TEST_P(RawSocketTest, BindConnectSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), SyscallSucceeds()); @@ -474,7 +470,7 @@ TEST_P(RawSocketTest, BindConnectSendAndReceive) { // Check that setting SO_RCVBUF below min is clamped to the minimum // receive buffer size. TEST_P(RawSocketTest, SetSocketRecvBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Discover minimum receive buf size by trying to set it to zero. // See: @@ -507,7 +503,7 @@ TEST_P(RawSocketTest, SetSocketRecvBufBelowMin) { // Check that setting SO_RCVBUF above max is clamped to the maximum // receive buffer size. TEST_P(RawSocketTest, SetSocketRecvBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Discover max buf size by trying to set the largest possible buffer size. constexpr int kRcvBufSz = 0xffffffff; @@ -534,7 +530,7 @@ TEST_P(RawSocketTest, SetSocketRecvBufAboveMax) { // Check that setting SO_RCVBUF min <= kRcvBufSz <= max is honored. TEST_P(RawSocketTest, SetSocketRecvBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int max = 0; int min = 0; @@ -584,7 +580,7 @@ TEST_P(RawSocketTest, SetSocketRecvBuf) { // Check that setting SO_SNDBUF below min is clamped to the minimum // receive buffer size. TEST_P(RawSocketTest, SetSocketSendBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Discover minimum buffer size by trying to set it to zero. constexpr int kSndBufSz = 0; @@ -615,7 +611,7 @@ TEST_P(RawSocketTest, SetSocketSendBufBelowMin) { // Check that setting SO_SNDBUF above max is clamped to the maximum // send buffer size. TEST_P(RawSocketTest, SetSocketSendBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Discover maximum buffer size by trying to set it to a large value. constexpr int kSndBufSz = 0xffffffff; @@ -642,7 +638,7 @@ TEST_P(RawSocketTest, SetSocketSendBufAboveMax) { // Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored. TEST_P(RawSocketTest, SetSocketSendBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int max = 0; int min = 0; @@ -688,11 +684,10 @@ TEST_P(RawSocketTest, SetSocketSendBuf) { // Test that receive buffer limits are not enforced when the recv buffer is // empty. TEST_P(RawSocketTest, RecvBufLimitsEmptyRecvBuffer) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), SyscallSucceeds()); @@ -719,9 +714,7 @@ TEST_P(RawSocketTest, RecvBufLimitsEmptyRecvBuffer) { // Receive the packet and make sure it's identical. std::vector<char> recv_buf(buf.size() + HdrLen()); ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ( - memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), - 0); + EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), 0); } { @@ -734,9 +727,7 @@ TEST_P(RawSocketTest, RecvBufLimitsEmptyRecvBuffer) { // Receive the packet and make sure it's identical. std::vector<char> recv_buf(buf.size() + HdrLen()); ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ( - memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), - 0); + EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), 0); } } @@ -750,11 +741,10 @@ TEST_P(RawSocketTest, RecvBufLimits) { if (Protocol() == IPPROTO_TCP) { return; } - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), SyscallSucceeds()); @@ -814,9 +804,7 @@ TEST_P(RawSocketTest, RecvBufLimits) { // Receive the packet and make sure it's identical. std::vector<char> recv_buf(buf.size() + HdrLen()); ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(), - buf.size()), - 0); + EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), 0); } // Assert that the last packet is dropped because the receive buffer should @@ -885,7 +873,7 @@ TEST_P(RawSocketTest, GetSocketDetachFilter) { // AF_INET6+SOCK_RAW+IPPROTO_RAW sockets can be created, but not written to. TEST(RawSocketTest, IPv6ProtoRaw) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int sock; ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW), @@ -902,7 +890,7 @@ TEST(RawSocketTest, IPv6ProtoRaw) { } TEST(RawSocketTest, IPv6SendMsg) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int sock; ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), @@ -930,7 +918,7 @@ TEST(RawSocketTest, IPv6SendMsg) { } TEST_P(RawSocketTest, ConnectOnIPv6Socket) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int sock; ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), diff --git a/test/syscalls/linux/raw_socket_hdrincl.cc b/test/syscalls/linux/raw_socket_hdrincl.cc index f1d8fd295..d45bd07bc 100644 --- a/test/syscalls/linux/raw_socket_hdrincl.cc +++ b/test/syscalls/linux/raw_socket_hdrincl.cc @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <linux/capability.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> @@ -63,7 +62,7 @@ class RawHDRINCL : public ::testing::Test { }; void RawHDRINCL::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { ASSERT_THAT(socket(AF_INET, SOCK_RAW, IPPROTO_RAW), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -81,7 +80,7 @@ void RawHDRINCL::SetUp() { void RawHDRINCL::TearDown() { // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { EXPECT_THAT(close(socket_), SyscallSucceeds()); } } diff --git a/test/syscalls/linux/raw_socket_icmp.cc b/test/syscalls/linux/raw_socket_icmp.cc index 27d3fffee..3f9717284 100644 --- a/test/syscalls/linux/raw_socket_icmp.cc +++ b/test/syscalls/linux/raw_socket_icmp.cc @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <linux/capability.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> @@ -77,7 +76,7 @@ class RawSocketICMPTest : public ::testing::Test { }; void RawSocketICMPTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { ASSERT_THAT(socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -95,7 +94,7 @@ void RawSocketICMPTest::SetUp() { void RawSocketICMPTest::TearDown() { // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { EXPECT_THAT(close(s_), SyscallSucceeds()); } } @@ -103,7 +102,7 @@ void RawSocketICMPTest::TearDown() { // We'll only read an echo in this case, as the kernel won't respond to the // malformed ICMP checksum. TEST_F(RawSocketICMPTest, SendAndReceiveBadChecksum) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence, // and ID. None of that should matter for raw sockets - the kernel should @@ -132,7 +131,7 @@ TEST_F(RawSocketICMPTest, SendAndReceiveBadChecksum) { // Send and receive an ICMP packet. TEST_F(RawSocketICMPTest, SendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Prepare and send an ICMP packet. Use arbitrary junk for sequence and ID. // None of that should matter for raw sockets - the kernel should still give @@ -152,7 +151,7 @@ TEST_F(RawSocketICMPTest, SendAndReceive) { // We should be able to create multiple raw sockets for the same protocol and // receive the same packet on both. TEST_F(RawSocketICMPTest, MultipleSocketReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); FileDescriptor s2 = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)); @@ -215,7 +214,7 @@ TEST_F(RawSocketICMPTest, MultipleSocketReceive) { // A raw ICMP socket and ping socket should both receive the ICMP packets // intended for the ping socket. TEST_F(RawSocketICMPTest, RawAndPingSockets) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); FileDescriptor ping_sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); @@ -265,7 +264,7 @@ TEST_F(RawSocketICMPTest, RawAndPingSockets) { // while a ping socket should not. Neither should be able to receieve a short // malformed packet. TEST_F(RawSocketICMPTest, ShortEchoRawAndPingSockets) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); FileDescriptor ping_sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); @@ -306,7 +305,7 @@ TEST_F(RawSocketICMPTest, ShortEchoRawAndPingSockets) { // while ping socket should not. // Neither should be able to receieve a short malformed packet. TEST_F(RawSocketICMPTest, ShortEchoReplyRawAndPingSockets) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); FileDescriptor ping_sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); @@ -345,7 +344,7 @@ TEST_F(RawSocketICMPTest, ShortEchoReplyRawAndPingSockets) { // Test that connect() sends packets to the right place. TEST_F(RawSocketICMPTest, SendAndReceiveViaConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), @@ -369,7 +368,7 @@ TEST_F(RawSocketICMPTest, SendAndReceiveViaConnect) { // Bind to localhost, then send and receive packets. TEST_F(RawSocketICMPTest, BindSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), @@ -392,7 +391,7 @@ TEST_F(RawSocketICMPTest, BindSendAndReceive) { // Bind and connect to localhost and send/receive packets. TEST_F(RawSocketICMPTest, BindConnectSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), @@ -418,7 +417,7 @@ TEST_F(RawSocketICMPTest, BindConnectSendAndReceive) { // Set and get SO_LINGER. TEST_F(RawSocketICMPTest, SetAndGetSocketLinger) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int level = SOL_SOCKET; int type = SO_LINGER; @@ -440,7 +439,7 @@ TEST_F(RawSocketICMPTest, SetAndGetSocketLinger) { // Test getsockopt for SO_ACCEPTCONN. TEST_F(RawSocketICMPTest, GetSocketAcceptConn) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int got = -1; socklen_t length = sizeof(got); diff --git a/test/syscalls/linux/stat.cc b/test/syscalls/linux/stat.cc index 72f888659..19dc80d0c 100644 --- a/test/syscalls/linux/stat.cc +++ b/test/syscalls/linux/stat.cc @@ -765,7 +765,7 @@ TEST_F(StatTest, StatxSymlink) { SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 && errno == ENOSYS); - std::string parent_dir = "/tmp"; + std::string parent_dir = GetAbsoluteTestTmpdir(); TempPath link = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateSymlinkTo(parent_dir, test_file_name_)); std::string p = link.path(); diff --git a/test/syscalls/linux/statfs.cc b/test/syscalls/linux/statfs.cc index d4ea8e026..d057cdc09 100644 --- a/test/syscalls/linux/statfs.cc +++ b/test/syscalls/linux/statfs.cc @@ -28,7 +28,7 @@ namespace testing { namespace { TEST(StatfsTest, CannotStatBadPath) { - auto temp_file = NewTempAbsPathInDir("/tmp"); + auto temp_file = NewTempAbsPath(); struct statfs st; EXPECT_THAT(statfs(temp_file.c_str(), &st), SyscallFailsWithErrno(ENOENT)); diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index cb77986c2..3fbbf1423 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -2088,6 +2088,66 @@ TEST_P(SimpleTcpSocketTest, ConnectUnspecifiedAddress) { } } +// Tests that send will return EWOULDBLOCK initially with large buffer and will +// succeed after the send buffer size is increased. +TEST_P(TcpSocketTest, SendUnblocksOnSendBufferIncrease) { + // Set the FD to O_NONBLOCK. + int opts; + ASSERT_THAT(opts = fcntl(first_fd, F_GETFL), SyscallSucceeds()); + opts |= O_NONBLOCK; + ASSERT_THAT(fcntl(first_fd, F_SETFL, opts), SyscallSucceeds()); + + // Get maximum buffer size by trying to set it to a large value. + constexpr int kSndBufSz = 0xffffffff; + ASSERT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, + sizeof(kSndBufSz)), + SyscallSucceeds()); + + int max_buffer_sz = 0; + socklen_t max_len = sizeof(max_buffer_sz); + ASSERT_THAT( + getsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &max_buffer_sz, &max_len), + SyscallSucceeds()); + + int buffer_sz = max_buffer_sz >> 2; + EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &buffer_sz, + sizeof(buffer_sz)), + SyscallSucceedsWithValue(0)); + + // Create a large buffer that will be used for sending. + std::vector<char> buffer(max_buffer_sz); + + // Write until we receive an error. + while (RetryEINTR(send)(first_fd, buffer.data(), buffer.size(), 0) != -1) { + // Sleep to give linux a chance to move data from the send buffer to the + // receive buffer. + usleep(10000); // 10ms. + } + + // The last error should have been EWOULDBLOCK. + ASSERT_EQ(errno, EWOULDBLOCK); + + ScopedThread send_thread([this]() { + int flags = 0; + ASSERT_THAT(flags = fcntl(first_fd, F_GETFL), SyscallSucceeds()); + EXPECT_THAT(fcntl(first_fd, F_SETFL, flags & ~O_NONBLOCK), + SyscallSucceeds()); + + // Expect the send() to succeed. + char buffer; + ASSERT_THAT(RetryEINTR(send)(first_fd, &buffer, sizeof(buffer), 0), + SyscallSucceeds()); + }); + + // Set SO_SNDBUF to maximum buffer size allowed. + buffer_sz = max_buffer_sz >> 1; + EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &buffer_sz, + sizeof(buffer_sz)), + SyscallSucceedsWithValue(0)); + + send_thread.Join(); +} + INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest, ::testing::Values(AF_INET, AF_INET6)); diff --git a/test/util/BUILD b/test/util/BUILD index 4a4401ba8..b92af1c27 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -8,13 +8,20 @@ package( cc_library( name = "capability_util", testonly = 1, - srcs = ["capability_util.cc"], - hdrs = ["capability_util.h"], + srcs = [ + "fuchsia_capability_util.cc", + "linux_capability_util.cc", + ], + hdrs = [ + "capability_util.h", + "linux_capability_util.h", + ], deps = [ ":cleanup", ":memory_util", ":posix_error", ":save_util", + ":socket_util", ":test_util", "@com_google_absl//absl/strings", ], diff --git a/test/util/capability_util.h b/test/util/capability_util.h index c4b0feade..318a43feb 100644 --- a/test/util/capability_util.h +++ b/test/util/capability_util.h @@ -17,112 +17,30 @@ #ifndef GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ #define GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ -#ifdef __linux__ - -#include <errno.h> -#include <linux/capability.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include "test/util/cleanup.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" - -#ifndef _LINUX_CAPABILITY_VERSION_3 -#error Expecting _LINUX_CAPABILITY_VERSION_3 support +#if defined(__Fuchsia__) +// Nothing to include. +#elif defined(__linux__) +#include "test/util/linux_capability_util.h" +#else +#error "Unhandled platform" #endif namespace gvisor { namespace testing { -// HaveCapability returns true if the process has the specified EFFECTIVE -// capability. -inline PosixErrorOr<bool> HaveCapability(int cap) { - if (!cap_valid(cap)) { - return PosixError(EINVAL, "Invalid capability"); - } - - struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; - struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); - MaybeSave(); - - return (caps[CAP_TO_INDEX(cap)].effective & CAP_TO_MASK(cap)) != 0; -} - -// SetCapability sets the specified EFFECTIVE capability. -inline PosixError SetCapability(int cap, bool set) { - if (!cap_valid(cap)) { - return PosixError(EINVAL, "Invalid capability"); - } - - struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; - struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); - MaybeSave(); - - if (set) { - caps[CAP_TO_INDEX(cap)].effective |= CAP_TO_MASK(cap); - } else { - caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); - } - header = {_LINUX_CAPABILITY_VERSION_3, 0}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); - MaybeSave(); - - return NoError(); -} - -// DropPermittedCapability drops the specified PERMITTED. The EFFECTIVE -// capabilities must be a subset of PERMITTED, so those are dropped as well. -inline PosixError DropPermittedCapability(int cap) { - if (!cap_valid(cap)) { - return PosixError(EINVAL, "Invalid capability"); - } - - struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; - struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); - MaybeSave(); - - caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); - caps[CAP_TO_INDEX(cap)].permitted &= ~CAP_TO_MASK(cap); - - header = {_LINUX_CAPABILITY_VERSION_3, 0}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); - MaybeSave(); - - return NoError(); -} - -PosixErrorOr<bool> CanCreateUserNamespace(); - -class AutoCapability { - public: - AutoCapability(int cap, bool set) : cap_(cap), set_(set) { - const bool has = EXPECT_NO_ERRNO_AND_VALUE(HaveCapability(cap)); - if (set != has) { - EXPECT_NO_ERRNO(SetCapability(cap_, set_)); - applied_ = true; - } - } - - ~AutoCapability() { - if (applied_) { - EXPECT_NO_ERRNO(SetCapability(cap_, !set_)); - } - } +// HaveRawIPSocketCapability returns whether or not the process has access to +// raw IP sockets. +// +// Returns an error when raw IP socket access cannot be determined. +PosixErrorOr<bool> HaveRawIPSocketCapability(); - private: - int cap_; - bool set_; - bool applied_ = false; -}; +// HavePacketSocketCapability returns whether or not the process has access to +// packet sockets. +// +// Returns an error when packet socket access cannot be determined. +PosixErrorOr<bool> HavePacketSocketCapability(); } // namespace testing } // namespace gvisor -#endif // __linux__ - #endif // GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ diff --git a/test/util/cgroup_util.cc b/test/util/cgroup_util.cc index 977993f41..df3c57b87 100644 --- a/test/util/cgroup_util.cc +++ b/test/util/cgroup_util.cc @@ -25,12 +25,26 @@ namespace gvisor { namespace testing { -Cgroup::Cgroup(std::string_view path) : cgroup_path_(path) { +Cgroup::Cgroup(absl::string_view path) : cgroup_path_(path) { id_ = ++Cgroup::next_id_; std::cerr << absl::StreamFormat("[cg#%d] <= %s", id_, cgroup_path_) << std::endl; } +PosixErrorOr<Cgroup> Cgroup::RecursivelyCreate(absl::string_view path) { + RETURN_IF_ERRNO(RecursivelyCreateDir(path)); + return Cgroup(path); +} + +PosixErrorOr<Cgroup> Cgroup::Create(absl::string_view path) { + RETURN_IF_ERRNO(Mkdir(path)); + return Cgroup(path); +} + +PosixErrorOr<Cgroup> Cgroup::CreateChild(absl::string_view name) const { + return Cgroup::Create(JoinPath(Path(), name)); +} + PosixErrorOr<std::string> Cgroup::ReadControlFile( absl::string_view name) const { std::string buf; @@ -93,7 +107,7 @@ PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::ParsePIDList( absl::string_view data) const { absl::flat_hash_set<pid_t> res; std::vector<absl::string_view> lines = absl::StrSplit(data, '\n'); - for (const std::string_view& line : lines) { + for (const absl::string_view& line : lines) { if (line.empty()) { continue; } diff --git a/test/util/cgroup_util.h b/test/util/cgroup_util.h index e3f696a89..ccc7219e3 100644 --- a/test/util/cgroup_util.h +++ b/test/util/cgroup_util.h @@ -34,8 +34,20 @@ class Cgroup { uint64_t id() const { return id_; } + // RecursivelyCreate creates cgroup specified by path, including all + // components leading up to path. Path should end inside a cgroupfs mount. If + // path already exists, RecursivelyCreate does nothing and silently succeeds. + static PosixErrorOr<Cgroup> RecursivelyCreate(std::string_view path); + + // Creates a new cgroup at path. The parent directory must exist and be a + // cgroupfs directory. + static PosixErrorOr<Cgroup> Create(std::string_view path); + const std::string& Path() const { return cgroup_path_; } + // Creates a child cgroup under this cgroup with the given name. + PosixErrorOr<Cgroup> CreateChild(std::string_view name) const; + std::string Relpath(absl::string_view leaf) const { return JoinPath(cgroup_path_, leaf); } diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc index 483ae848d..253411858 100644 --- a/test/util/fs_util.cc +++ b/test/util/fs_util.cc @@ -201,7 +201,8 @@ PosixError UnlinkAt(const FileDescriptor& dfd, absl::string_view path, PosixError Mkdir(absl::string_view path, int mode) { int res = mkdir(std::string(path).c_str(), mode); if (res < 0) { - return PosixError(errno, absl::StrCat("mkdir ", path, " mode ", mode)); + return PosixError(errno, + absl::StrFormat("mkdir \"%s\" mode %#o", path, mode)); } return NoError(); diff --git a/test/util/fuchsia_capability_util.cc b/test/util/fuchsia_capability_util.cc new file mode 100644 index 000000000..bbe8643e7 --- /dev/null +++ b/test/util/fuchsia_capability_util.cc @@ -0,0 +1,73 @@ +// Copyright 2021 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. + +#ifdef __Fuchsia__ + +#include <netinet/if_ether.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include "test/util/socket_util.h" + +namespace gvisor { +namespace testing { + +// On Linux, access to raw IP and packet socket is controlled by a single +// capability (CAP_NET_RAW). However on Fuchsia, access to raw IP and packet +// sockets are controlled by separate capabilities/protocols. + +namespace { + +PosixErrorOr<bool> HaveSocketCapability(int domain, int type, int protocol) { + // Fuchsia does not have a platform supported way to check the protocols made + // available to a sandbox. As a workaround, simply try to create the specified + // socket and assume no access if we get a no permissions error. + auto s = Socket(domain, type, protocol); + if (s.ok()) { + return true; + } + if (s.error().errno_value() == EPERM) { + return false; + } + return s.error(); +} + +} // namespace + +PosixErrorOr<bool> HaveRawIPSocketCapability() { + static PosixErrorOr<bool> result(false); + static std::once_flag once; + + std::call_once(once, [&]() { + result = HaveSocketCapability(AF_INET, SOCK_RAW, IPPROTO_UDP); + }); + + return result; +} + +PosixErrorOr<bool> HavePacketSocketCapability() { + static PosixErrorOr<bool> result(false); + static std::once_flag once; + + std::call_once(once, [&]() { + result = HaveSocketCapability(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + }); + + return result; +} + +} // namespace testing +} // namespace gvisor + +#endif // __Fuchsia__ diff --git a/test/util/capability_util.cc b/test/util/linux_capability_util.cc index 3bf218128..7218aa4ac 100644 --- a/test/util/capability_util.cc +++ b/test/util/linux_capability_util.cc @@ -14,7 +14,7 @@ #ifdef __linux__ -#include "test/util/capability_util.h" +#include "test/util/linux_capability_util.h" #include <linux/capability.h> #include <sched.h> @@ -32,6 +32,14 @@ namespace gvisor { namespace testing { +PosixErrorOr<bool> HaveRawIPSocketCapability() { + return HaveCapability(CAP_NET_RAW); +} + +PosixErrorOr<bool> HavePacketSocketCapability() { + return HaveCapability(CAP_NET_RAW); +} + PosixErrorOr<bool> CanCreateUserNamespace() { // The most reliable way to determine if userns creation is possible is by // trying to create one; see below. diff --git a/test/util/linux_capability_util.h b/test/util/linux_capability_util.h new file mode 100644 index 000000000..be94ebd19 --- /dev/null +++ b/test/util/linux_capability_util.h @@ -0,0 +1,128 @@ +// 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. + +// Utilities for testing capabilities on Linux. + +#ifndef GVISOR_TEST_UTIL_LINUX_CAPABILITY_UTIL_H_ +#define GVISOR_TEST_UTIL_LINUX_CAPABILITY_UTIL_H_ + +#ifdef __linux__ + +#include <errno.h> +#include <linux/capability.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "test/util/cleanup.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" +#include "test/util/test_util.h" + +#ifndef _LINUX_CAPABILITY_VERSION_3 +#error Expecting _LINUX_CAPABILITY_VERSION_3 support +#endif + +namespace gvisor { +namespace testing { + +// HaveCapability returns true if the process has the specified EFFECTIVE +// capability. +inline PosixErrorOr<bool> HaveCapability(int cap) { + if (!cap_valid(cap)) { + return PosixError(EINVAL, "Invalid capability"); + } + + struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; + struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); + MaybeSave(); + + return (caps[CAP_TO_INDEX(cap)].effective & CAP_TO_MASK(cap)) != 0; +} + +// SetCapability sets the specified EFFECTIVE capability. +inline PosixError SetCapability(int cap, bool set) { + if (!cap_valid(cap)) { + return PosixError(EINVAL, "Invalid capability"); + } + + struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; + struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); + MaybeSave(); + + if (set) { + caps[CAP_TO_INDEX(cap)].effective |= CAP_TO_MASK(cap); + } else { + caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); + } + header = {_LINUX_CAPABILITY_VERSION_3, 0}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); + MaybeSave(); + + return NoError(); +} + +// DropPermittedCapability drops the specified PERMITTED. The EFFECTIVE +// capabilities must be a subset of PERMITTED, so those are dropped as well. +inline PosixError DropPermittedCapability(int cap) { + if (!cap_valid(cap)) { + return PosixError(EINVAL, "Invalid capability"); + } + + struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; + struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); + MaybeSave(); + + caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); + caps[CAP_TO_INDEX(cap)].permitted &= ~CAP_TO_MASK(cap); + + header = {_LINUX_CAPABILITY_VERSION_3, 0}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); + MaybeSave(); + + return NoError(); +} + +PosixErrorOr<bool> CanCreateUserNamespace(); + +class AutoCapability { + public: + AutoCapability(int cap, bool set) : cap_(cap), set_(set) { + const bool has = EXPECT_NO_ERRNO_AND_VALUE(HaveCapability(cap)); + if (set != has) { + EXPECT_NO_ERRNO(SetCapability(cap_, set_)); + applied_ = true; + } + } + + ~AutoCapability() { + if (applied_) { + EXPECT_NO_ERRNO(SetCapability(cap_, !set_)); + } + } + + private: + int cap_; + bool set_; + bool applied_ = false; +}; + +} // namespace testing +} // namespace gvisor + +#endif // __linux__ + +#endif // GVISOR_TEST_UTIL_LINUX_CAPABILITY_UTIL_H_ |