diff options
Diffstat (limited to 'test/syscalls')
45 files changed, 1941 insertions, 427 deletions
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index b5a4ef4df..0da35f7be 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -1,3 +1,4 @@ +load("//tools:defs.bzl", "more_shards", "most_shards") load("//test/runner:defs.bzl", "syscall_test") package(licenses = ["notice"]) @@ -12,7 +13,7 @@ syscall_test( syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:accept_bind_test", ) @@ -32,7 +33,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:alarm_test", ) @@ -66,7 +67,7 @@ syscall_test( size = "large", # Produce too many logs in the debug mode. debug = False, - shard_count = 50, + shard_count = most_shards, # Takes too long for TSAN. Since this is kind of a stress test that doesn't # involve much concurrency, TSAN's usefulness here is limited anyway. tags = ["nogotsan"], @@ -211,7 +212,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:futex_test", ) @@ -258,7 +259,7 @@ syscall_test( syscall_test( size = "large", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:itimer_test", ) @@ -313,7 +314,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:mmap_test", ) @@ -347,6 +348,7 @@ syscall_test( syscall_test( add_overlay = True, + shard_count = more_shards, test = "//test/syscalls/linux:open_test", ) @@ -376,7 +378,7 @@ syscall_test( syscall_test( size = "large", add_overlay = True, - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:pipe_test", ) @@ -448,7 +450,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:pty_test", ) @@ -475,6 +477,7 @@ syscall_test( ) syscall_test( + shard_count = more_shards, test = "//test/syscalls/linux:raw_socket_test", ) @@ -490,7 +493,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:readv_socket_test", ) @@ -539,7 +542,7 @@ syscall_test( ) syscall_test( - shard_count = 20, + shard_count = more_shards, test = "//test/syscalls/linux:semaphore_test", ) @@ -594,7 +597,7 @@ syscall_test( syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_abstract_test", ) @@ -605,7 +608,7 @@ syscall_test( syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_domain_test", ) @@ -618,55 +621,62 @@ syscall_test( syscall_test( size = "large", add_overlay = True, - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_filesystem_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_inet_loopback_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, # Takes too long for TSAN. Creates a lot of TCP sockets. tags = ["nogotsan"], test = "//test/syscalls/linux:socket_inet_loopback_nogotsan_test", ) syscall_test( + test = "//test/syscalls/linux:socket_ipv4_udp_unbound_external_networking_test", +) + +syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_ip_tcp_generic_loopback_test", ) syscall_test( size = "medium", + add_hostinet = True, test = "//test/syscalls/linux:socket_ip_tcp_loopback_non_blocking_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_ip_tcp_loopback_test", ) syscall_test( size = "medium", - shard_count = 50, + add_hostinet = True, + shard_count = most_shards, test = "//test/syscalls/linux:socket_ip_tcp_udp_generic_loopback_test", ) syscall_test( size = "medium", + add_hostinet = True, test = "//test/syscalls/linux:socket_ip_udp_loopback_non_blocking_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_ip_udp_loopback_test", ) @@ -677,6 +687,13 @@ syscall_test( syscall_test( size = "medium", + test = "//test/syscalls/linux:socket_ipv6_udp_unbound_loopback_test", +) + +syscall_test( + size = "medium", + add_hostinet = True, + shard_count = more_shards, # Takes too long under gotsan to run. tags = ["nogotsan"], test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_nogotsan_test", @@ -691,6 +708,7 @@ syscall_test( ) syscall_test( + shard_count = more_shards, test = "//test/syscalls/linux:socket_ip_unbound_test", ) @@ -723,6 +741,7 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:socket_non_stream_blocking_local_test", ) @@ -753,7 +772,7 @@ syscall_test( syscall_test( # NOTE(b/116636318): Large sendmsg may stall a long time. size = "enormous", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:socket_unix_dgram_local_test", ) @@ -765,14 +784,14 @@ syscall_test( syscall_test( size = "large", add_overlay = True, - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_unix_pair_test", ) syscall_test( # NOTE(b/116636318): Large sendmsg may stall a long time. size = "enormous", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:socket_unix_seqpacket_local_test", ) @@ -798,13 +817,13 @@ syscall_test( syscall_test( size = "medium", - shard_count = 10, + shard_count = more_shards, test = "//test/syscalls/linux:socket_unix_unbound_seqpacket_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_unix_unbound_stream_test", ) @@ -858,7 +877,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 10, + shard_count = more_shards, test = "//test/syscalls/linux:tcp_socket_test", ) @@ -867,6 +886,7 @@ syscall_test( ) syscall_test( + shard_count = more_shards, test = "//test/syscalls/linux:timerfd_test", ) @@ -897,13 +917,14 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:udp_bind_test", ) syscall_test( size = "medium", add_hostinet = True, - shard_count = 10, + shard_count = more_shards, test = "//test/syscalls/linux:udp_socket_test", ) @@ -947,7 +968,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:wait_test", ) @@ -961,6 +982,7 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:proc_net_tcp_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 2350f7e69..017f997de 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -19,9 +19,10 @@ exports_files( "socket_ip_udp_loopback_blocking.cc", "socket_ip_udp_loopback_nonblock.cc", "socket_ip_unbound.cc", - "socket_ipv4_tcp_unbound_external_networking_test.cc", "socket_ipv4_udp_unbound_external_networking_test.cc", + "socket_ipv6_udp_unbound_external_networking_test.cc", "socket_ipv4_udp_unbound_loopback.cc", + "socket_ipv6_udp_unbound_loopback.cc", "socket_ipv4_udp_unbound_loopback_nogotsan.cc", "tcp_socket.cc", "udp_bind.cc", @@ -433,6 +434,9 @@ cc_binary( testonly = 1, srcs = ["chown.cc"], linkstatic = 1, + # We require additional UIDs for this test, so don't include the bazel + # sandbox as standard. + tags = ["no-sandbox"], deps = [ "//test/util:capability_util", "//test/util:file_descriptor", @@ -619,10 +623,7 @@ cc_binary( cc_binary( name = "exceptions_test", testonly = 1, - srcs = select_arch( - amd64 = ["exceptions.cc"], - arm64 = [], - ), + srcs = ["exceptions.cc"], linkstatic = 1, deps = [ gtest, @@ -797,8 +798,8 @@ cc_binary( deps = [ ":socket_test_util", "//test/util:cleanup", - "//test/util:epoll_util", "//test/util:eventfd_util", + "//test/util:file_descriptor", "//test/util:fs_util", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/flags:flag", @@ -809,6 +810,7 @@ cc_binary( "//test/util:multiprocess_util", "//test/util:posix_error", "//test/util:save_util", + "//test/util:signal_util", "//test/util:temp_path", "//test/util:test_util", "//test/util:thread_util", @@ -944,6 +946,7 @@ cc_binary( "//test/util:eventfd_util", "//test/util:file_descriptor", "//test/util:fs_util", + "@com_google_absl//absl/container:node_hash_map", "@com_google_absl//absl/container:node_hash_set", "@com_google_absl//absl/strings", gtest, @@ -2451,6 +2454,27 @@ cc_library( ) cc_library( + name = "socket_ipv6_udp_unbound_test_cases", + testonly = 1, + srcs = [ + "socket_ipv6_udp_unbound.cc", + ], + hdrs = [ + "socket_ipv6_udp_unbound.h", + ], + deps = [ + ":ip_socket_test_util", + ":socket_test_util", + "@com_google_absl//absl/memory", + gtest, + "//test/util:posix_error", + "//test/util:save_util", + "//test/util:test_util", + ], + alwayslink = 1, +) + +cc_library( name = "socket_ipv4_udp_unbound_netlink_test_cases", testonly = 1, srcs = [ @@ -2488,6 +2512,23 @@ cc_library( ) cc_library( + name = "socket_ip_udp_unbound_external_networking", + testonly = 1, + srcs = [ + "socket_ip_udp_unbound_external_networking.cc", + ], + hdrs = [ + "socket_ip_udp_unbound_external_networking.h", + ], + deps = [ + ":ip_socket_test_util", + ":socket_test_util", + "//test/util:test_util", + ], + alwayslink = 1, +) + +cc_library( name = "socket_ipv4_udp_unbound_external_networking_test_cases", testonly = 1, srcs = [ @@ -2497,28 +2538,24 @@ cc_library( "socket_ipv4_udp_unbound_external_networking.h", ], deps = [ - ":ip_socket_test_util", - ":socket_test_util", + ":socket_ip_udp_unbound_external_networking", gtest, - "//test/util:test_util", ], alwayslink = 1, ) cc_library( - name = "socket_ipv4_tcp_unbound_external_networking_test_cases", + name = "socket_ipv6_udp_unbound_external_networking_test_cases", testonly = 1, srcs = [ - "socket_ipv4_tcp_unbound_external_networking.cc", + "socket_ipv6_udp_unbound_external_networking.cc", ], hdrs = [ - "socket_ipv4_tcp_unbound_external_networking.h", + "socket_ipv6_udp_unbound_external_networking.h", ], deps = [ - ":ip_socket_test_util", - ":socket_test_util", + ":socket_ip_udp_unbound_external_networking", gtest, - "//test/util:test_util", ], alwayslink = 1, ) @@ -2718,15 +2755,15 @@ cc_binary( ) cc_binary( - name = "socket_ipv4_tcp_unbound_external_networking_test", + name = "socket_ipv6_udp_unbound_external_networking_test", testonly = 1, srcs = [ - "socket_ipv4_tcp_unbound_external_networking_test.cc", + "socket_ipv6_udp_unbound_external_networking_test.cc", ], linkstatic = 1, deps = [ ":ip_socket_test_util", - ":socket_ipv4_tcp_unbound_external_networking_test_cases", + ":socket_ipv6_udp_unbound_external_networking_test_cases", ":socket_test_util", "//test/util:test_main", "//test/util:test_util", @@ -2824,6 +2861,22 @@ cc_binary( ) cc_binary( + name = "socket_ipv6_udp_unbound_loopback_test", + testonly = 1, + srcs = [ + "socket_ipv6_udp_unbound_loopback.cc", + ], + linkstatic = 1, + deps = [ + ":ip_socket_test_util", + ":socket_ipv6_udp_unbound_test_cases", + ":socket_test_util", + "//test/util:test_main", + "//test/util:test_util", + ], +) + +cc_binary( name = "socket_ipv4_udp_unbound_loopback_nogotsan_test", testonly = 1, srcs = [ @@ -3320,6 +3373,7 @@ cc_binary( ":socket_test_util", ":unix_domain_socket_test_util", gtest, + "//test/util:file_descriptor", "//test/util:test_main", "//test/util:test_util", ], diff --git a/test/syscalls/linux/chown.cc b/test/syscalls/linux/chown.cc index 7a28b674d..5530ad18f 100644 --- a/test/syscalls/linux/chown.cc +++ b/test/syscalls/linux/chown.cc @@ -75,7 +75,16 @@ TEST_P(ChownParamTest, ChownFileSucceeds) { if (num_groups > 0) { std::vector<gid_t> list(num_groups); EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds()); - gid = list[0]; + // Scan the list of groups for a valid gid. Note that if a group is not + // defined in this local user namespace, then we will see 65534, and the + // group will not chown below as expected. So only change if we find a + // valid group in this list. + for (const gid_t other_gid : list) { + if (other_gid != 65534) { + gid = other_gid; + break; + } + } } EXPECT_NO_ERRNO(GetParam()(file.path(), geteuid(), gid)); @@ -90,6 +99,7 @@ TEST_P(ChownParamTest, ChownFilePermissionDenied) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0777)); + EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds()); // Drop privileges and change IDs only in child thread, or else this parent // thread won't be able to open some log files after the test ends. @@ -119,6 +129,7 @@ TEST_P(ChownParamTest, ChownFileSucceedsAsRoot) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_SETUID)))); const std::string filename = NewTempAbsPath(); + EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds()); absl::Notification fileCreated, fileChowned; // Change UID only in child thread, or else this parent thread won't be able diff --git a/test/syscalls/linux/exceptions.cc b/test/syscalls/linux/exceptions.cc index 420b9543f..11dc1c651 100644 --- a/test/syscalls/linux/exceptions.cc +++ b/test/syscalls/linux/exceptions.cc @@ -23,6 +23,7 @@ namespace gvisor { namespace testing { +#if defined(__x86_64__) // Default value for the x87 FPU control word. See Intel SDM Vol 1, Ch 8.1.5 // "x87 FPU Control Word". constexpr uint16_t kX87ControlWordDefault = 0x37f; @@ -93,6 +94,9 @@ void InIOHelper(int width, int value) { }, ::testing::KilledBySignal(SIGSEGV), ""); } +#elif defined(__aarch64__) +void inline Halt() { asm("hlt #0\r\n"); } +#endif TEST(ExceptionTest, Halt) { // In order to prevent the regular handler from messing with things (and @@ -102,9 +106,14 @@ TEST(ExceptionTest, Halt) { sa.sa_handler = SIG_DFL; auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); +#if defined(__x86_64__) EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGSEGV), ""); +#elif defined(__aarch64__) + EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGILL), ""); +#endif } +#if defined(__x86_64__) TEST(ExceptionTest, DivideByZero) { // See above. struct sigaction sa = {}; @@ -362,6 +371,7 @@ TEST(ExceptionTest, Int3Compact) { EXPECT_EXIT(Int3Compact(), ::testing::KilledBySignal(SIGTRAP), ""); } +#endif } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc index 34016d4bd..4b581045b 100644 --- a/test/syscalls/linux/fcntl.cc +++ b/test/syscalls/linux/fcntl.cc @@ -14,10 +14,13 @@ #include <fcntl.h> #include <signal.h> +#include <sys/epoll.h> #include <sys/types.h> #include <syscall.h> #include <unistd.h> +#include <atomic> +#include <deque> #include <iostream> #include <list> #include <string> @@ -34,25 +37,27 @@ #include "test/syscalls/linux/socket_test_util.h" #include "test/util/cleanup.h" #include "test/util/eventfd_util.h" +#include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" #include "test/util/save_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" #include "test/util/timer_util.h" -ABSL_FLAG(std::string, child_setlock_on, "", +ABSL_FLAG(std::string, child_set_lock_on, "", "Contains the path to try to set a file lock on."); -ABSL_FLAG(bool, child_setlock_write, false, +ABSL_FLAG(bool, child_set_lock_write, false, "Whether to set a writable lock (otherwise readable)"); ABSL_FLAG(bool, blocking, false, "Whether to set a blocking lock (otherwise non-blocking)."); ABSL_FLAG(bool, retry_eintr, false, "Whether to retry in the subprocess on EINTR."); -ABSL_FLAG(uint64_t, child_setlock_start, 0, "The value of struct flock start"); -ABSL_FLAG(uint64_t, child_setlock_len, 0, "The value of struct flock len"); +ABSL_FLAG(uint64_t, child_set_lock_start, 0, "The value of struct flock start"); +ABSL_FLAG(uint64_t, child_set_lock_len, 0, "The value of struct flock len"); ABSL_FLAG(int32_t, socket_fd, -1, "A socket to use for communicating more state back " "to the parent."); @@ -60,6 +65,11 @@ ABSL_FLAG(int32_t, socket_fd, -1, namespace gvisor { namespace testing { +std::function<void(int, siginfo_t*, void*)> setsig_signal_handle; +void setsig_signal_handler(int signum, siginfo_t* siginfo, void* ucontext) { + setsig_signal_handle(signum, siginfo, ucontext); +} + class FcntlLockTest : public ::testing::Test { public: void SetUp() override { @@ -84,18 +94,93 @@ class FcntlLockTest : public ::testing::Test { int fds_[2] = {}; }; +struct SignalDelivery { + int num; + siginfo_t info; +}; + +class FcntlSignalTest : public ::testing::Test { + public: + void SetUp() override { + int pipe_fds[2]; + ASSERT_THAT(pipe2(pipe_fds, O_NONBLOCK), SyscallSucceeds()); + pipe_read_fd_ = pipe_fds[0]; + pipe_write_fd_ = pipe_fds[1]; + } + + PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) { + struct sigaction handler; + handler.sa_sigaction = setsig_signal_handler; + setsig_signal_handle = [&](int signum, siginfo_t* siginfo, + void* unused_ucontext) { + SignalDelivery sig; + sig.num = signum; + sig.info = *siginfo; + signals_received_.push_back(sig); + num_signals_received_++; + }; + sigemptyset(&handler.sa_mask); + handler.sa_flags = SA_SIGINFO; + return ScopedSigaction(signum, handler); + } + + void FlushAndCloseFD(int fd) { + char buf; + int read_bytes; + do { + read_bytes = read(fd, &buf, 1); + } while (read_bytes > 0); + // read() can also fail with EWOULDBLOCK since the pipe is open in + // non-blocking mode. This is not an error. + EXPECT_TRUE(read_bytes == 0 || (read_bytes == -1 && errno == EWOULDBLOCK)); + EXPECT_THAT(close(fd), SyscallSucceeds()); + } + + void DupReadFD() { + ASSERT_THAT(pipe_read_fd_dup_ = dup(pipe_read_fd_), SyscallSucceeds()); + max_expected_signals++; + } + + void RegisterFD(int fd, int signum) { + ASSERT_THAT(fcntl(fd, F_SETOWN, getpid()), SyscallSucceeds()); + ASSERT_THAT(fcntl(fd, F_SETSIG, signum), SyscallSucceeds()); + int old_flags; + ASSERT_THAT(old_flags = fcntl(fd, F_GETFL), SyscallSucceeds()); + ASSERT_THAT(fcntl(fd, F_SETFL, old_flags | O_ASYNC), SyscallSucceeds()); + } + + void GenerateIOEvent() { + ASSERT_THAT(write(pipe_write_fd_, "test", 4), SyscallSucceedsWithValue(4)); + } + + void WaitForSignalDelivery(absl::Duration timeout) { + absl::Time wait_start = absl::Now(); + while (num_signals_received_ < max_expected_signals && + absl::Now() - wait_start < timeout) { + absl::SleepFor(absl::Milliseconds(10)); + } + } + + int pipe_read_fd_ = -1; + int pipe_read_fd_dup_ = -1; + int pipe_write_fd_ = -1; + int max_expected_signals = 1; + std::deque<SignalDelivery> signals_received_; + std::atomic<int> num_signals_received_ = 0; +}; + namespace { PosixErrorOr<Cleanup> SubprocessLock(std::string const& path, bool for_write, bool blocking, bool retry_eintr, int fd, off_t start, off_t length, pid_t* child) { std::vector<std::string> args = { - "/proc/self/exe", "--child_setlock_on", path, - "--child_setlock_start", absl::StrCat(start), "--child_setlock_len", - absl::StrCat(length), "--socket_fd", absl::StrCat(fd)}; + "/proc/self/exe", "--child_set_lock_on", path, + "--child_set_lock_start", absl::StrCat(start), "--child_set_lock_len", + absl::StrCat(length), "--socket_fd", absl::StrCat(fd)}; if (for_write) { - args.push_back("--child_setlock_write"); + args.push_back("--child_set_lock_write"); } if (blocking) { @@ -965,7 +1050,6 @@ TEST(FcntlTest, GetOwnNone) { // into F_{GET,SET}OWN_EX. EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(0)); - MaybeSave(); } TEST(FcntlTest, GetOwnExNone) { @@ -1009,7 +1093,6 @@ TEST(FcntlTest, SetOwnPid) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(pid)); - MaybeSave(); } TEST(FcntlTest, SetOwnPgrp) { @@ -1030,7 +1113,6 @@ TEST(FcntlTest, SetOwnPgrp) { SyscallSucceedsWithValue(0)); EXPECT_EQ(got_owner.type, F_OWNER_PGRP); EXPECT_EQ(got_owner.pid, pgid); - MaybeSave(); } TEST(FcntlTest, SetOwnUnset) { @@ -1058,7 +1140,6 @@ TEST(FcntlTest, SetOwnUnset) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(0)); - MaybeSave(); } // F_SETOWN flips the sign of negative values, an operation that is guarded @@ -1130,7 +1211,6 @@ TEST(FcntlTest, SetOwnExTid) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(owner.pid)); - MaybeSave(); } TEST(FcntlTest, SetOwnExPid) { @@ -1146,7 +1226,6 @@ TEST(FcntlTest, SetOwnExPid) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(owner.pid)); - MaybeSave(); } TEST(FcntlTest, SetOwnExPgrp) { @@ -1168,7 +1247,6 @@ TEST(FcntlTest, SetOwnExPgrp) { SyscallSucceedsWithValue(0)); EXPECT_EQ(got_owner.type, set_owner.type); EXPECT_EQ(got_owner.pid, set_owner.pid); - MaybeSave(); } TEST(FcntlTest, SetOwnExUnset) { @@ -1201,7 +1279,6 @@ TEST(FcntlTest, SetOwnExUnset) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(0)); - MaybeSave(); } TEST(FcntlTest, GetOwnExTid) { @@ -1258,9 +1335,269 @@ TEST(FcntlTest, GetOwnExPgrp) { EXPECT_EQ(got_owner.pid, set_owner.pid); } +TEST(FcntlTest, SetSig) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1), + SyscallSucceedsWithValue(0)); + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(SIGUSR1)); +} + +TEST(FcntlTest, SetSigDefaultsToZero) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + // Defaults to returning the zero value, indicating default behavior (SIGIO). + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(0)); +} + +TEST(FcntlTest, SetSigToDefault) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGIO), + SyscallSucceedsWithValue(0)); + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(SIGIO)); + + // Can be reset to the default behavior. + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, 0), + SyscallSucceedsWithValue(0)); + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(0)); +} + +TEST(FcntlTest, SetSigInvalid) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGRTMAX + 1), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(0)); +} + +TEST(FcntlTest, SetSigInvalidDoesNotResetPreviousChoice) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1), + SyscallSucceedsWithValue(0)); + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGRTMAX + 1), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(SIGUSR1)); +} + +TEST_F(FcntlSignalTest, SetSigDefault) { + const auto signal_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + RegisterFD(pipe_read_fd_, 0); // Zero = default behavior + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + signals_received_.pop_front(); + EXPECT_EQ(sig.num, SIGIO); + EXPECT_EQ(sig.info.si_signo, SIGIO); + // siginfo contents is undefined in this case. +} + +TEST_F(FcntlSignalTest, SetSigCustom) { + const auto signal_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + signals_received_.pop_front(); + EXPECT_EQ(sig.num, SIGUSR1); + EXPECT_EQ(sig.info.si_signo, SIGUSR1); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigUnregisterStillGetsSigio) { + const auto sigio_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + RegisterFD(pipe_read_fd_, 0); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + signals_received_.pop_front(); + EXPECT_EQ(sig.num, SIGIO); + // siginfo contents is undefined in this case. +} + +TEST_F(FcntlSignalTest, SetSigWithSigioStillGetsSiginfo) { + const auto signal_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + RegisterFD(pipe_read_fd_, SIGIO); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + EXPECT_EQ(sig.num, SIGIO); + EXPECT_EQ(sig.info.si_signo, SIGIO); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupThenCloseOld) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + FlushAndCloseFD(pipe_read_fd_); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the **old** FD (even though it is closed). + EXPECT_EQ(sig.num, SIGUSR1); + EXPECT_EQ(sig.info.si_signo, SIGUSR1); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupThenCloseNew) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + FlushAndCloseFD(pipe_read_fd_dup_); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the old FD. + EXPECT_EQ(sig.num, SIGUSR1); + EXPECT_EQ(sig.info.si_signo, SIGUSR1); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupOldRegistered) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the old FD. + EXPECT_EQ(sig.num, SIGUSR1); + EXPECT_EQ(sig.info.si_signo, SIGUSR1); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupNewRegistered) { + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + DupReadFD(); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the new FD. + EXPECT_EQ(sig.num, SIGUSR2); + EXPECT_EQ(sig.info.si_signo, SIGUSR2); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_dup_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupBothRegistered) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the **new** signal number, but the **old** FD. + EXPECT_EQ(sig.num, SIGUSR2); + EXPECT_EQ(sig.info.si_signo, SIGUSR2); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupBothRegisteredAfterDup) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + DupReadFD(); + RegisterFD(pipe_read_fd_, SIGUSR1); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the **new** signal number, but the **old** FD. + EXPECT_EQ(sig.num, SIGUSR2); + EXPECT_EQ(sig.info.si_signo, SIGUSR2); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupUnregisterOld) { + const auto sigio_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + RegisterFD(pipe_read_fd_, 0); // Should go back to SIGIO behavior. + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with SIGIO. + EXPECT_EQ(sig.num, SIGIO); + // siginfo is undefined in this case. +} + +TEST_F(FcntlSignalTest, SetSigDupUnregisterNew) { + const auto sigio_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + RegisterFD(pipe_read_fd_dup_, 0); // Should go back to SIGIO behavior. + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with SIGIO. + EXPECT_EQ(sig.num, SIGIO); + // siginfo is undefined in this case. +} + // Make sure that making multiple concurrent changes to async signal generation // does not cause any race issues. -TEST(FcntlTest, SetFlSetOwnDoNotRace) { +TEST(FcntlTest, SetFlSetOwnSetSigDoNotRace) { FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); @@ -1268,32 +1605,40 @@ TEST(FcntlTest, SetFlSetOwnDoNotRace) { EXPECT_THAT(pid = getpid(), SyscallSucceeds()); constexpr absl::Duration runtime = absl::Milliseconds(300); - auto setAsync = [&s, &runtime] { + auto set_async = [&s, &runtime] { for (auto start = absl::Now(); absl::Now() - start < runtime;) { ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETFL, O_ASYNC), SyscallSucceeds()); sched_yield(); } }; - auto resetAsync = [&s, &runtime] { + auto reset_async = [&s, &runtime] { for (auto start = absl::Now(); absl::Now() - start < runtime;) { ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETFL, 0), SyscallSucceeds()); sched_yield(); } }; - auto setOwn = [&s, &pid, &runtime] { + auto set_own = [&s, &pid, &runtime] { for (auto start = absl::Now(); absl::Now() - start < runtime;) { ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, pid), SyscallSucceeds()); sched_yield(); } }; + auto set_sig = [&s, &runtime] { + for (auto start = absl::Now(); absl::Now() - start < runtime;) { + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1), + SyscallSucceeds()); + sched_yield(); + } + }; std::list<ScopedThread> threads; for (int i = 0; i < 10; i++) { - threads.emplace_back(setAsync); - threads.emplace_back(resetAsync); - threads.emplace_back(setOwn); + threads.emplace_back(set_async); + threads.emplace_back(reset_async); + threads.emplace_back(set_own); + threads.emplace_back(set_sig); } } @@ -1302,57 +1647,60 @@ TEST(FcntlTest, SetFlSetOwnDoNotRace) { } // namespace testing } // namespace gvisor -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - - const std::string setlock_on = absl::GetFlag(FLAGS_child_setlock_on); - if (!setlock_on.empty()) { - int socket_fd = absl::GetFlag(FLAGS_socket_fd); - int fd = open(setlock_on.c_str(), O_RDWR, 0666); - if (fd == -1 && errno != 0) { - int err = errno; - std::cerr << "CHILD open " << setlock_on << " failed " << err - << std::endl; - exit(err); - } +int set_lock() { + const std::string set_lock_on = absl::GetFlag(FLAGS_child_set_lock_on); + int socket_fd = absl::GetFlag(FLAGS_socket_fd); + int fd = open(set_lock_on.c_str(), O_RDWR, 0666); + if (fd == -1 && errno != 0) { + int err = errno; + std::cerr << "CHILD open " << set_lock_on << " failed: " << err + << std::endl; + return err; + } - struct flock fl; - if (absl::GetFlag(FLAGS_child_setlock_write)) { - fl.l_type = F_WRLCK; - } else { - fl.l_type = F_RDLCK; - } - fl.l_whence = SEEK_SET; - fl.l_start = absl::GetFlag(FLAGS_child_setlock_start); - fl.l_len = absl::GetFlag(FLAGS_child_setlock_len); + struct flock fl; + if (absl::GetFlag(FLAGS_child_set_lock_write)) { + fl.l_type = F_WRLCK; + } else { + fl.l_type = F_RDLCK; + } + fl.l_whence = SEEK_SET; + fl.l_start = absl::GetFlag(FLAGS_child_set_lock_start); + fl.l_len = absl::GetFlag(FLAGS_child_set_lock_len); + + // Test the fcntl. + int err = 0; + int ret = 0; + + gvisor::testing::MonotonicTimer timer; + timer.Start(); + do { + ret = fcntl(fd, absl::GetFlag(FLAGS_blocking) ? F_SETLKW : F_SETLK, &fl); + } while (absl::GetFlag(FLAGS_retry_eintr) && ret == -1 && errno == EINTR); + auto usec = absl::ToInt64Microseconds(timer.Duration()); + + if (ret == -1 && errno != 0) { + err = errno; + std::cerr << "CHILD lock " << set_lock_on << " failed " << err << std::endl; + } - // Test the fcntl. - int err = 0; - int ret = 0; + // If there is a socket fd let's send back the time in microseconds it took + // to execute this syscall. + if (socket_fd != -1) { + gvisor::testing::WriteFd(socket_fd, reinterpret_cast<void*>(&usec), + sizeof(usec)); + close(socket_fd); + } - gvisor::testing::MonotonicTimer timer; - timer.Start(); - do { - ret = fcntl(fd, absl::GetFlag(FLAGS_blocking) ? F_SETLKW : F_SETLK, &fl); - } while (absl::GetFlag(FLAGS_retry_eintr) && ret == -1 && errno == EINTR); - auto usec = absl::ToInt64Microseconds(timer.Duration()); - - if (ret == -1 && errno != 0) { - err = errno; - std::cerr << "CHILD lock " << setlock_on << " failed " << err - << std::endl; - } + close(fd); + return err; +} - // If there is a socket fd let's send back the time in microseconds it took - // to execute this syscall. - if (socket_fd != -1) { - gvisor::testing::WriteFd(socket_fd, reinterpret_cast<void*>(&usec), - sizeof(usec)); - close(socket_fd); - } +int main(int argc, char** argv) { + gvisor::testing::TestInit(&argc, &argv); - close(fd); - exit(err); + if (!absl::GetFlag(FLAGS_child_set_lock_on).empty()) { + exit(set_lock()); } return gvisor::testing::RunAllTests(); diff --git a/test/syscalls/linux/getdents.cc b/test/syscalls/linux/getdents.cc index b040cdcf7..93c692dd6 100644 --- a/test/syscalls/linux/getdents.cc +++ b/test/syscalls/linux/getdents.cc @@ -32,6 +32,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/container/node_hash_map.h" #include "absl/container/node_hash_set.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" @@ -381,7 +382,7 @@ TYPED_TEST(GetdentsTest, PartialBuffer) { // getdents iterates correctly despite mutation of /proc/self/fd. TYPED_TEST(GetdentsTest, ProcSelfFd) { constexpr size_t kNfds = 10; - std::unordered_map<int, FileDescriptor> fds; + absl::node_hash_map<int, FileDescriptor> fds; fds.reserve(kNfds); for (size_t i = 0; i < kNfds; i++) { FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc index e4392a450..8137f0e29 100644 --- a/test/syscalls/linux/inotify.cc +++ b/test/syscalls/linux/inotify.cc @@ -1703,6 +1703,45 @@ TEST(Inotify, Fallocate) { EXPECT_THAT(events, Are({Event(IN_MODIFY, wd)})); } +TEST(Inotify, Utimensat) { + SKIP_IF(IsRunningWithVFS1()); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); + + const FileDescriptor inotify_fd = + ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); + const int wd = ASSERT_NO_ERRNO_AND_VALUE( + InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS)); + + // Just update the access time. + struct timespec times[2] = {}; + times[0].tv_nsec = UTIME_NOW; + times[1].tv_nsec = UTIME_OMIT; + ASSERT_THAT(RetryEINTR(utimensat)(AT_FDCWD, file.path().c_str(), times, 0), + SyscallSucceeds()); + std::vector<Event> events = + ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); + EXPECT_THAT(events, Are({Event(IN_ACCESS, wd)})); + + // Just the modify time. + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_nsec = UTIME_NOW; + ASSERT_THAT(utimensat(AT_FDCWD, file.path().c_str(), times, 0), + SyscallSucceeds()); + events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); + EXPECT_THAT(events, Are({Event(IN_MODIFY, wd)})); + + // Both together. + times[0].tv_nsec = UTIME_NOW; + times[1].tv_nsec = UTIME_NOW; + ASSERT_THAT(utimensat(AT_FDCWD, file.path().c_str(), times, 0), + SyscallSucceeds()); + events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); + EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)})); +} + TEST(Inotify, Sendfile) { SKIP_IF(IsRunningWithVFS1()); diff --git a/test/syscalls/linux/kill.cc b/test/syscalls/linux/kill.cc index db29bd59c..5d1735853 100644 --- a/test/syscalls/linux/kill.cc +++ b/test/syscalls/linux/kill.cc @@ -58,6 +58,12 @@ void SigHandler(int sig, siginfo_t* info, void* context) { _exit(0); } // If pid equals -1, then sig is sent to every process for which the calling // process has permission to send signals, except for process 1 (init). TEST(KillTest, CanKillAllPIDs) { + // If we're not running inside the sandbox, then we skip this test + // as our namespace may contain may more processes that cannot tolerate + // the signal below. We also cannot reliably create a new pid namespace + // for ourselves and test the same functionality. + SKIP_IF(!IsRunningOnGvisor()); + int pipe_fds[2]; ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); FileDescriptor read_fd(pipe_fds[0]); diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc index d65b7d031..15b645fb7 100644 --- a/test/syscalls/linux/mount.cc +++ b/test/syscalls/linux/mount.cc @@ -345,42 +345,6 @@ TEST(MountTest, RenameRemoveMountPoint) { ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY)); } -TEST(MountTest, MountFuseFilesystemNoDevice) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - SKIP_IF(IsRunningOnGvisor() && !IsFUSEEnabled()); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Before kernel version 4.16-rc6, FUSE mount is protected by - // capable(CAP_SYS_ADMIN). After this version, it uses - // ns_capable(CAP_SYS_ADMIN) to protect. Before the 4.16 kernel, it was not - // allowed to mount fuse file systems without the global CAP_SYS_ADMIN. - int res = mount("", dir.path().c_str(), "fuse", 0, ""); - SKIP_IF(!IsRunningOnGvisor() && res == -1 && errno == EPERM); - - EXPECT_THAT(mount("", dir.path().c_str(), "fuse", 0, ""), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(MountTest, MountFuseFilesystem) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - SKIP_IF(IsRunningOnGvisor() && !IsFUSEEnabled()); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_WRONLY)); - std::string mopts = "fd=" + std::to_string(fd.get()); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // See comments in MountFuseFilesystemNoDevice for the reason why we skip - // EPERM when running on Linux. - int res = mount("", dir.path().c_str(), "fuse", 0, ""); - SKIP_IF(!IsRunningOnGvisor() && res == -1 && errno == EPERM); - - auto const mount = - ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "fuse", 0, mopts, 0)); -} - } // namespace } // namespace testing diff --git a/test/syscalls/linux/open.cc b/test/syscalls/linux/open.cc index 77f390f3c..fcd162ca2 100644 --- a/test/syscalls/linux/open.cc +++ b/test/syscalls/linux/open.cc @@ -505,6 +505,18 @@ TEST_F(OpenTest, OpenNonDirectoryWithTrailingSlash) { EXPECT_THAT(open(bad_path.c_str(), O_RDONLY), SyscallFailsWithErrno(ENOTDIR)); } +TEST_F(OpenTest, OpenWithStrangeFlags) { + // VFS1 incorrectly allows read/write operations on such file descriptors. + SKIP_IF(IsRunningWithVFS1()); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY | O_RDWR)); + EXPECT_THAT(write(fd.get(), "x", 1), SyscallFailsWithErrno(EBADF)); + char c; + EXPECT_THAT(read(fd.get(), &c, 1), SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/open_create.cc b/test/syscalls/linux/open_create.cc index 78c36f98f..9d63782fb 100644 --- a/test/syscalls/linux/open_create.cc +++ b/test/syscalls/linux/open_create.cc @@ -112,14 +112,6 @@ TEST(CreateTest, CreatFileWithOTruncAndReadOnly) { ASSERT_THAT(close(dirfd), SyscallSucceeds()); } -TEST(CreateTest, CreateFailsOnUnpermittedDir) { - // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to - // always override directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_THAT(open("/foo", O_CREAT | O_RDWR, 0644), - SyscallFailsWithErrno(EACCES)); -} - TEST(CreateTest, CreateFailsOnDirWithoutWritePerms) { // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to // always override directory permissions. diff --git a/test/syscalls/linux/partial_bad_buffer.cc b/test/syscalls/linux/partial_bad_buffer.cc index df7129acc..13afa0eaf 100644 --- a/test/syscalls/linux/partial_bad_buffer.cc +++ b/test/syscalls/linux/partial_bad_buffer.cc @@ -320,7 +320,10 @@ PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) { // EFAULT. It also verifies that passing a buffer which is made up of 2 // pages one valid and one guard page succeeds as long as the write is // for exactly the size of 1 page. -TEST_F(PartialBadBufferTest, SendMsgTCP) { +TEST_F(PartialBadBufferTest, SendMsgTCP_NoRandomSave) { + // FIXME(b/171436815): Netstack save/restore is broken. + const DisableSave ds; + auto listen_socket = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc index 06d9dbf65..01ccbdcd2 100644 --- a/test/syscalls/linux/pipe.cc +++ b/test/syscalls/linux/pipe.cc @@ -71,13 +71,13 @@ class PipeTest : public ::testing::TestWithParam<PipeCreator> { // Returns true iff the pipe represents a named pipe. bool IsNamedPipe() const { return named_pipe_; } - int Size() const { + size_t Size() const { int s1 = fcntl(rfd_.get(), F_GETPIPE_SZ); int s2 = fcntl(wfd_.get(), F_GETPIPE_SZ); EXPECT_GT(s1, 0); EXPECT_GT(s2, 0); EXPECT_EQ(s1, s2); - return s1; + return static_cast<size_t>(s1); } static void TearDownTestSuite() { @@ -568,7 +568,7 @@ TEST_P(PipeTest, Streaming) { DisableSave ds; // Size() requires 2 syscalls, call it once and remember the value. - const int pipe_size = Size(); + const size_t pipe_size = Size(); const size_t streamed_bytes = 4 * pipe_size; absl::Notification notify; @@ -576,7 +576,7 @@ TEST_P(PipeTest, Streaming) { std::vector<char> buf(1024); // Don't start until it's full. notify.WaitForNotification(); - ssize_t total = 0; + size_t total = 0; while (total < streamed_bytes) { ASSERT_THAT(read(rfd_.get(), buf.data(), buf.size()), SyscallSucceedsWithValue(buf.size())); @@ -593,7 +593,7 @@ TEST_P(PipeTest, Streaming) { // page) for the check for notify.Notify() below to be correct. std::vector<char> buf(1024); RandomizeBuffer(buf.data(), buf.size()); - ssize_t total = 0; + size_t total = 0; while (total < streamed_bytes) { ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()), SyscallSucceedsWithValue(buf.size())); diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index 7a0f33dff..e508ce27f 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -17,6 +17,7 @@ #include <fcntl.h> #include <limits.h> #include <linux/magic.h> +#include <linux/sem.h> #include <sched.h> #include <signal.h> #include <stddef.h> @@ -1801,6 +1802,33 @@ TEST(ProcPidCmdline, SubprocessForkSameCmdline) { } } +TEST(ProcPidCmdline, SubprocessSeekCmdline) { + FileDescriptor fd; + ASSERT_NO_ERRNO(WithSubprocess( + [&](int pid) -> PosixError { + // Running. Open /proc/pid/cmdline. + ASSIGN_OR_RETURN_ERRNO( + fd, Open(absl::StrCat("/proc/", pid, "/cmdline"), O_RDONLY)); + return NoError(); + }, + [&](int pid) -> PosixError { + // Zombie, but seek should still succeed. + int ret = lseek(fd.get(), 0x801, 0); + if (ret < 0) { + return PosixError(errno); + } + return NoError(); + }, + [&](int pid) -> PosixError { + // Exited. + int ret = lseek(fd.get(), 0x801, 0); + if (ret < 0) { + return PosixError(errno); + } + return NoError(); + })); +} + // Test whether /proc/PID/ symlinks can be read for a running process. TEST(ProcPidSymlink, SubprocessRunning) { char buf[1]; @@ -2409,6 +2437,28 @@ TEST(ProcFilesystems, PresenceOfShmMaxMniAll) { ASSERT_LE(shmall, ULONG_MAX - (1UL << 24)); } +TEST(ProcFilesystems, PresenceOfSem) { + uint32_t semmsl = 0; + uint32_t semmns = 0; + uint32_t semopm = 0; + uint32_t semmni = 0; + std::string proc_file; + proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/sem")); + ASSERT_FALSE(proc_file.empty()); + std::vector<absl::string_view> sem_limits = + absl::StrSplit(proc_file, absl::ByAnyChar("\t"), absl::SkipWhitespace()); + ASSERT_EQ(sem_limits.size(), 4); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[0], &semmsl)); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[1], &semmns)); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[2], &semopm)); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[3], &semmni)); + + ASSERT_EQ(semmsl, SEMMSL); + ASSERT_EQ(semmns, SEMMNS); + ASSERT_EQ(semopm, SEMOPM); + ASSERT_EQ(semmni, SEMMNI); +} + // Check that /proc/mounts is a symlink to self/mounts. TEST(ProcMounts, IsSymlink) { auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/mounts")); @@ -2459,7 +2509,7 @@ void CheckDuplicatesRecursively(std::string path) { return; } auto dir_closer = Cleanup([&dir]() { closedir(dir); }); - std::unordered_set<std::string> children; + absl::node_hash_set<std::string> children; while (true) { // Readdir(3): If the end of the directory stream is reached, NULL is // returned and errno is not changed. If an error occurs, NULL is @@ -2478,6 +2528,10 @@ void CheckDuplicatesRecursively(std::string path) { absl::EndsWith(path, "/net")) { break; } + // We may also see permission failures traversing some files. + if (errno == EACCES && absl::StartsWith(path, "/proc/")) { + break; + } // Otherwise, no errors are allowed. ASSERT_EQ(errno, 0) << path; diff --git a/test/syscalls/linux/proc_net.cc b/test/syscalls/linux/proc_net.cc index 23677e296..73140b2e9 100644 --- a/test/syscalls/linux/proc_net.cc +++ b/test/syscalls/linux/proc_net.cc @@ -420,14 +420,14 @@ TEST(ProcNetSnmp, CheckNetStat) { int name_count = 0; int value_count = 0; std::vector<absl::string_view> lines = absl::StrSplit(contents, '\n'); - for (int i = 0; i + 1 < lines.size(); i += 2) { + for (long unsigned int i = 0; i + 1 < lines.size(); i += 2) { std::vector<absl::string_view> names = absl::StrSplit(lines[i], absl::ByAnyChar("\t ")); std::vector<absl::string_view> values = absl::StrSplit(lines[i + 1], absl::ByAnyChar("\t ")); EXPECT_EQ(names.size(), values.size()) << " mismatch in lines '" << lines[i] << "' and '" << lines[i + 1] << "'"; - for (int j = 0; j < names.size() && j < values.size(); ++j) { + for (long unsigned int j = 0; j < names.size() && j < values.size(); ++j) { if (names[j] == "TCPOrigDataSent" || names[j] == "TCPSynRetrans" || names[j] == "TCPDSACKRecv" || names[j] == "TCPDSACKOfoRecv") { ++name_count; @@ -457,14 +457,14 @@ TEST(ProcNetSnmp, CheckSnmp) { int name_count = 0; int value_count = 0; std::vector<absl::string_view> lines = absl::StrSplit(contents, '\n'); - for (int i = 0; i + 1 < lines.size(); i += 2) { + for (long unsigned int i = 0; i + 1 < lines.size(); i += 2) { std::vector<absl::string_view> names = absl::StrSplit(lines[i], absl::ByAnyChar("\t ")); std::vector<absl::string_view> values = absl::StrSplit(lines[i + 1], absl::ByAnyChar("\t ")); EXPECT_EQ(names.size(), values.size()) << " mismatch in lines '" << lines[i] << "' and '" << lines[i + 1] << "'"; - for (int j = 0; j < names.size() && j < values.size(); ++j) { + for (long unsigned int j = 0; j < names.size() && j < values.size(); ++j) { if (names[j] == "RetransSegs") { ++name_count; int64_t val; @@ -499,7 +499,13 @@ TEST(ProcSysNetIpv4Recovery, CanReadAndWrite) { // Check initial value is set to 1. EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), SyscallSucceedsWithValue(sizeof(to_write) + 1)); - EXPECT_EQ(strcmp(buf, "1\n"), 0); + if (IsRunningOnGvisor()) { + // TODO(gvisor.dev/issue/5243): TCPRACKLossDetection = 1 should be turned on + // by default. + EXPECT_EQ(strcmp(buf, "0\n"), 0); + } else { + EXPECT_EQ(strcmp(buf, "1\n"), 0); + } // Set tcp_recovery to one of the allowed constants. EXPECT_THAT(PwriteFd(fd.get(), &to_write, sizeof(to_write), 0), diff --git a/test/syscalls/linux/proc_net_unix.cc b/test/syscalls/linux/proc_net_unix.cc index a63067586..662c6feb2 100644 --- a/test/syscalls/linux/proc_net_unix.cc +++ b/test/syscalls/linux/proc_net_unix.cc @@ -181,7 +181,7 @@ PosixErrorOr<std::vector<UnixEntry>> ProcNetUnixEntries() { // Returns true on match, and sets 'match' to point to the matching entry. bool FindBy(std::vector<UnixEntry> entries, UnixEntry* match, std::function<bool(const UnixEntry&)> predicate) { - for (int i = 0; i < entries.size(); ++i) { + for (long unsigned int i = 0; i < entries.size(); ++i) { if (predicate(entries[i])) { *match = entries[i]; return true; diff --git a/test/syscalls/linux/proc_pid_uid_gid_map.cc b/test/syscalls/linux/proc_pid_uid_gid_map.cc index 748f7be58..af052a63c 100644 --- a/test/syscalls/linux/proc_pid_uid_gid_map.cc +++ b/test/syscalls/linux/proc_pid_uid_gid_map.cc @@ -203,7 +203,8 @@ TEST_P(ProcSelfUidGidMapTest, IdentityMapOwnID) { EXPECT_THAT( InNewUserNamespaceWithMapFD([&](int fd) { DenySelfSetgroups(); - TEST_PCHECK(write(fd, line.c_str(), line.size()) == line.size()); + TEST_PCHECK(static_cast<long unsigned int>( + write(fd, line.c_str(), line.size())) == line.size()); }), IsPosixErrorOkAndHolds(0)); } @@ -220,7 +221,8 @@ TEST_P(ProcSelfUidGidMapTest, TrailingNewlineAndNULIgnored) { DenySelfSetgroups(); // The write should return the full size of the write, even though // characters after the NUL were ignored. - TEST_PCHECK(write(fd, line.c_str(), line.size()) == line.size()); + TEST_PCHECK(static_cast<long unsigned int>( + write(fd, line.c_str(), line.size())) == line.size()); }), IsPosixErrorOkAndHolds(0)); } @@ -233,7 +235,8 @@ TEST_P(ProcSelfUidGidMapTest, NonIdentityMapOwnID) { EXPECT_THAT( InNewUserNamespaceWithMapFD([&](int fd) { DenySelfSetgroups(); - TEST_PCHECK(write(fd, line.c_str(), line.size()) == line.size()); + TEST_PCHECK(static_cast<long unsigned int>( + write(fd, line.c_str(), line.size())) == line.size()); }), IsPosixErrorOkAndHolds(0)); } diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc index 54709371c..955bcee4b 100644 --- a/test/syscalls/linux/raw_socket.cc +++ b/test/syscalls/linux/raw_socket.cc @@ -852,6 +852,51 @@ TEST(RawSocketTest, IPv6ProtoRaw) { SyscallFailsWithErrno(EINVAL)); } +TEST(RawSocketTest, IPv6SendMsg) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int sock; + ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), + SyscallSucceeds()); + + char kBuf[] = "hello"; + struct iovec iov = {}; + iov.iov_base = static_cast<void*>(const_cast<char*>(kBuf)); + iov.iov_len = static_cast<size_t>(sizeof(kBuf)); + + struct sockaddr_storage addr = {}; + struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct msghdr msg = {}; + msg.msg_name = static_cast<void*>(&addr); + msg.msg_namelen = sizeof(sockaddr_in); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ASSERT_THAT(sendmsg(sock, &msg, 0), SyscallFailsWithErrno(EINVAL)); +} + +TEST_P(RawSocketTest, ConnectOnIPv6Socket) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int sock; + ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), + SyscallSucceeds()); + + struct sockaddr_storage addr = {}; + struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + ASSERT_THAT(connect(sock, reinterpret_cast<struct sockaddr*>(&addr), + sizeof(sockaddr_in6)), + SyscallFailsWithErrno(EAFNOSUPPORT)); +} + INSTANTIATE_TEST_SUITE_P( AllInetTests, RawSocketTest, ::testing::Combine(::testing::Values(IPPROTO_TCP, IPPROTO_UDP), diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc index 890f4a246..0530fce44 100644 --- a/test/syscalls/linux/semaphore.cc +++ b/test/syscalls/linux/semaphore.cc @@ -20,6 +20,7 @@ #include <atomic> #include <cerrno> #include <ctime> +#include <set> #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -35,6 +36,17 @@ namespace gvisor { namespace testing { namespace { +constexpr int kSemMap = 1024000000; +constexpr int kSemMni = 32000; +constexpr int kSemMns = 1024000000; +constexpr int kSemMnu = 1024000000; +constexpr int kSemMsl = 32000; +constexpr int kSemOpm = 500; +constexpr int kSemUme = 500; +constexpr int kSemUsz = 20; +constexpr int kSemVmx = 32767; +constexpr int kSemAem = 32767; + class AutoSem { public: explicit AutoSem(int id) : id_(id) {} @@ -586,7 +598,7 @@ TEST(SemaphoreTest, SemopGetzcnt) { buf.sem_num = 0; buf.sem_op = 0; constexpr size_t kLoops = 10; - for (auto i = 0; i < kLoops; i++) { + for (size_t i = 0; i < kLoops; i++) { auto child_pid = fork(); if (child_pid == 0) { TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0); @@ -693,7 +705,7 @@ TEST(SemaphoreTest, SemopGetncnt) { buf.sem_num = 0; buf.sem_op = -1; constexpr size_t kLoops = 10; - for (auto i = 0; i < kLoops; i++) { + for (size_t i = 0; i < kLoops; i++) { auto child_pid = fork(); if (child_pid == 0) { TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0); @@ -773,6 +785,151 @@ TEST(SemaphoreTest, SemopGetncntOnSignal_NoRandomSave) { EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0); } +TEST(SemaphoreTest, IpcInfo) { + constexpr int kLoops = 5; + std::set<int> sem_ids; + struct seminfo info; + // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); + for (int i = 0; i < kLoops; i++) { + AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + sem_ids.insert(sem.release()); + } + ASSERT_EQ(sem_ids.size(), kLoops); + + int max_used_index = 0; + EXPECT_THAT(max_used_index = semctl(0, 0, IPC_INFO, &info), + SyscallSucceeds()); + + std::set<int> sem_ids_before_max_index; + for (int i = 0; i <= max_used_index; i++) { + struct semid_ds ds = {}; + int sem_id = semctl(i, 0, SEM_STAT, &ds); + // Only if index i is used within the registry. + if (sem_ids.find(sem_id) != sem_ids.end()) { + struct semid_ds ipc_stat_ds; + ASSERT_THAT(semctl(sem_id, 0, IPC_STAT, &ipc_stat_ds), SyscallSucceeds()); + EXPECT_EQ(ds.sem_perm.__key, ipc_stat_ds.sem_perm.__key); + EXPECT_EQ(ds.sem_perm.uid, ipc_stat_ds.sem_perm.uid); + EXPECT_EQ(ds.sem_perm.gid, ipc_stat_ds.sem_perm.gid); + EXPECT_EQ(ds.sem_perm.cuid, ipc_stat_ds.sem_perm.cuid); + EXPECT_EQ(ds.sem_perm.cgid, ipc_stat_ds.sem_perm.cgid); + EXPECT_EQ(ds.sem_perm.mode, ipc_stat_ds.sem_perm.mode); + EXPECT_EQ(ds.sem_otime, ipc_stat_ds.sem_otime); + EXPECT_EQ(ds.sem_ctime, ipc_stat_ds.sem_ctime); + EXPECT_EQ(ds.sem_nsems, ipc_stat_ds.sem_nsems); + + // Remove the semaphore set's read permission. + struct semid_ds ipc_set_ds; + ipc_set_ds.sem_perm.uid = getuid(); + ipc_set_ds.sem_perm.gid = getgid(); + // Keep the semaphore set's write permission so that it could be removed. + ipc_set_ds.sem_perm.mode = 0200; + ASSERT_THAT(semctl(sem_id, 0, IPC_SET, &ipc_set_ds), SyscallSucceeds()); + ASSERT_THAT(semctl(i, 0, SEM_STAT, &ds), SyscallFailsWithErrno(EACCES)); + + sem_ids_before_max_index.insert(sem_id); + } + } + EXPECT_EQ(sem_ids_before_max_index.size(), kLoops); + for (const int sem_id : sem_ids) { + ASSERT_THAT(semctl(sem_id, 0, IPC_RMID), SyscallSucceeds()); + } + + ASSERT_THAT(semctl(0, 0, IPC_INFO, &info), SyscallSucceeds()); + EXPECT_EQ(info.semmap, kSemMap); + EXPECT_EQ(info.semmni, kSemMni); + EXPECT_EQ(info.semmns, kSemMns); + EXPECT_EQ(info.semmnu, kSemMnu); + EXPECT_EQ(info.semmsl, kSemMsl); + EXPECT_EQ(info.semopm, kSemOpm); + EXPECT_EQ(info.semume, kSemUme); + EXPECT_EQ(info.semusz, kSemUsz); + EXPECT_EQ(info.semvmx, kSemVmx); + EXPECT_EQ(info.semaem, kSemAem); +} + +TEST(SemaphoreTest, SemInfo) { + constexpr int kLoops = 5; + constexpr int kSemSetSize = 3; + std::set<int> sem_ids; + struct seminfo info; + // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); + for (int i = 0; i < kLoops; i++) { + AutoSem sem(semget(IPC_PRIVATE, kSemSetSize, 0600 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + sem_ids.insert(sem.release()); + } + ASSERT_EQ(sem_ids.size(), kLoops); + int max_used_index = 0; + EXPECT_THAT(max_used_index = semctl(0, 0, SEM_INFO, &info), + SyscallSucceeds()); + EXPECT_EQ(info.semmap, kSemMap); + EXPECT_EQ(info.semmni, kSemMni); + EXPECT_EQ(info.semmns, kSemMns); + EXPECT_EQ(info.semmnu, kSemMnu); + EXPECT_EQ(info.semmsl, kSemMsl); + EXPECT_EQ(info.semopm, kSemOpm); + EXPECT_EQ(info.semume, kSemUme); + // There could be semaphores existing in the system during the test, which + // prevents the test from getting a exact number, but the test could expect at + // least the number of sempahroes it creates in the begining of the test. + EXPECT_GE(info.semusz, sem_ids.size()); + EXPECT_EQ(info.semvmx, kSemVmx); + EXPECT_GE(info.semaem, sem_ids.size() * kSemSetSize); + + std::set<int> sem_ids_before_max_index; + for (int i = 0; i <= max_used_index; i++) { + struct semid_ds ds = {}; + int sem_id = semctl(i, 0, SEM_STAT, &ds); + // Only if index i is used within the registry. + if (sem_ids.find(sem_id) != sem_ids.end()) { + struct semid_ds ipc_stat_ds; + ASSERT_THAT(semctl(sem_id, 0, IPC_STAT, &ipc_stat_ds), SyscallSucceeds()); + EXPECT_EQ(ds.sem_perm.__key, ipc_stat_ds.sem_perm.__key); + EXPECT_EQ(ds.sem_perm.uid, ipc_stat_ds.sem_perm.uid); + EXPECT_EQ(ds.sem_perm.gid, ipc_stat_ds.sem_perm.gid); + EXPECT_EQ(ds.sem_perm.cuid, ipc_stat_ds.sem_perm.cuid); + EXPECT_EQ(ds.sem_perm.cgid, ipc_stat_ds.sem_perm.cgid); + EXPECT_EQ(ds.sem_perm.mode, ipc_stat_ds.sem_perm.mode); + EXPECT_EQ(ds.sem_otime, ipc_stat_ds.sem_otime); + EXPECT_EQ(ds.sem_ctime, ipc_stat_ds.sem_ctime); + EXPECT_EQ(ds.sem_nsems, ipc_stat_ds.sem_nsems); + + // Remove the semaphore set's read permission. + struct semid_ds ipc_set_ds; + ipc_set_ds.sem_perm.uid = getuid(); + ipc_set_ds.sem_perm.gid = getgid(); + // Keep the semaphore set's write permission so that it could be removed. + ipc_set_ds.sem_perm.mode = 0200; + ASSERT_THAT(semctl(sem_id, 0, IPC_SET, &ipc_set_ds), SyscallSucceeds()); + ASSERT_THAT(semctl(i, 0, SEM_STAT, &ds), SyscallFailsWithErrno(EACCES)); + + sem_ids_before_max_index.insert(sem_id); + } + } + EXPECT_EQ(sem_ids_before_max_index.size(), kLoops); + for (const int sem_id : sem_ids) { + ASSERT_THAT(semctl(sem_id, 0, IPC_RMID), SyscallSucceeds()); + } + + ASSERT_THAT(semctl(0, 0, SEM_INFO, &info), SyscallSucceeds()); + EXPECT_EQ(info.semmap, kSemMap); + EXPECT_EQ(info.semmni, kSemMni); + EXPECT_EQ(info.semmns, kSemMns); + EXPECT_EQ(info.semmnu, kSemMnu); + EXPECT_EQ(info.semmsl, kSemMsl); + EXPECT_EQ(info.semopm, kSemOpm); + EXPECT_EQ(info.semume, kSemUme); + // Apart from semapahores that are not created by the test, we can't determine + // the exact number of semaphore sets and semaphores, as a result, semusz and + // semaem range from 0 to a random number. Since the numbers are always + // non-negative, the test will not check the reslts of semusz and semaem. + EXPECT_EQ(info.semvmx, kSemVmx); +} + } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/sendfile.cc b/test/syscalls/linux/sendfile.cc index cf0977118..3924e0001 100644 --- a/test/syscalls/linux/sendfile.cc +++ b/test/syscalls/linux/sendfile.cc @@ -631,6 +631,24 @@ TEST(SendFileTest, SendFileToPipe) { SyscallSucceedsWithValue(kDataSize)); } +TEST(SendFileTest, SendFileToSelf_NoRandomSave) { + int rawfd; + ASSERT_THAT(rawfd = memfd_create("memfd", 0), SyscallSucceeds()); + const FileDescriptor fd(rawfd); + + char c = 0x01; + ASSERT_THAT(WriteFd(fd.get(), &c, 1), SyscallSucceedsWithValue(1)); + + // Arbitrarily chosen to make sendfile() take long enough that the sentry + // watchdog usually fires unless it's reset by sendfile() between iterations + // of the buffered copy. See b/172076632. + constexpr size_t kSendfileSize = 0xa00000; + + off_t offset = 0; + ASSERT_THAT(sendfile(fd.get(), fd.get(), &offset, kSendfileSize), + SyscallSucceedsWithValue(kSendfileSize)); +} + static volatile int signaled = 0; void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; } diff --git a/test/syscalls/linux/signalfd.cc b/test/syscalls/linux/signalfd.cc index 389e5fca2..c86cd2755 100644 --- a/test/syscalls/linux/signalfd.cc +++ b/test/syscalls/linux/signalfd.cc @@ -126,7 +126,7 @@ TEST_P(SignalfdTest, Blocking) { // Shared tid variable. absl::Mutex mu; - bool has_tid; + bool has_tid = false; pid_t tid; // Start a thread reading. diff --git a/test/syscalls/linux/socket.cc b/test/syscalls/linux/socket.cc index e680d3dd7..32f583581 100644 --- a/test/syscalls/linux/socket.cc +++ b/test/syscalls/linux/socket.cc @@ -46,7 +46,7 @@ TEST(SocketTest, ProtocolUnix) { {AF_UNIX, SOCK_SEQPACKET, PF_UNIX}, {AF_UNIX, SOCK_DGRAM, PF_UNIX}, }; - for (int i = 0; i < ABSL_ARRAYSIZE(tests); i++) { + for (long unsigned int i = 0; i < ABSL_ARRAYSIZE(tests); i++) { ASSERT_NO_ERRNO_AND_VALUE( Socket(tests[i].domain, tests[i].type, tests[i].protocol)); } @@ -59,7 +59,7 @@ TEST(SocketTest, ProtocolInet) { {AF_INET, SOCK_DGRAM, IPPROTO_UDP}, {AF_INET, SOCK_STREAM, IPPROTO_TCP}, }; - for (int i = 0; i < ABSL_ARRAYSIZE(tests); i++) { + for (long unsigned int i = 0; i < ABSL_ARRAYSIZE(tests); i++) { ASSERT_NO_ERRNO_AND_VALUE( Socket(tests[i].domain, tests[i].type, tests[i].protocol)); } @@ -87,7 +87,7 @@ TEST(SocketTest, UnixSocketStat) { ASSERT_THAT(stat(addr.sun_path, &statbuf), SyscallSucceeds()); // Mode should be S_IFSOCK. - EXPECT_EQ(statbuf.st_mode, S_IFSOCK | sock_perm & ~mask); + EXPECT_EQ(statbuf.st_mode, S_IFSOCK | (sock_perm & ~mask)); // Timestamps should be equal and non-zero. // TODO(b/158882152): Sockets currently don't implement timestamps. diff --git a/test/syscalls/linux/socket_bind_to_device_distribution.cc b/test/syscalls/linux/socket_bind_to_device_distribution.cc index 5ed57625c..f8a0a80f2 100644 --- a/test/syscalls/linux/socket_bind_to_device_distribution.cc +++ b/test/syscalls/linux/socket_bind_to_device_distribution.cc @@ -168,7 +168,7 @@ TEST_P(BindToDeviceDistributionTest, Tcp) { std::vector<std::unique_ptr<ScopedThread>> listen_threads( listener_fds.size()); - for (int i = 0; i < listener_fds.size(); i++) { + for (long unsigned int i = 0; i < listener_fds.size(); i++) { listen_threads[i] = absl::make_unique<ScopedThread>( [&listener_fds, &accept_counts, &connects_received, i, kConnectAttempts]() { @@ -204,7 +204,7 @@ TEST_P(BindToDeviceDistributionTest, Tcp) { }); } - for (int i = 0; i < kConnectAttempts; i++) { + for (int32_t i = 0; i < kConnectAttempts; i++) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); ASSERT_THAT( @@ -212,22 +212,8 @@ TEST_P(BindToDeviceDistributionTest, Tcp) { connector.addr_len), SyscallSucceeds()); - // Do two separate sends to ensure two segments are received. This is - // required for netstack where read is incorrectly assuming a whole - // segment is read when endpoint.Read() is called which is technically - // incorrect as the syscall that invoked endpoint.Read() may only - // consume it partially. This results in a case where a close() of - // such a socket does not trigger a RST in netstack due to the - // endpoint assuming that the endpoint has no unread data. EXPECT_THAT(RetryEINTR(send)(fd.get(), &i, sizeof(i), 0), SyscallSucceedsWithValue(sizeof(i))); - - // TODO(gvisor.dev/issue/1449): Remove this block once netstack correctly - // generates a RST. - if (IsRunningOnGvisor()) { - EXPECT_THAT(RetryEINTR(send)(fd.get(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - } } // Join threads to be sure that all connections have been counted. @@ -235,7 +221,7 @@ TEST_P(BindToDeviceDistributionTest, Tcp) { listen_thread->Join(); } // Check that connections are distributed correctly among listening sockets. - for (int i = 0; i < accept_counts.size(); i++) { + for (long unsigned int i = 0; i < accept_counts.size(); i++) { EXPECT_THAT( accept_counts[i], EquivalentWithin(static_cast<int>(kConnectAttempts * @@ -308,7 +294,7 @@ TEST_P(BindToDeviceDistributionTest, Udp) { std::vector<std::unique_ptr<ScopedThread>> receiver_threads( listener_fds.size()); - for (int i = 0; i < listener_fds.size(); i++) { + for (long unsigned int i = 0; i < listener_fds.size(); i++) { receiver_threads[i] = absl::make_unique<ScopedThread>( [&listener_fds, &packets_per_socket, &packets_received, i]() { do { @@ -366,7 +352,7 @@ TEST_P(BindToDeviceDistributionTest, Udp) { receiver_thread->Join(); } // Check that packets are distributed correctly among listening sockets. - for (int i = 0; i < packets_per_socket.size(); i++) { + for (long unsigned int i = 0; i < packets_per_socket.size(); i++) { EXPECT_THAT( packets_per_socket[i], EquivalentWithin(static_cast<int>(kConnectAttempts * diff --git a/test/syscalls/linux/socket_generic.cc b/test/syscalls/linux/socket_generic.cc index 5d39e6fbd..de0b8bb11 100644 --- a/test/syscalls/linux/socket_generic.cc +++ b/test/syscalls/linux/socket_generic.cc @@ -43,6 +43,15 @@ TEST_P(AllSocketPairTest, BasicReadWrite) { EXPECT_EQ(data, absl::string_view(buf, 3)); } +TEST_P(AllSocketPairTest, BasicReadWriteBadBuffer) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + const std::string data = "abc"; + ASSERT_THAT(WriteFd(sockets->first_fd(), data.c_str(), 3), + SyscallSucceedsWithValue(3)); + ASSERT_THAT(ReadFd(sockets->second_fd(), nullptr, 3), + SyscallFailsWithErrno(EFAULT)); +} + TEST_P(AllSocketPairTest, BasicSendRecv) { auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); char sent_data[512]; @@ -818,5 +827,56 @@ TEST_P(AllSocketPairTest, GetSockoptProtocol) { } } +TEST_P(AllSocketPairTest, SetAndGetBooleanSocketOptions) { + int sock_opts[] = {SO_BROADCAST, SO_PASSCRED, SO_NO_CHECK, + SO_REUSEADDR, SO_REUSEPORT, SO_KEEPALIVE}; + for (int sock_opt : sock_opts) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + int enable = -1; + socklen_t enableLen = sizeof(enable); + + // Test that the option is initially set to false. + ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable, + &enableLen), + SyscallSucceeds()); + ASSERT_EQ(enableLen, sizeof(enable)); + EXPECT_EQ(enable, 0) << absl::StrFormat( + "getsockopt(fd, SOL_SOCKET, %d, &enable, &enableLen) => enable=%d", + sock_opt, enable); + + // Test that setting the option to true is reflected in the subsequent + // call to getsockopt(2). + enable = 1; + ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable, + sizeof(enable)), + SyscallSucceeds()); + enable = -1; + enableLen = sizeof(enable); + ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable, + &enableLen), + SyscallSucceeds()); + ASSERT_EQ(enableLen, sizeof(enable)); + EXPECT_EQ(enable, 1) << absl::StrFormat( + "getsockopt(fd, SOL_SOCKET, %d, &enable, &enableLen) => enable=%d", + sock_opt, enable); + } +} + +TEST_P(AllSocketPairTest, GetSocketOutOfBandInlineOption) { + // We do not support disabling this option. It is always enabled. + SKIP_IF(!IsRunningOnGvisor()); + + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + int enable = -1; + socklen_t enableLen = sizeof(enable); + + int want = 1; + ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_OOBINLINE, &enable, + &enableLen), + SyscallSucceeds()); + ASSERT_EQ(enableLen, sizeof(enable)); + EXPECT_EQ(enable, want); +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc index e19a83413..a11147085 100644 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ b/test/syscalls/linux/socket_inet_loopback.cc @@ -1185,19 +1185,44 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) { listen_fd.get(), reinterpret_cast<sockaddr*>(&accept_addr), &addrlen)); ASSERT_EQ(addrlen, listener.addr_len); - // TODO(gvisor.dev/issue/3812): Remove after SO_ERROR is fixed. - if (IsRunningOnGvisor()) { - char buf[10]; - ASSERT_THAT(ReadFd(accept_fd.get(), buf, sizeof(buf)), - SyscallFailsWithErrno(ECONNRESET)); - } else { + // Wait for accept_fd to process the RST. + const int kTimeout = 10000; + struct pollfd pfd = { + .fd = accept_fd.get(), + .events = POLLIN, + }; + ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); + ASSERT_EQ(pfd.revents, POLLIN | POLLHUP | POLLERR); + + { int err; socklen_t optlen = sizeof(err); ASSERT_THAT( getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), SyscallSucceeds()); + // This should return ECONNRESET as the socket just received a RST packet + // from the peer. + ASSERT_EQ(optlen, sizeof(err)); ASSERT_EQ(err, ECONNRESET); + } + { + int err; + socklen_t optlen = sizeof(err); + ASSERT_THAT( + getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), + SyscallSucceeds()); + // This should return no error as the previous getsockopt call would have + // cleared the socket error. ASSERT_EQ(optlen, sizeof(err)); + ASSERT_EQ(err, 0); + } + { + sockaddr_storage peer_addr; + socklen_t addrlen = sizeof(peer_addr); + // The socket is not connected anymore and should return ENOTCONN. + ASSERT_THAT(getpeername(accept_fd.get(), + reinterpret_cast<sockaddr*>(&peer_addr), &addrlen), + SyscallFailsWithErrno(ENOTCONN)); } } @@ -1482,7 +1507,7 @@ TEST_P(SocketInetReusePortTest, TcpPortReuseMultiThread_NoRandomSave) { } ScopedThread connecting_thread([&connector, &conn_addr]() { - for (int i = 0; i < kConnectAttempts; i++) { + for (int32_t i = 0; i < kConnectAttempts; i++) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); ASSERT_THAT( @@ -1490,22 +1515,8 @@ TEST_P(SocketInetReusePortTest, TcpPortReuseMultiThread_NoRandomSave) { connector.addr_len), SyscallSucceeds()); - // Do two separate sends to ensure two segments are received. This is - // required for netstack where read is incorrectly assuming a whole - // segment is read when endpoint.Read() is called which is technically - // incorrect as the syscall that invoked endpoint.Read() may only - // consume it partially. This results in a case where a close() of - // such a socket does not trigger a RST in netstack due to the - // endpoint assuming that the endpoint has no unread data. EXPECT_THAT(RetryEINTR(send)(fd.get(), &i, sizeof(i), 0), SyscallSucceedsWithValue(sizeof(i))); - - // TODO(gvisor.dev/issue/1449): Remove this block once netstack correctly - // generates a RST. - if (IsRunningOnGvisor()) { - EXPECT_THAT(RetryEINTR(send)(fd.get(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - } } }); @@ -2805,5 +2816,28 @@ INSTANTIATE_TEST_SUITE_P( } // namespace +// Check that loopback receives connections from any address in the range: +// 127.0.0.1 to 127.254.255.255. This behavior is exclusive to IPv4. +TEST_F(SocketInetLoopbackTest, LoopbackAddressRangeConnect) { + TestAddress const& listener = V4Any(); + + in_addr_t addresses[] = { + INADDR_LOOPBACK, + INADDR_LOOPBACK + 1, // 127.0.0.2 + (in_addr_t)0x7f000101, // 127.0.1.1 + (in_addr_t)0x7f010101, // 127.1.1.1 + (in_addr_t)0x7ffeffff, // 127.254.255.255 + }; + for (const auto& address : addresses) { + TestAddress connector("V4Loopback"); + connector.addr.ss_family = AF_INET; + connector.addr_len = sizeof(sockaddr_in); + reinterpret_cast<sockaddr_in*>(&connector.addr)->sin_addr.s_addr = + htonl(address); + + tcpSimpleConnectTest(listener, connector, true); + } +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc index f69f8f99f..1694e188a 100644 --- a/test/syscalls/linux/socket_ip_udp_generic.cc +++ b/test/syscalls/linux/socket_ip_udp_generic.cc @@ -15,6 +15,9 @@ #include "test/syscalls/linux/socket_ip_udp_generic.h" #include <errno.h> +#ifdef __linux__ +#include <linux/in6.h> +#endif // __linux__ #include <netinet/in.h> #include <netinet/tcp.h> #include <poll.h> @@ -356,6 +359,58 @@ TEST_P(UDPSocketPairTest, SetAndGetIPPKTINFO) { EXPECT_EQ(get_len, sizeof(get)); } +// Test getsockopt for a socket which is not set with IP_RECVORIGDSTADDR option. +TEST_P(UDPSocketPairTest, ReceiveOrigDstAddrDefault) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int get = -1; + socklen_t get_len = sizeof(get); + int level = SOL_IP; + int type = IP_RECVORIGDSTADDR; + if (sockets->first_addr()->sa_family == AF_INET6) { + level = SOL_IPV6; + type = IPV6_RECVORIGDSTADDR; + } + ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len), + SyscallSucceedsWithValue(0)); + EXPECT_EQ(get_len, sizeof(get)); + EXPECT_EQ(get, kSockOptOff); +} + +// Test setsockopt and getsockopt for a socket with IP_RECVORIGDSTADDR option. +TEST_P(UDPSocketPairTest, SetAndGetReceiveOrigDstAddr) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int level = SOL_IP; + int type = IP_RECVORIGDSTADDR; + if (sockets->first_addr()->sa_family == AF_INET6) { + level = SOL_IPV6; + type = IPV6_RECVORIGDSTADDR; + } + + // Check getsockopt before IP_PKTINFO is set. + int get = -1; + socklen_t get_len = sizeof(get); + + ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceedsWithValue(0)); + + ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len), + SyscallSucceedsWithValue(0)); + EXPECT_EQ(get, kSockOptOn); + EXPECT_EQ(get_len, sizeof(get)); + + ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &kSockOptOff, + sizeof(kSockOptOff)), + SyscallSucceedsWithValue(0)); + + ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len), + SyscallSucceedsWithValue(0)); + EXPECT_EQ(get, kSockOptOff); + EXPECT_EQ(get_len, sizeof(get)); +} + // Holds TOS or TClass information for IPv4 or IPv6 respectively. struct RecvTosOption { int level; @@ -438,7 +493,7 @@ TEST_P(UDPSocketPairTest, TClassRecvMismatch) { // This should only test AF_INET6 sockets for the mismatch behavior. SKIP_IF(GetParam().domain != AF_INET6); // IPV6_RECVTCLASS is only valid for SOCK_DGRAM and SOCK_RAW. - SKIP_IF(GetParam().type != SOCK_DGRAM | GetParam().type != SOCK_RAW); + SKIP_IF((GetParam().type != SOCK_DGRAM) | (GetParam().type != SOCK_RAW)); auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); diff --git a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc new file mode 100644 index 000000000..fdbb2216b --- /dev/null +++ b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc @@ -0,0 +1,59 @@ +// Copyright 2020 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 "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" + +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +void IPUDPUnboundExternalNetworkingSocketTest::SetUp() { + // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its + // IPv4 address on eth0. + found_net_interfaces_ = false; + + // Get interface list. + ASSERT_NO_ERRNO(if_helper_.Load()); + std::vector<std::string> if_names = if_helper_.InterfaceList(AF_INET); + if (if_names.size() != 2) { + return; + } + + // Figure out which interface is where. + std::string lo = if_names[0]; + std::string eth = if_names[1]; + if (lo != "lo") std::swap(lo, eth); + if (lo != "lo") return; + + lo_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(lo)); + auto lo_if_addr = if_helper_.GetAddr(AF_INET, lo); + if (lo_if_addr == nullptr) { + return; + } + lo_if_addr_ = *reinterpret_cast<const sockaddr_in*>(lo_if_addr); + + eth_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(eth)); + auto eth_if_addr = if_helper_.GetAddr(AF_INET, eth); + if (eth_if_addr == nullptr) { + return; + } + eth_if_addr_ = *reinterpret_cast<const sockaddr_in*>(eth_if_addr); + + found_net_interfaces_ = true; +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h new file mode 100644 index 000000000..e5287addb --- /dev/null +++ b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h @@ -0,0 +1,46 @@ +// Copyright 2020 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. + +#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ + +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_test_util.h" + +namespace gvisor { +namespace testing { + +// Test fixture for tests that apply to unbound IP UDP sockets in a sandbox +// with external networking support. +class IPUDPUnboundExternalNetworkingSocketTest : public SimpleSocketTest { + protected: + void SetUp() override; + + IfAddrHelper if_helper_; + + // found_net_interfaces_ is set to false if SetUp() could not obtain + // all interface infos that we need. + bool found_net_interfaces_; + + // Interface infos. + int lo_if_idx_; + int eth_if_idx_; + sockaddr_in lo_if_addr_; + sockaddr_in eth_if_addr_; +}; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc deleted file mode 100644 index 80f12b0a9..000000000 --- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2019 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 "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h" - -#include <netinet/in.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <cstdio> -#include <cstring> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// Verifies that a newly instantiated TCP socket does not have the -// broadcast socket option enabled. -TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, TCPBroadcastDefault) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int get = -1; - socklen_t get_sz = sizeof(get); - EXPECT_THAT( - getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get, kSockOptOff); - EXPECT_EQ(get_sz, sizeof(get)); -} - -// Verifies that a newly instantiated TCP socket returns true after enabling -// the broadcast socket option. -TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, SetTCPBroadcast) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_sz = sizeof(get); - EXPECT_THAT( - getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get, kSockOptOn); - EXPECT_EQ(get_sz, sizeof(get)); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index b3f54e7f6..e557572a7 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -2222,6 +2222,90 @@ TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) { EXPECT_EQ(received_pktinfo.ipi_addr.s_addr, htonl(INADDR_LOOPBACK)); } +// Test that socket will receive IP_RECVORIGDSTADDR control message. +TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) { + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver_addr = V4Loopback(); + int level = SOL_IP; + int type = IP_RECVORIGDSTADDR; + + ASSERT_THAT( + bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Retrieve the port bound by the receiver. + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver->get(), + reinterpret_cast<sockaddr*>(&receiver_addr.addr), + &receiver_addr_len), + SyscallSucceeds()); + EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + + ASSERT_THAT( + connect(sender->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Get address and port bound by the sender. + sockaddr_storage sender_addr_storage; + socklen_t sender_addr_len = sizeof(sender_addr_storage); + ASSERT_THAT(getsockname(sender->get(), + reinterpret_cast<sockaddr*>(&sender_addr_storage), + &sender_addr_len), + SyscallSucceeds()); + ASSERT_EQ(sender_addr_len, sizeof(struct sockaddr_in)); + + // Enable IP_RECVORIGDSTADDR on socket so that we get the original destination + // address of the datagram as auxiliary information in the control message. + ASSERT_THAT( + setsockopt(receiver->get(), level, type, &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + + // Prepare message to send. + constexpr size_t kDataLength = 1024; + msghdr sent_msg = {}; + iovec sent_iov = {}; + char sent_data[kDataLength]; + sent_iov.iov_base = sent_data; + sent_iov.iov_len = kDataLength; + sent_msg.msg_iov = &sent_iov; + sent_msg.msg_iovlen = 1; + sent_msg.msg_flags = 0; + + ASSERT_THAT(RetryEINTR(sendmsg)(sender->get(), &sent_msg, 0), + SyscallSucceedsWithValue(kDataLength)); + + msghdr received_msg = {}; + iovec received_iov = {}; + char received_data[kDataLength]; + char received_cmsg_buf[CMSG_SPACE(sizeof(sockaddr_in))] = {}; + size_t cmsg_data_len = sizeof(sockaddr_in); + received_iov.iov_base = received_data; + received_iov.iov_len = kDataLength; + received_msg.msg_iov = &received_iov; + received_msg.msg_iovlen = 1; + received_msg.msg_controllen = CMSG_LEN(cmsg_data_len); + received_msg.msg_control = received_cmsg_buf; + + ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/), + IsPosixErrorOkAndHolds(kDataLength)); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); + ASSERT_NE(cmsg, nullptr); + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len)); + EXPECT_EQ(cmsg->cmsg_level, level); + EXPECT_EQ(cmsg->cmsg_type, type); + + // Check the data + sockaddr_in received_addr = {}; + memcpy(&received_addr, CMSG_DATA(cmsg), sizeof(received_addr)); + auto orig_receiver_addr = reinterpret_cast<sockaddr_in*>(&receiver_addr.addr); + EXPECT_EQ(received_addr.sin_addr.s_addr, orig_receiver_addr->sin_addr.s_addr); + EXPECT_EQ(received_addr.sin_port, orig_receiver_addr->sin_port); +} + // Check that setting SO_RCVBUF below min is clamped to the minimum // receive buffer size. TEST_P(IPv4UDPUnboundSocketTest, SetSocketRecvBufBelowMin) { diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc index b206137eb..940289d15 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc @@ -14,23 +14,6 @@ #include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h" -#include <arpa/inet.h> -#include <ifaddrs.h> -#include <netinet/in.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <cstdint> -#include <cstdio> -#include <cstring> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - namespace gvisor { namespace testing { @@ -41,73 +24,6 @@ TestAddress V4EmptyAddress() { return t; } -void IPv4UDPUnboundExternalNetworkingSocketTest::SetUp() { - // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its - // IPv4 address on eth0. - found_net_interfaces_ = false; - - // Get interface list. - ASSERT_NO_ERRNO(if_helper_.Load()); - std::vector<std::string> if_names = if_helper_.InterfaceList(AF_INET); - if (if_names.size() != 2) { - return; - } - - // Figure out which interface is where. - std::string lo = if_names[0]; - std::string eth = if_names[1]; - if (lo != "lo") std::swap(lo, eth); - if (lo != "lo") return; - - lo_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(lo)); - auto lo_if_addr = if_helper_.GetAddr(AF_INET, lo); - if (lo_if_addr == nullptr) { - return; - } - lo_if_addr_ = *reinterpret_cast<const sockaddr_in*>(lo_if_addr); - - eth_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(eth)); - auto eth_if_addr = if_helper_.GetAddr(AF_INET, eth); - if (eth_if_addr == nullptr) { - return; - } - eth_if_addr_ = *reinterpret_cast<const sockaddr_in*>(eth_if_addr); - - found_net_interfaces_ = true; -} - -// Verifies that a newly instantiated UDP socket does not have the -// broadcast socket option enabled. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastDefault) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int get = -1; - socklen_t get_sz = sizeof(get); - EXPECT_THAT( - getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get, kSockOptOff); - EXPECT_EQ(get_sz, sizeof(get)); -} - -// Verifies that a newly instantiated UDP socket returns true after enabling -// the broadcast socket option. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, SetUDPBroadcast) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_sz = sizeof(get); - EXPECT_THAT( - getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get, kSockOptOn); - EXPECT_EQ(get_sz, sizeof(get)); -} - // Verifies that a broadcast UDP packet will arrive at all UDP sockets with // the destination port number. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h index 0e9e70e8e..20922ac1f 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h @@ -15,30 +15,15 @@ #ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ #define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" +#include "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" namespace gvisor { namespace testing { // Test fixture for tests that apply to unbound IPv4 UDP sockets in a sandbox // with external networking support. -class IPv4UDPUnboundExternalNetworkingSocketTest : public SimpleSocketTest { - protected: - void SetUp(); - - IfAddrHelper if_helper_; - - // found_net_interfaces_ is set to false if SetUp() could not obtain - // all interface infos that we need. - bool found_net_interfaces_; - - // Interface infos. - int lo_if_idx_; - int eth_if_idx_; - sockaddr_in lo_if_addr_; - sockaddr_in eth_if_addr_; -}; +using IPv4UDPUnboundExternalNetworkingSocketTest = + IPUDPUnboundExternalNetworkingSocketTest; } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc index 875016812..9a9ddc297 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc @@ -177,7 +177,7 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) { // Broadcasts from each socket should be received by every socket (including // the sending socket). - for (int w = 0; w < socks.size(); w++) { + for (long unsigned int w = 0; w < socks.size(); w++) { auto& w_sock = socks[w]; ASSERT_THAT( RetryEINTR(sendto)(w_sock->get(), send_buf, kSendBufSize, 0, @@ -187,7 +187,7 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) { << "write socks[" << w << "]"; // Check that we received the packet on all sockets. - for (int r = 0; r < socks.size(); r++) { + for (long unsigned int r = 0; r < socks.size(); r++) { auto& r_sock = socks[r]; struct pollfd poll_fd = {r_sock->get(), POLLIN, 0}; diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.cc b/test/syscalls/linux/socket_ipv6_udp_unbound.cc new file mode 100644 index 000000000..08526468e --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound.cc @@ -0,0 +1,131 @@ +// Copyright 2020 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 "test/syscalls/linux/socket_ipv6_udp_unbound.h" + +#include <arpa/inet.h> +#include <netinet/in.h> +#ifdef __linux__ +#include <linux/in6.h> +#endif // __linux__ +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> + +#include <cstdio> +#include <cstring> + +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// Test that socket will receive IP_RECVORIGDSTADDR control message. +TEST_P(IPv6UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) { + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver_addr = V6Loopback(); + int level = SOL_IPV6; + int type = IPV6_RECVORIGDSTADDR; + + ASSERT_THAT( + bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Retrieve the port bound by the receiver. + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver->get(), + reinterpret_cast<sockaddr*>(&receiver_addr.addr), + &receiver_addr_len), + SyscallSucceeds()); + EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + + ASSERT_THAT( + connect(sender->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Get address and port bound by the sender. + sockaddr_storage sender_addr_storage; + socklen_t sender_addr_len = sizeof(sender_addr_storage); + ASSERT_THAT(getsockname(sender->get(), + reinterpret_cast<sockaddr*>(&sender_addr_storage), + &sender_addr_len), + SyscallSucceeds()); + ASSERT_EQ(sender_addr_len, sizeof(struct sockaddr_in6)); + + // Enable IP_RECVORIGDSTADDR on socket so that we get the original destination + // address of the datagram as auxiliary information in the control message. + ASSERT_THAT( + setsockopt(receiver->get(), level, type, &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + + // Prepare message to send. + constexpr size_t kDataLength = 1024; + msghdr sent_msg = {}; + iovec sent_iov = {}; + char sent_data[kDataLength]; + sent_iov.iov_base = sent_data; + sent_iov.iov_len = kDataLength; + sent_msg.msg_iov = &sent_iov; + sent_msg.msg_iovlen = 1; + sent_msg.msg_flags = 0; + + ASSERT_THAT(RetryEINTR(sendmsg)(sender->get(), &sent_msg, 0), + SyscallSucceedsWithValue(kDataLength)); + + msghdr received_msg = {}; + iovec received_iov = {}; + char received_data[kDataLength]; + char received_cmsg_buf[CMSG_SPACE(sizeof(sockaddr_in6))] = {}; + size_t cmsg_data_len = sizeof(sockaddr_in6); + received_iov.iov_base = received_data; + received_iov.iov_len = kDataLength; + received_msg.msg_iov = &received_iov; + received_msg.msg_iovlen = 1; + received_msg.msg_controllen = CMSG_LEN(cmsg_data_len); + received_msg.msg_control = received_cmsg_buf; + + ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/), + IsPosixErrorOkAndHolds(kDataLength)); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); + ASSERT_NE(cmsg, nullptr); + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len)); + EXPECT_EQ(cmsg->cmsg_level, level); + EXPECT_EQ(cmsg->cmsg_type, type); + + // Check that the received address in the control message matches the expected + // receiver's address. + sockaddr_in6 received_addr = {}; + memcpy(&received_addr, CMSG_DATA(cmsg), sizeof(received_addr)); + auto orig_receiver_addr = + reinterpret_cast<sockaddr_in6*>(&receiver_addr.addr); + EXPECT_EQ(memcmp(&received_addr.sin6_addr, &orig_receiver_addr->sin6_addr, + sizeof(in6_addr)), + 0); + EXPECT_EQ(received_addr.sin6_port, orig_receiver_addr->sin6_port); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.h b/test/syscalls/linux/socket_ipv6_udp_unbound.h new file mode 100644 index 000000000..71e160f73 --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound.h @@ -0,0 +1,29 @@ +// Copyright 2020 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. + +#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_ + +#include "test/syscalls/linux/socket_test_util.h" + +namespace gvisor { +namespace testing { + +// Test fixture for tests that apply to IPv6 UDP sockets. +using IPv6UDPUnboundSocketTest = SimpleSocketTest; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_ diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc new file mode 100644 index 000000000..7364a1ea5 --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc @@ -0,0 +1,90 @@ +// Copyright 2020 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 "test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h" + +namespace gvisor { +namespace testing { + +TEST_P(IPv6UDPUnboundExternalNetworkingSocketTest, TestJoinLeaveMulticast) { + SKIP_IF(!found_net_interfaces_); + + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + auto receiver_addr = V6Any(); + ASSERT_THAT( + bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver->get(), + reinterpret_cast<sockaddr*>(&receiver_addr.addr), + &receiver_addr_len), + SyscallSucceeds()); + EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + + // Register to receive multicast packets. + auto multicast_addr = V6Multicast(); + ipv6_mreq group_req = { + .ipv6mr_multiaddr = + reinterpret_cast<sockaddr_in6*>(&multicast_addr.addr)->sin6_addr, + .ipv6mr_interface = + (unsigned int)ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")), + }; + ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, + &group_req, sizeof(group_req)), + SyscallSucceeds()); + + // Set the sender to the loopback interface. + auto sender_addr = V6Loopback(); + ASSERT_THAT( + bind(sender->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), + sender_addr.addr_len), + SyscallSucceeds()); + + // Send a multicast packet. + auto send_addr = multicast_addr; + reinterpret_cast<sockaddr_in6*>(&send_addr.addr)->sin6_port = + reinterpret_cast<sockaddr_in6*>(&receiver_addr.addr)->sin6_port; + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&send_addr.addr), + send_addr.addr_len), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Check that we received the multicast packet. + char recv_buf[sizeof(send_buf)] = {}; + ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0), + SyscallSucceedsWithValue(sizeof(recv_buf))); + + EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); + + // Leave the group and make sure we don't receive its multicast traffic. + ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, + &group_req, sizeof(group_req)), + SyscallSucceeds()); + RandomizeBuffer(send_buf, sizeof(send_buf)); + ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&send_addr.addr), + send_addr.addr_len), + SyscallSucceedsWithValue(sizeof(send_buf))); + ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), + MSG_DONTWAIT), + SyscallFailsWithErrno(EAGAIN)); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h index fb582b224..731ae0a1f 100644 --- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h @@ -1,4 +1,4 @@ -// Copyright 2019 The gVisor Authors. +// Copyright 2020 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. @@ -12,19 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_ +#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ -#include "test/syscalls/linux/socket_test_util.h" +#include "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" namespace gvisor { namespace testing { -// Test fixture for tests that apply to unbound IPv4 TCP sockets in a sandbox +// Test fixture for tests that apply to unbound IPv6 UDP sockets in a sandbox // with external networking support. -using IPv4TCPUnboundExternalNetworkingSocketTest = SimpleSocketTest; +using IPv6UDPUnboundExternalNetworkingSocketTest = + IPUDPUnboundExternalNetworkingSocketTest; } // namespace testing } // namespace gvisor -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_ +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6yy_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking_test.cc index 797c4174e..5c764b8fd 100644 --- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking_test.cc @@ -1,4 +1,4 @@ -// Copyright 2019 The gVisor Authors. +// Copyright 2020 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. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h" +#include "test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h" #include <vector> @@ -26,12 +26,12 @@ namespace { std::vector<SocketKind> GetSockets() { return ApplyVec<SocketKind>( - IPv4TCPUnboundSocket, + IPv6UDPUnboundSocket, AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})); } -INSTANTIATE_TEST_SUITE_P(IPv4TCPUnboundSockets, - IPv4TCPUnboundExternalNetworkingSocketTest, +INSTANTIATE_TEST_SUITE_P(IPv6UDPUnboundSockets, + IPv6UDPUnboundExternalNetworkingSocketTest, ::testing::ValuesIn(GetSockets())); } // namespace diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc new file mode 100644 index 000000000..058336ecc --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc @@ -0,0 +1,32 @@ +// Copyright 2020 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 <vector> + +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_ipv6_udp_unbound.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +INSTANTIATE_TEST_SUITE_P( + IPv6UDPSockets, IPv6UDPUnboundSocketTest, + ::testing::ValuesIn(ApplyVec<SocketKind>(IPv6UDPUnboundSocket, + AllBitwiseCombinations(List<int>{ + 0, SOCK_NONBLOCK})))); + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc index a760581b5..26dacc95e 100644 --- a/test/syscalls/linux/socket_test_util.cc +++ b/test/syscalls/linux/socket_test_util.cc @@ -860,6 +860,17 @@ TestAddress V6Loopback() { return t; } +TestAddress V6Multicast() { + TestAddress t("V6Multicast"); + t.addr.ss_family = AF_INET6; + t.addr_len = sizeof(sockaddr_in6); + EXPECT_EQ( + 1, + inet_pton(AF_INET6, "ff05::1234", + reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr)); + return t; +} + // Checksum computes the internet checksum of a buffer. uint16_t Checksum(uint16_t* buf, ssize_t buf_size) { // Add up the 16-bit values in the buffer. diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h index 5e205339f..75c0d4735 100644 --- a/test/syscalls/linux/socket_test_util.h +++ b/test/syscalls/linux/socket_test_util.h @@ -502,6 +502,7 @@ TestAddress V4MappedLoopback(); TestAddress V4Multicast(); TestAddress V6Any(); TestAddress V6Loopback(); +TestAddress V6Multicast(); // Compute the internet checksum of an IP header. uint16_t IPChecksum(struct iphdr ip); diff --git a/test/syscalls/linux/socket_unix_unbound_filesystem.cc b/test/syscalls/linux/socket_unix_unbound_filesystem.cc index cab912152..a035fb095 100644 --- a/test/syscalls/linux/socket_unix_unbound_filesystem.cc +++ b/test/syscalls/linux/socket_unix_unbound_filesystem.cc @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <fcntl.h> #include <stdio.h> #include <sys/un.h> #include "gtest/gtest.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" +#include "test/util/file_descriptor.h" #include "test/util/test_util.h" namespace gvisor { @@ -70,6 +72,20 @@ TEST_P(UnboundFilesystemUnixSocketPairTest, GetSockNameLength) { strlen(want_addr.sun_path) + 1 + sizeof(want_addr.sun_family)); } +TEST_P(UnboundFilesystemUnixSocketPairTest, OpenSocketWithTruncate) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), + sockets->first_addr_size()), + SyscallSucceeds()); + + const struct sockaddr_un *addr = + reinterpret_cast<const struct sockaddr_un *>(sockets->first_addr()); + EXPECT_THAT(chmod(addr->sun_path, 0777), SyscallSucceeds()); + EXPECT_THAT(open(addr->sun_path, O_RDONLY | O_TRUNC), + SyscallFailsWithErrno(ENXIO)); +} + INSTANTIATE_TEST_SUITE_P( AllUnixDomainSockets, UnboundFilesystemUnixSocketPairTest, ::testing::ValuesIn(ApplyVec<SocketPairKind>( diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index 0b25d8b5f..714848b8e 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -467,7 +467,7 @@ TEST_P(TcpSocketTest, PollWithFullBufferBlocks) { TEST_P(TcpSocketTest, ClosedWriteBlockingSocket) { FillSocketBuffers(first_fd, second_fd); - constexpr int timeout = 2; + constexpr int timeout = 10; struct timeval tv = {.tv_sec = timeout, .tv_usec = 0}; EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), SyscallSucceeds()); @@ -485,7 +485,7 @@ TEST_P(TcpSocketTest, ClosedWriteBlockingSocket) { }); // Wait for the thread to be blocked on write. - absl::SleepFor(absl::Milliseconds(50)); + absl::SleepFor(absl::Milliseconds(250)); // Socket close does not have any effect on a blocked write. ASSERT_THAT(close(first_fd), SyscallSucceeds()); // Indicate to the cleanup routine that we are already closed. @@ -518,7 +518,7 @@ TEST_P(TcpSocketTest, ClosedReadBlockingSocket) { }); // Wait for the thread to be blocked on read. - absl::SleepFor(absl::Milliseconds(50)); + absl::SleepFor(absl::Milliseconds(250)); // Socket close does not have any effect on a blocked read. ASSERT_THAT(close(first_fd), SyscallSucceeds()); // Indicate to the cleanup routine that we are already closed. @@ -964,37 +964,156 @@ TEST_P(TcpSocketTest, PollAfterShutdown) { SyscallSucceedsWithValue(1)); } -TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) { +TEST_P(SimpleTcpSocketTest, NonBlockingConnectRetry) { + const FileDescriptor listener = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + // Initialize address to the loopback one. sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); socklen_t addrlen = sizeof(addr); - const FileDescriptor s = + // Bind to some port but don't listen yet. + ASSERT_THAT( + bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + // Get the address we're bound to, then connect to it. We need to do this + // because we're allowing the stack to pick a port for us. + ASSERT_THAT(getsockname(listener.get(), + reinterpret_cast<struct sockaddr*>(&addr), &addrlen), + SyscallSucceeds()); + + FileDescriptor connector = ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - // Set the FD to O_NONBLOCK. - int opts; - ASSERT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds()); + // Verify that connect fails. + ASSERT_THAT( + RetryEINTR(connect)(connector.get(), + reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNREFUSED)); - ASSERT_THAT(RetryEINTR(connect)( + // Now start listening + ASSERT_THAT(listen(listener.get(), SOMAXCONN), SyscallSucceeds()); + + // TODO(gvisor.dev/issue/3828): Issuing connect() again on a socket that + // failed first connect should succeed. + if (IsRunningOnGvisor()) { + ASSERT_THAT( + RetryEINTR(connect)(connector.get(), + reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNABORTED)); + return; + } + + // Verify that connect now succeeds. + ASSERT_THAT( + RetryEINTR(connect)(connector.get(), + reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + // Accept the connection. + const FileDescriptor accepted = + ASSERT_NO_ERRNO_AND_VALUE(Accept(listener.get(), nullptr, nullptr)); +} + +// nonBlockingConnectNoListener returns a socket on which a connect that is +// expected to fail has been issued. +PosixErrorOr<FileDescriptor> nonBlockingConnectNoListener(const int family, + sockaddr_storage addr, + socklen_t addrlen) { + // We will first create a socket and bind to ensure we bind a port but will + // not call listen on this socket. + // Then we will create a new socket that will connect to the port bound by + // the first socket and that shoud fail. + constexpr int sock_type = SOCK_STREAM | SOCK_NONBLOCK; + int b_sock; + RETURN_ERROR_IF_SYSCALL_FAIL(b_sock = socket(family, sock_type, IPPROTO_TCP)); + FileDescriptor b(b_sock); + EXPECT_THAT(bind(b.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + // Get the address bound by the listening socket. + EXPECT_THAT( + getsockname(b.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), + SyscallSucceeds()); + + // Now create another socket and issue a connect on this one. This connect + // should fail as there is no listener. + int c_sock; + RETURN_ERROR_IF_SYSCALL_FAIL(c_sock = socket(family, sock_type, IPPROTO_TCP)); + FileDescriptor s(c_sock); + + // Now connect to the bound address and this should fail as nothing + // is listening on the bound address. + EXPECT_THAT(RetryEINTR(connect)( s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), SyscallFailsWithErrno(EINPROGRESS)); - // Now polling on the FD with a timeout should return 0 corresponding to no - // FDs ready. - struct pollfd poll_fd = {s.get(), POLLOUT, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000), - SyscallSucceedsWithValue(1)); + // Wait for the connect to fail. + struct pollfd poll_fd = {s.get(), POLLERR, 0}; + EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 1000), SyscallSucceedsWithValue(1)); + return std::move(s); +} + +TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) { + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie(); int err; socklen_t optlen = sizeof(err); ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), SyscallSucceeds()); - + ASSERT_THAT(optlen, sizeof(err)); EXPECT_EQ(err, ECONNREFUSED); + + unsigned char c; + ASSERT_THAT(read(s.get(), &c, sizeof(c)), SyscallSucceedsWithValue(0)); + int opts; + EXPECT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds()); + opts &= ~O_NONBLOCK; + EXPECT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds()); + // Try connecting again. + ASSERT_THAT(RetryEINTR(connect)( + s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNABORTED)); +} + +TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerRead) { + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie(); + + unsigned char c; + ASSERT_THAT(read(s.get(), &c, 1), SyscallFailsWithErrno(ECONNREFUSED)); + ASSERT_THAT(read(s.get(), &c, 1), SyscallSucceedsWithValue(0)); + ASSERT_THAT(RetryEINTR(connect)( + s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNABORTED)); +} + +TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerPeek) { + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie(); + + unsigned char c; + ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK), + SyscallFailsWithErrno(ECONNREFUSED)); + ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK), SyscallSucceedsWithValue(0)); + ASSERT_THAT(RetryEINTR(connect)( + s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNABORTED)); } TEST_P(SimpleTcpSocketTest, SelfConnectSendRecv_NoRandomSave) { @@ -1235,6 +1354,19 @@ TEST_P(SimpleTcpSocketTest, CleanupOnConnectionRefused) { // Attempt #2, with the new socket and reused addr our connect should fail in // the same way as before, not with an EADDRINUSE. + // + // TODO(gvisor.dev/issue/3828): 2nd connect on a socket which failed connect + // first time should succeed. + // gVisor never issues the second connect and returns ECONNABORTED instead. + // Linux actually sends a SYN again and gets a RST and correctly returns + // ECONNREFUSED. + if (IsRunningOnGvisor()) { + ASSERT_THAT(connect(client_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallFailsWithErrno(ECONNABORTED)); + return; + } ASSERT_THAT(connect(client_s.get(), reinterpret_cast<const struct sockaddr*>(&bound_addr), bound_addrlen), diff --git a/test/syscalls/linux/tuntap.cc b/test/syscalls/linux/tuntap.cc index 97d554e72..538652183 100644 --- a/test/syscalls/linux/tuntap.cc +++ b/test/syscalls/linux/tuntap.cc @@ -162,12 +162,19 @@ TEST(TuntapStaticTest, NetTunExists) { class TuntapTest : public ::testing::Test { protected: + void SetUp() override { + have_net_admin_cap_ = + ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)); + } + void TearDown() override { - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))) { + if (have_net_admin_cap_) { // Bring back capability if we had dropped it in test case. ASSERT_NO_ERRNO(SetCapability(CAP_NET_ADMIN, true)); } } + + bool have_net_admin_cap_; }; TEST_F(TuntapTest, CreateInterfaceNoCap) { @@ -324,8 +331,9 @@ TEST_F(TuntapTest, PingKernel) { }; while (1) { inpkt r = {}; - int n = read(fd.get(), &r, sizeof(r)); - EXPECT_THAT(n, SyscallSucceeds()); + int nread = read(fd.get(), &r, sizeof(r)); + EXPECT_THAT(nread, SyscallSucceeds()); + long unsigned int n = static_cast<long unsigned int>(nread); if (n < sizeof(pihdr)) { std::cerr << "Ignored packet, protocol: " << r.pi.pi_protocol @@ -383,8 +391,9 @@ TEST_F(TuntapTest, SendUdpTriggersArpResolution) { }; while (1) { inpkt r = {}; - int n = read(fd.get(), &r, sizeof(r)); - EXPECT_THAT(n, SyscallSucceeds()); + int nread = read(fd.get(), &r, sizeof(r)); + EXPECT_THAT(nread, SyscallSucceeds()); + long unsigned int n = static_cast<long unsigned int>(nread); if (n < sizeof(pihdr)) { std::cerr << "Ignored packet, protocol: " << r.pi.pi_protocol diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index d65275fd3..650f12350 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -14,6 +14,8 @@ #include <arpa/inet.h> #include <fcntl.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> #include <ctime> @@ -374,6 +376,69 @@ TEST_P(UdpSocketTest, BindInUse) { SyscallFailsWithErrno(EADDRINUSE)); } +TEST_P(UdpSocketTest, ConnectWriteToInvalidPort) { + // Discover a free unused port by creating a new UDP socket, binding it + // recording the just bound port and closing it. This is not guaranteed as it + // can still race with other port UDP sockets trying to bind a port at the + // same time. + struct sockaddr_storage addr_storage = InetLoopbackAddr(); + socklen_t addrlen = sizeof(addr_storage); + struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); + FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); + ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds()); + ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds()); + EXPECT_EQ(addrlen, addrlen_); + EXPECT_NE(*Port(&addr_storage), 0); + ASSERT_THAT(close(s.release()), SyscallSucceeds()); + + // Now connect to the port that we just released. This should generate an + // ECONNREFUSED error. + ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds()); + char buf[512]; + RandomizeBuffer(buf, sizeof(buf)); + // Send from sock_ to an unbound port. + ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_), + SyscallSucceedsWithValue(sizeof(buf))); + + // Now verify that we got an ICMP error back of ECONNREFUSED. + int err; + socklen_t optlen = sizeof(err); + ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), + SyscallSucceeds()); + ASSERT_EQ(err, ECONNREFUSED); + ASSERT_EQ(optlen, sizeof(err)); +} + +TEST_P(UdpSocketTest, ConnectSimultaneousWriteToInvalidPort) { + // Discover a free unused port by creating a new UDP socket, binding it + // recording the just bound port and closing it. This is not guaranteed as it + // can still race with other port UDP sockets trying to bind a port at the + // same time. + struct sockaddr_storage addr_storage = InetLoopbackAddr(); + socklen_t addrlen = sizeof(addr_storage); + struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); + FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); + ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds()); + ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds()); + EXPECT_EQ(addrlen, addrlen_); + EXPECT_NE(*Port(&addr_storage), 0); + ASSERT_THAT(close(s.release()), SyscallSucceeds()); + + // Now connect to the port that we just released. + ScopedThread t([&] { + ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds()); + }); + + char buf[512]; + RandomizeBuffer(buf, sizeof(buf)); + // Send from sock_ to an unbound port. + ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_), + SyscallSucceedsWithValue(sizeof(buf))); + t.Join(); +} + TEST_P(UdpSocketTest, ReceiveAfterConnect) { ASSERT_NO_ERRNO(BindLoopback()); ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); @@ -716,6 +781,99 @@ TEST_P(UdpSocketTest, ConnectAndSendNoReceiver) { SyscallFailsWithErrno(ECONNREFUSED)); } +#ifdef __linux__ +TEST_P(UdpSocketTest, RecvErrorConnRefused) { + // We will simulate an ICMP error and verify that we do receive that error via + // recvmsg(MSG_ERRQUEUE). + ASSERT_NO_ERRNO(BindLoopback()); + // Close the socket to release the port so that we get an ICMP error. + ASSERT_THAT(close(bind_.release()), SyscallSucceeds()); + + // Set IP_RECVERR socket option to enable error queueing. + int v = kSockOptOn; + socklen_t optlen = sizeof(v); + int opt_level = SOL_IP; + int opt_type = IP_RECVERR; + if (GetParam() != AddressFamily::kIpv4) { + opt_level = SOL_IPV6; + opt_type = IPV6_RECVERR; + } + ASSERT_THAT(setsockopt(sock_.get(), opt_level, opt_type, &v, optlen), + SyscallSucceeds()); + + // Connect to loopback:bind_addr_ which should *hopefully* not be bound by an + // UDP socket. There is no easy way to ensure that the UDP port is not bound + // by another conncurrently running test. *This is potentially flaky*. + const int kBufLen = 300; + ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); + char buf[kBufLen]; + RandomizeBuffer(buf, sizeof(buf)); + // Send from sock_ to an unbound port. This should cause ECONNREFUSED. + EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0), + SyscallSucceedsWithValue(sizeof(buf))); + + // Dequeue error using recvmsg(MSG_ERRQUEUE). + char got[kBufLen]; + struct iovec iov; + iov.iov_base = reinterpret_cast<void*>(got); + iov.iov_len = kBufLen; + + size_t control_buf_len = CMSG_SPACE(sizeof(sock_extended_err) + addrlen_); + char* control_buf = static_cast<char*>(calloc(1, control_buf_len)); + struct sockaddr_storage remote; + memset(&remote, 0, sizeof(remote)); + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + msg.msg_control = control_buf; + msg.msg_controllen = control_buf_len; + msg.msg_name = reinterpret_cast<void*>(&remote); + msg.msg_namelen = addrlen_; + ASSERT_THAT(recvmsg(sock_.get(), &msg, MSG_ERRQUEUE), + SyscallSucceedsWithValue(kBufLen)); + + // Check the contents of msg. + EXPECT_EQ(memcmp(got, buf, sizeof(buf)), 0); // iovec check + // TODO(b/176251997): The next check fails on the gvisor platform due to the + // kernel bug. + if (!IsRunningWithHostinet() || GvisorPlatform() == Platform::kPtrace || + GvisorPlatform() == Platform::kKVM || + GvisorPlatform() == Platform::kNative) + EXPECT_NE(msg.msg_flags & MSG_ERRQUEUE, 0); + EXPECT_EQ(memcmp(&remote, bind_addr_, addrlen_), 0); + + // Check the contents of the control message. + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + ASSERT_NE(cmsg, nullptr); + EXPECT_EQ(CMSG_NXTHDR(&msg, cmsg), nullptr); + EXPECT_EQ(cmsg->cmsg_level, opt_level); + EXPECT_EQ(cmsg->cmsg_type, opt_type); + + // Check the contents of socket error. + struct sock_extended_err* sock_err = + (struct sock_extended_err*)CMSG_DATA(cmsg); + EXPECT_EQ(sock_err->ee_errno, ECONNREFUSED); + if (GetParam() == AddressFamily::kIpv4) { + EXPECT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP); + EXPECT_EQ(sock_err->ee_type, ICMP_DEST_UNREACH); + EXPECT_EQ(sock_err->ee_code, ICMP_PORT_UNREACH); + } else { + EXPECT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP6); + EXPECT_EQ(sock_err->ee_type, ICMP6_DST_UNREACH); + EXPECT_EQ(sock_err->ee_code, ICMP6_DST_UNREACH_NOPORT); + } + + // Now verify that the socket error was cleared by recvmsg(MSG_ERRQUEUE). + int err; + optlen = sizeof(err); + ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), + SyscallSucceeds()); + ASSERT_EQ(err, 0); + ASSERT_EQ(optlen, sizeof(err)); +} +#endif // __linux__ + TEST_P(UdpSocketTest, ZerolengthWriteAllowed) { // TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes. SKIP_IF(IsRunningWithHostinet()); |