summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls')
-rw-r--r--test/syscalls/BUILD8
-rw-r--r--test/syscalls/linux/BUILD70
-rw-r--r--test/syscalls/linux/accept_bind.cc3
-rw-r--r--test/syscalls/linux/cgroup.cc87
-rw-r--r--test/syscalls/linux/concurrency.cc3
-rw-r--r--test/syscalls/linux/epoll.cc4
-rw-r--r--test/syscalls/linux/exec.cc4
-rw-r--r--test/syscalls/linux/exec_state_workload.cc4
-rw-r--r--test/syscalls/linux/fcntl.cc4
-rw-r--r--test/syscalls/linux/flock.cc4
-rw-r--r--test/syscalls/linux/ip_socket_test_util.cc24
-rw-r--r--test/syscalls/linux/ip_socket_test_util.h16
-rw-r--r--test/syscalls/linux/lseek.cc2
-rw-r--r--test/syscalls/linux/ping_socket.cc221
-rw-r--r--test/syscalls/linux/pipe.cc50
-rw-r--r--test/syscalls/linux/poll.cc4
-rw-r--r--test/syscalls/linux/prctl.cc9
-rw-r--r--test/syscalls/linux/priority.cc23
-rw-r--r--test/syscalls/linux/proc.cc9
-rw-r--r--test/syscalls/linux/pty.cc30
-rw-r--r--test/syscalls/linux/read.cc3
-rw-r--r--test/syscalls/linux/rename.cc56
-rw-r--r--test/syscalls/linux/semaphore.cc17
-rw-r--r--test/syscalls/linux/setgid.cc8
-rw-r--r--test/syscalls/linux/sigstop.cc49
-rw-r--r--test/syscalls/linux/socket_bind_to_device_util.cc5
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc198
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic.cc2
-rw-r--r--test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc4
-rw-r--r--test/syscalls/linux/socket_test_util.cc115
-rw-r--r--test/syscalls/linux/socket_test_util.h30
-rw-r--r--test/syscalls/linux/timers.cc7
-rw-r--r--test/syscalls/linux/udp_socket.cc4
-rw-r--r--test/syscalls/linux/uidgid.cc4
-rw-r--r--test/syscalls/linux/uname.cc3
-rw-r--r--test/syscalls/linux/verity_getdents.cc95
-rw-r--r--test/syscalls/linux/verity_ioctl.cc103
-rw-r--r--test/syscalls/linux/verity_mmap.cc158
-rw-r--r--test/syscalls/linux/verity_mount.cc8
39 files changed, 1213 insertions, 235 deletions
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index 0435f61a2..99743b14a 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -216,6 +216,10 @@ syscall_test(
)
syscall_test(
+ test = "//test/syscalls/linux:verity_getdents_test",
+)
+
+syscall_test(
test = "//test/syscalls/linux:getrandom_test",
)
@@ -313,6 +317,10 @@ syscall_test(
)
syscall_test(
+ test = "//test/syscalls/linux:verity_mmap_test",
+)
+
+syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:mount_test",
)
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 94a582256..d8b562e9d 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -1,4 +1,4 @@
-load("//tools:defs.bzl", "cc_binary", "cc_library", "default_net_util", "gtest", "select_arch", "select_system")
+load("//tools:defs.bzl", "cc_binary", "cc_library", "default_net_util", "gbenchmark", "gtest", "select_arch", "select_system")
package(
default_visibility = ["//:sandbox"],
@@ -520,13 +520,14 @@ cc_binary(
srcs = ["concurrency.cc"],
linkstatic = 1,
deps = [
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
+ gbenchmark,
gtest,
"//test/util:platform_util",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/time",
],
)
@@ -961,6 +962,22 @@ cc_binary(
)
cc_binary(
+ name = "verity_getdents_test",
+ testonly = 1,
+ srcs = ["verity_getdents.cc"],
+ linkstatic = 1,
+ deps = [
+ "//test/util:capability_util",
+ gtest,
+ "//test/util:fs_util",
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "//test/util:verity_util",
+ ],
+)
+
+cc_binary(
name = "getrandom_test",
testonly = 1,
srcs = ["getrandom.cc"],
@@ -1024,6 +1041,7 @@ cc_binary(
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
+ "//test/util:verity_util",
],
)
@@ -1294,6 +1312,23 @@ cc_binary(
)
cc_binary(
+ name = "verity_mmap_test",
+ testonly = 1,
+ srcs = ["verity_mmap.cc"],
+ linkstatic = 1,
+ deps = [
+ "//test/util:capability_util",
+ gtest,
+ "//test/util:fs_util",
+ "//test/util:memory_util",
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "//test/util:verity_util",
+ ],
+)
+
+cc_binary(
name = "mount_test",
testonly = 1,
srcs = ["mount.cc"],
@@ -1327,6 +1362,7 @@ cc_binary(
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
+ "//test/util:verity_util",
],
)
@@ -1471,6 +1507,7 @@ cc_binary(
"//test/util:cleanup",
"//test/util:posix_error",
"//test/util:pty_util",
+ "//test/util:signal_util",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
@@ -1497,7 +1534,8 @@ cc_binary(
cc_binary(
name = "partial_bad_buffer_test",
testonly = 1,
- srcs = ["partial_bad_buffer.cc"],
+ # Android does not support preadv or pwritev in r22.
+ srcs = select_system(linux = ["partial_bad_buffer.cc"]),
linkstatic = 1,
deps = [
":socket_test_util",
@@ -1534,10 +1572,13 @@ cc_binary(
srcs = ["ping_socket.cc"],
linkstatic = 1,
deps = [
+ ":ip_socket_test_util",
":socket_test_util",
"//test/util:file_descriptor",
+ "@com_google_absl//absl/algorithm:container",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:optional",
gtest,
- "//test/util:save_util",
"//test/util:test_main",
"//test/util:test_util",
],
@@ -1556,6 +1597,7 @@ cc_binary(
"@com_google_absl//absl/time",
gtest,
"//test/util:posix_error",
+ "//test/util:signal_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
@@ -3653,7 +3695,8 @@ cc_binary(
cc_binary(
name = "sync_test",
testonly = 1,
- srcs = ["sync.cc"],
+ # Android does not support syncfs in r22.
+ srcs = select_system(linux = ["sync.cc"]),
linkstatic = 1,
deps = [
gtest,
@@ -3766,10 +3809,9 @@ cc_binary(
srcs = ["timers.cc"],
linkstatic = 1,
deps = [
- "//test/util:cleanup",
- "@com_google_absl//absl/flags:flag",
- "@com_google_absl//absl/time",
+ gbenchmark,
gtest,
+ "//test/util:cleanup",
"//test/util:logging",
"//test/util:multiprocess_util",
"//test/util:posix_error",
@@ -3777,6 +3819,8 @@ cc_binary(
"//test/util:test_util",
"//test/util:thread_util",
"//test/util:timer_util",
+ "@com_google_absl//absl/flags:flag",
+ "@com_google_absl//absl/time",
],
)
@@ -3948,7 +3992,8 @@ cc_binary(
cc_binary(
name = "utimes_test",
testonly = 1,
- srcs = ["utimes.cc"],
+ # Android does not support futimesat in r22.
+ srcs = select_system(linux = ["utimes.cc"]),
linkstatic = 1,
deps = [
"//test/util:file_descriptor",
@@ -4064,7 +4109,8 @@ cc_binary(
cc_binary(
name = "semaphore_test",
testonly = 1,
- srcs = ["semaphore.cc"],
+ # Android does not support XSI semaphores in r22.
+ srcs = select_system(linux = ["semaphore.cc"]),
linkstatic = 1,
deps = [
"//test/util:capability_util",
@@ -4247,10 +4293,12 @@ cc_binary(
"//test/util:mount_util",
"@com_google_absl//absl/strings",
gtest,
+ "//test/util:cleanup",
"//test/util:posix_error",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
+ "//test/util:thread_util",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
],
diff --git a/test/syscalls/linux/accept_bind.cc b/test/syscalls/linux/accept_bind.cc
index fe560cfc5..aa13e9f84 100644
--- a/test/syscalls/linux/accept_bind.cc
+++ b/test/syscalls/linux/accept_bind.cc
@@ -37,7 +37,8 @@ TEST_P(AllSocketPairTest, Listen) {
sockets->first_addr_size()),
SyscallSucceeds());
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5),
+ ASSERT_THAT(listen(sockets->first_fd(),
+ /* backlog = */ 5), // NOLINT(bugprone-argument-comment)
SyscallSucceeds());
}
diff --git a/test/syscalls/linux/cgroup.cc b/test/syscalls/linux/cgroup.cc
index 70ad5868f..f29891571 100644
--- a/test/syscalls/linux/cgroup.cc
+++ b/test/syscalls/linux/cgroup.cc
@@ -25,9 +25,11 @@
#include "absl/strings/str_split.h"
#include "test/util/capability_util.h"
#include "test/util/cgroup_util.h"
+#include "test/util/cleanup.h"
#include "test/util/mount_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
+#include "test/util/thread_util.h"
namespace gvisor {
namespace testing {
@@ -192,6 +194,91 @@ TEST(Cgroup, MoptAllMustBeExclusive) {
SyscallFailsWithErrno(EINVAL));
}
+TEST(Cgroup, MountRace) {
+ SKIP_IF(!CgroupsAvailable());
+
+ TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+
+ const DisableSave ds; // Too many syscalls.
+
+ auto mount_thread = [&mountpoint]() {
+ for (int i = 0; i < 100; ++i) {
+ mount("none", mountpoint.path().c_str(), "cgroup", 0, 0);
+ }
+ };
+ std::list<ScopedThread> threads;
+ for (int i = 0; i < 10; ++i) {
+ threads.emplace_back(mount_thread);
+ }
+ for (auto& t : threads) {
+ t.Join();
+ }
+
+ auto cleanup = Cleanup([&mountpoint] {
+ // We need 1 umount call per successful mount. If some of the mount calls
+ // were unsuccessful, their corresponding umount will silently fail.
+ for (int i = 0; i < (10 * 100) + 1; ++i) {
+ umount(mountpoint.path().c_str());
+ }
+ });
+
+ Cgroup c = Cgroup(mountpoint.path());
+ // c should be a valid cgroup.
+ EXPECT_NO_ERRNO(c.ContainsCallingProcess());
+}
+
+TEST(Cgroup, MountUnmountRace) {
+ SKIP_IF(!CgroupsAvailable());
+
+ TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+
+ const DisableSave ds; // Too many syscalls.
+
+ auto mount_thread = [&mountpoint]() {
+ for (int i = 0; i < 100; ++i) {
+ mount("none", mountpoint.path().c_str(), "cgroup", 0, 0);
+ }
+ };
+ auto unmount_thread = [&mountpoint]() {
+ for (int i = 0; i < 100; ++i) {
+ umount(mountpoint.path().c_str());
+ }
+ };
+ std::list<ScopedThread> threads;
+ for (int i = 0; i < 10; ++i) {
+ threads.emplace_back(mount_thread);
+ }
+ for (int i = 0; i < 10; ++i) {
+ threads.emplace_back(unmount_thread);
+ }
+ for (auto& t : threads) {
+ t.Join();
+ }
+
+ // We don't know how many mount refs are remaining, since the count depends on
+ // the ordering of mount and umount calls. Keep calling unmount until it
+ // returns an error.
+ while (umount(mountpoint.path().c_str()) == 0) {
+ }
+}
+
+TEST(Cgroup, UnmountRepeated) {
+ SKIP_IF(!CgroupsAvailable());
+
+ const DisableSave ds; // Too many syscalls.
+
+ Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
+ Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(""));
+
+ // First unmount should succeed.
+ EXPECT_THAT(umount(c.Path().c_str()), SyscallSucceeds());
+
+ // We just manually unmounted, so release managed resources.
+ m.release(c);
+
+ EXPECT_THAT(umount(c.Path().c_str()), SyscallFailsWithErrno(EINVAL));
+}
+
TEST(MemoryCgroup, MemoryUsageInBytes) {
SKIP_IF(!CgroupsAvailable());
diff --git a/test/syscalls/linux/concurrency.cc b/test/syscalls/linux/concurrency.cc
index 7cd6a75bd..f2daf49ee 100644
--- a/test/syscalls/linux/concurrency.cc
+++ b/test/syscalls/linux/concurrency.cc
@@ -20,6 +20,7 @@
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
+#include "benchmark/benchmark.h"
#include "test/util/platform_util.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
@@ -106,6 +107,8 @@ TEST(ConcurrencyTest, MultiProcessConcurrency) {
pid_t child_pid = fork();
if (child_pid == 0) {
while (true) {
+ int x = 0;
+ benchmark::DoNotOptimize(x); // Don't optimize this loop away.
}
}
ASSERT_THAT(child_pid, SyscallSucceeds());
diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc
index af3d27894..3ef8b0327 100644
--- a/test/syscalls/linux/epoll.cc
+++ b/test/syscalls/linux/epoll.cc
@@ -230,6 +230,8 @@ TEST(EpollTest, WaitThenUnblock) {
EXPECT_THAT(pthread_detach(thread), SyscallSucceeds());
}
+#ifndef ANDROID // Android does not support pthread_cancel
+
void sighandler(int s) {}
void* signaler(void* arg) {
@@ -272,6 +274,8 @@ TEST(EpollTest, UnblockWithSignal) {
EXPECT_THAT(pthread_detach(thread), SyscallSucceeds());
}
+#endif // ANDROID
+
TEST(EpollTest, TimeoutNoFds) {
auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
struct epoll_event result[kFDsPerEpoll];
diff --git a/test/syscalls/linux/exec.cc b/test/syscalls/linux/exec.cc
index c5acfc794..a0016146a 100644
--- a/test/syscalls/linux/exec.cc
+++ b/test/syscalls/linux/exec.cc
@@ -283,7 +283,7 @@ TEST(ExecTest, InterpreterScriptTrailingWhitespace) {
TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload)));
TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " "), 0755));
+ GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " \n"), 0755));
CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(1, 0),
absl::StrCat(link.path(), "\n", script.path(), "\n"));
@@ -304,7 +304,7 @@ TEST(ExecTest, InterpreterScriptArgWhitespace) {
TEST(ExecTest, InterpreterScriptNoPath) {
TempPath script = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "#!", 0755));
+ TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "#!\n\n", 0755));
int execve_errno;
ASSERT_NO_ERRNO_AND_VALUE(
diff --git a/test/syscalls/linux/exec_state_workload.cc b/test/syscalls/linux/exec_state_workload.cc
index 028902b14..eafdc2bfa 100644
--- a/test/syscalls/linux/exec_state_workload.cc
+++ b/test/syscalls/linux/exec_state_workload.cc
@@ -26,6 +26,8 @@
#include "absl/strings/numbers.h"
+#ifndef ANDROID // Conflicts with existing operator<< on Android.
+
// Pretty-print a sigset_t.
std::ostream& operator<<(std::ostream& out, const sigset_t& s) {
out << "{ ";
@@ -40,6 +42,8 @@ std::ostream& operator<<(std::ostream& out, const sigset_t& s) {
return out;
}
+#endif
+
// Verify that the signo handler is handler.
int CheckSigHandler(uint32_t signo, uintptr_t handler) {
struct sigaction sa;
diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc
index 4fa6751ff..91526572b 100644
--- a/test/syscalls/linux/fcntl.cc
+++ b/test/syscalls/linux/fcntl.cc
@@ -390,9 +390,7 @@ TEST_F(FcntlLockTest, SetLockDir) {
}
TEST_F(FcntlLockTest, SetLockSymlink) {
- // TODO(gvisor.dev/issue/2782): Replace with IsRunningWithVFS1() when O_PATH
- // is supported.
- SKIP_IF(IsRunningOnGvisor());
+ SKIP_IF(IsRunningWithVFS1());
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
auto symlink = ASSERT_NO_ERRNO_AND_VALUE(
diff --git a/test/syscalls/linux/flock.cc b/test/syscalls/linux/flock.cc
index fd387aa45..10dad042f 100644
--- a/test/syscalls/linux/flock.cc
+++ b/test/syscalls/linux/flock.cc
@@ -662,9 +662,7 @@ TEST(FlockTestNoFixture, FlockDir) {
}
TEST(FlockTestNoFixture, FlockSymlink) {
- // TODO(gvisor.dev/issue/2782): Replace with IsRunningWithVFS1() when O_PATH
- // is supported.
- SKIP_IF(IsRunningOnGvisor());
+ SKIP_IF(IsRunningWithVFS1());
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
auto symlink = ASSERT_NO_ERRNO_AND_VALUE(
diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc
index 98d07ae85..e90a7e411 100644
--- a/test/syscalls/linux/ip_socket_test_util.cc
+++ b/test/syscalls/linux/ip_socket_test_util.cc
@@ -140,6 +140,22 @@ SocketPairKind IPv4UDPUnboundSocketPair(int type) {
/* dual_stack = */ false)};
}
+SocketKind ICMPUnboundSocket(int type) {
+ std::string description =
+ absl::StrCat(DescribeSocketType(type), "ICMP socket");
+ return SocketKind{
+ description, AF_INET, type | SOCK_DGRAM, IPPROTO_ICMP,
+ UnboundSocketCreator(AF_INET, type | SOCK_DGRAM, IPPROTO_ICMP)};
+}
+
+SocketKind ICMPv6UnboundSocket(int type) {
+ std::string description =
+ absl::StrCat(DescribeSocketType(type), "ICMPv6 socket");
+ return SocketKind{
+ description, AF_INET6, type | SOCK_DGRAM, IPPROTO_ICMPV6,
+ UnboundSocketCreator(AF_INET6, type | SOCK_DGRAM, IPPROTO_ICMPV6)};
+}
+
SocketKind IPv4UDPUnboundSocket(int type) {
std::string description =
absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket");
@@ -174,13 +190,21 @@ SocketKind IPv6TCPUnboundSocket(int type) {
PosixError IfAddrHelper::Load() {
Release();
+#ifndef ANDROID
RETURN_ERROR_IF_SYSCALL_FAIL(getifaddrs(&ifaddr_));
+#else
+ // Android does not support getifaddrs in r22.
+ return PosixError(ENOSYS, "getifaddrs");
+#endif
return NoError();
}
void IfAddrHelper::Release() {
if (ifaddr_) {
+#ifndef ANDROID
+ // Android does not support freeifaddrs in r22.
freeifaddrs(ifaddr_);
+#endif
ifaddr_ = nullptr;
}
}
diff --git a/test/syscalls/linux/ip_socket_test_util.h b/test/syscalls/linux/ip_socket_test_util.h
index 9c3859fcd..bde481f7e 100644
--- a/test/syscalls/linux/ip_socket_test_util.h
+++ b/test/syscalls/linux/ip_socket_test_util.h
@@ -84,20 +84,28 @@ SocketPairKind DualStackUDPBidirectionalBindSocketPair(int type);
// SocketPairs created with AF_INET and the given type.
SocketPairKind IPv4UDPUnboundSocketPair(int type);
+// ICMPUnboundSocket returns a SocketKind that represents a SimpleSocket created
+// with AF_INET, SOCK_DGRAM, IPPROTO_ICMP, and the given type.
+SocketKind ICMPUnboundSocket(int type);
+
+// ICMPv6UnboundSocket returns a SocketKind that represents a SimpleSocket
+// created with AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6, and the given type.
+SocketKind ICMPv6UnboundSocket(int type);
+
// IPv4UDPUnboundSocket returns a SocketKind that represents a SimpleSocket
-// created with AF_INET, SOCK_DGRAM, and the given type.
+// created with AF_INET, SOCK_DGRAM, IPPROTO_UDP, and the given type.
SocketKind IPv4UDPUnboundSocket(int type);
// IPv6UDPUnboundSocket returns a SocketKind that represents a SimpleSocket
-// created with AF_INET6, SOCK_DGRAM, and the given type.
+// created with AF_INET6, SOCK_DGRAM, IPPROTO_UDP, and the given type.
SocketKind IPv6UDPUnboundSocket(int type);
// IPv4TCPUnboundSocket returns a SocketKind that represents a SimpleSocket
-// created with AF_INET, SOCK_STREAM and the given type.
+// created with AF_INET, SOCK_STREAM, IPPROTO_TCP and the given type.
SocketKind IPv4TCPUnboundSocket(int type);
// IPv6TCPUnboundSocket returns a SocketKind that represents a SimpleSocket
-// created with AF_INET6, SOCK_STREAM and the given type.
+// created with AF_INET6, SOCK_STREAM, IPPROTO_TCP and the given type.
SocketKind IPv6TCPUnboundSocket(int type);
// IfAddrHelper is a helper class that determines the local interfaces present
diff --git a/test/syscalls/linux/lseek.cc b/test/syscalls/linux/lseek.cc
index 6ce1e6cc3..d4f89527c 100644
--- a/test/syscalls/linux/lseek.cc
+++ b/test/syscalls/linux/lseek.cc
@@ -150,7 +150,7 @@ TEST(LseekTest, SeekCurrentDir) {
// From include/linux/fs.h.
constexpr loff_t MAX_LFS_FILESIZE = 0x7fffffffffffffff;
- char* dir = get_current_dir_name();
+ char* dir = getcwd(NULL, 0);
const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir, O_RDONLY));
ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
diff --git a/test/syscalls/linux/ping_socket.cc b/test/syscalls/linux/ping_socket.cc
index 8b78e4b16..8268e91da 100644
--- a/test/syscalls/linux/ping_socket.cc
+++ b/test/syscalls/linux/ping_socket.cc
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
@@ -19,14 +20,22 @@
#include <sys/types.h>
#include <unistd.h>
+#include <cctype>
+#include <cstring>
#include <vector>
#include "gtest/gtest.h"
+#include "absl/algorithm/container.h"
+#include "absl/strings/str_join.h"
+#include "absl/types/optional.h"
+#include "test/syscalls/linux/ip_socket_test_util.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/file_descriptor.h"
-#include "test/util/save_util.h"
#include "test/util/test_util.h"
+// Note: These tests require /proc/sys/net/ipv4/ping_group_range to be
+// configured to allow the tester to create ping sockets (see icmp(7)).
+
namespace gvisor {
namespace testing {
namespace {
@@ -42,7 +51,8 @@ TEST(PingSocket, ICMPPortExhaustion) {
auto s = Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (!s.ok()) {
ASSERT_EQ(s.error().errno_value(), EACCES);
- GTEST_SKIP();
+ GTEST_SKIP() << "TODO(gvisor.dev/issue/6126): Buildkite does not allow "
+ "creation of ICMP or ICMPv6 sockets";
}
}
@@ -70,7 +80,212 @@ TEST(PingSocket, ICMPPortExhaustion) {
}
}
-} // namespace
+struct BindTestCase {
+ TestAddress bind_to;
+ int want = 0;
+ absl::optional<int> want_gvisor;
+};
+
+// Test fixture for socket binding.
+class Fixture
+ : public ::testing::TestWithParam<std::tuple<SocketKind, BindTestCase>> {};
+
+TEST_P(Fixture, Bind) {
+ auto [socket_factory, test_case] = GetParam();
+ auto socket = socket_factory.Create();
+ if (!socket.ok()) {
+ ASSERT_EQ(socket.error().errno_value(), EACCES);
+ GTEST_SKIP() << "TODO(gvisor.dev/issue/6126): Buildkite does not allow "
+ "creation of ICMP or ICMPv6 sockets";
+ }
+ auto socket_fd = std::move(socket).ValueOrDie();
+
+ const int want = test_case.want_gvisor.has_value() && IsRunningOnGvisor()
+ ? *test_case.want_gvisor
+ : test_case.want;
+ if (want == 0) {
+ EXPECT_THAT(bind(socket_fd->get(), AsSockAddr(&test_case.bind_to.addr),
+ test_case.bind_to.addr_len),
+ SyscallSucceeds());
+ } else {
+ EXPECT_THAT(bind(socket_fd->get(), AsSockAddr(&test_case.bind_to.addr),
+ test_case.bind_to.addr_len),
+ SyscallFailsWithErrno(want));
+ }
+}
+
+std::vector<std::tuple<SocketKind, BindTestCase>> ICMPTestCases() {
+ return ApplyVec<std::tuple<SocketKind, BindTestCase>>(
+ [](const BindTestCase& test_case) {
+ return std::make_tuple(ICMPUnboundSocket(0), test_case);
+ },
+ std::vector<BindTestCase>{
+ {
+ .bind_to = V4Any(),
+ .want = 0,
+ .want_gvisor = 0,
+ },
+ {
+ .bind_to = V4Broadcast(),
+ .want = EADDRNOTAVAIL,
+ // TODO(gvisor.dev/issue/5711): Remove want_gvisor once ICMP
+ // sockets are no longer allowed to bind to broadcast addresses.
+ .want_gvisor = 0,
+ },
+ {
+ .bind_to = V4Loopback(),
+ .want = 0,
+ },
+ {
+ .bind_to = V4LoopbackSubnetBroadcast(),
+ .want = EADDRNOTAVAIL,
+ // TODO(gvisor.dev/issue/5711): Remove want_gvisor once ICMP
+ // sockets are no longer allowed to bind to broadcast addresses.
+ .want_gvisor = 0,
+ },
+ {
+ .bind_to = V4Multicast(),
+ .want = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V4MulticastAllHosts(),
+ .want = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V4AddrStr("IPv4UnknownUnicast", "192.168.1.1"),
+ .want = EADDRNOTAVAIL,
+ },
+ // TODO(gvisor.dev/issue/6021): Remove want_gvisor from all the test
+ // cases below once ICMP sockets return EAFNOSUPPORT when binding to
+ // IPv6 addresses.
+ {
+ .bind_to = V6Any(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6Loopback(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6Multicast(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6MulticastInterfaceLocalAllNodes(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6MulticastLinkLocalAllNodes(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6MulticastLinkLocalAllRouters(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6AddrStr("IPv6UnknownUnicast", "fc00::1"),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ });
+}
+std::vector<std::tuple<SocketKind, BindTestCase>> ICMPv6TestCases() {
+ return ApplyVec<std::tuple<SocketKind, BindTestCase>>(
+ [](const BindTestCase& test_case) {
+ return std::make_tuple(ICMPv6UnboundSocket(0), test_case);
+ },
+ std::vector<BindTestCase>{
+ {
+ .bind_to = V4Any(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4Broadcast(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4Loopback(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4LoopbackSubnetBroadcast(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4Multicast(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4MulticastAllHosts(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4AddrStr("IPv4UnknownUnicast", "192.168.1.1"),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V6Any(),
+ .want = 0,
+ },
+ {
+ .bind_to = V6Loopback(),
+ .want = 0,
+ },
+ // TODO(gvisor.dev/issue/6021): Remove want_gvisor from all the
+ // multicast test cases below once ICMPv6 sockets return EINVAL when
+ // binding to IPv6 multicast addresses.
+ {
+ .bind_to = V6Multicast(),
+ .want = EINVAL,
+ .want_gvisor = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V6MulticastInterfaceLocalAllNodes(),
+ .want = EINVAL,
+ .want_gvisor = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V6MulticastLinkLocalAllNodes(),
+ .want = EINVAL,
+ .want_gvisor = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V6MulticastLinkLocalAllRouters(),
+ .want = EINVAL,
+ .want_gvisor = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V6AddrStr("IPv6UnknownUnicast", "fc00::1"),
+ .want = EADDRNOTAVAIL,
+ },
+ });
+}
+
+std::vector<std::tuple<SocketKind, BindTestCase>> AllTestCases() {
+ return VecCat<std::tuple<SocketKind, BindTestCase>>(ICMPTestCases(),
+ ICMPv6TestCases());
+}
+
+std::string TestDescription(
+ const ::testing::TestParamInfo<Fixture::ParamType>& info) {
+ auto [socket_factory, test_case] = info.param;
+ std::string name = absl::StrJoin(
+ {socket_factory.description, test_case.bind_to.description}, "_");
+ absl::c_replace_if(
+ name, [](char c) { return !std::isalnum(c); }, '_');
+ return name;
+}
+
+INSTANTIATE_TEST_SUITE_P(PingSockets, Fixture,
+ ::testing::ValuesIn(AllTestCases()), TestDescription);
+
+} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc
index 96c454485..0bba86846 100644
--- a/test/syscalls/linux/pipe.cc
+++ b/test/syscalls/linux/pipe.cc
@@ -14,6 +14,7 @@
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <linux/magic.h>
+#include <signal.h>
#include <sys/ioctl.h>
#include <sys/statfs.h>
#include <sys/uio.h>
@@ -29,6 +30,7 @@
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
#include "test/util/posix_error.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"
@@ -44,6 +46,29 @@ constexpr int kTestValue = 0x12345678;
// Used for synchronization in race tests.
const absl::Duration syncDelay = absl::Seconds(2);
+std::atomic<int> global_num_signals_received = 0;
+void SigRecordingHandler(int signum, siginfo_t* siginfo,
+ void* unused_ucontext) {
+ global_num_signals_received++;
+}
+
+PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) {
+ global_num_signals_received = 0;
+ struct sigaction handler;
+ handler.sa_sigaction = SigRecordingHandler;
+ sigemptyset(&handler.sa_mask);
+ handler.sa_flags = SA_SIGINFO;
+ return ScopedSigaction(signum, handler);
+}
+
+void WaitForSignalDelivery(absl::Duration timeout, int max_expected) {
+ absl::Time wait_start = absl::Now();
+ while (global_num_signals_received < max_expected &&
+ absl::Now() - wait_start < timeout) {
+ absl::SleepFor(absl::Milliseconds(10));
+ }
+}
+
struct PipeCreator {
std::string name_;
@@ -267,6 +292,9 @@ TEST_P(PipeTest, Seek) {
}
}
+#ifndef ANDROID
+// Android does not support preadv or pwritev in r22.
+
TEST_P(PipeTest, OffsetCalls) {
SKIP_IF(!CreateBlocking());
@@ -283,6 +311,8 @@ TEST_P(PipeTest, OffsetCalls) {
EXPECT_THAT(pwritev(rfd_.get(), &iov, 1, 0), SyscallFailsWithErrno(ESPIPE));
}
+#endif // ANDROID
+
TEST_P(PipeTest, WriterSideCloses) {
SKIP_IF(!CreateBlocking());
@@ -333,10 +363,16 @@ TEST_P(PipeTest, WriterSideClosesReadDataFirst) {
TEST_P(PipeTest, ReaderSideCloses) {
SKIP_IF(!CreateBlocking());
+ const auto signal_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGPIPE));
+
ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
int buf = kTestValue;
EXPECT_THAT(write(wfd_.get(), &buf, sizeof(buf)),
SyscallFailsWithErrno(EPIPE));
+
+ WaitForSignalDelivery(absl::Seconds(1), 1);
+ ASSERT_EQ(global_num_signals_received, 1);
}
TEST_P(PipeTest, CloseTwice) {
@@ -355,6 +391,9 @@ TEST_P(PipeTest, CloseTwice) {
TEST_P(PipeTest, BlockWriteClosed) {
SKIP_IF(!CreateBlocking());
+ const auto signal_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGPIPE));
+
absl::Notification notify;
ScopedThread t([this, &notify]() {
std::vector<char> buf(Size());
@@ -371,6 +410,10 @@ TEST_P(PipeTest, BlockWriteClosed) {
notify.WaitForNotification();
ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
+
+ WaitForSignalDelivery(absl::Seconds(1), 1);
+ ASSERT_EQ(global_num_signals_received, 1);
+
t.Join();
}
@@ -379,6 +422,9 @@ TEST_P(PipeTest, BlockWriteClosed) {
TEST_P(PipeTest, BlockPartialWriteClosed) {
SKIP_IF(!CreateBlocking());
+ const auto signal_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGPIPE));
+
ScopedThread t([this]() {
const int pipe_size = Size();
std::vector<char> buf(2 * pipe_size);
@@ -396,6 +442,10 @@ TEST_P(PipeTest, BlockPartialWriteClosed) {
// Unblock the above.
ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
+
+ WaitForSignalDelivery(absl::Seconds(1), 2);
+ ASSERT_EQ(global_num_signals_received, 2);
+
t.Join();
}
diff --git a/test/syscalls/linux/poll.cc b/test/syscalls/linux/poll.cc
index 5ce7e8c8d..ccd084244 100644
--- a/test/syscalls/linux/poll.cc
+++ b/test/syscalls/linux/poll.cc
@@ -116,7 +116,7 @@ void BlockingReadableTest(int16_t mask) {
});
notify.WaitForNotification();
- absl::SleepFor(absl::Seconds(1.0));
+ absl::SleepFor(absl::Seconds(1));
// Write some data to the pipe.
char s[] = "foo\n";
@@ -221,7 +221,7 @@ TEST_F(PollTest, BlockingEventPOLLHUP) {
});
notify.WaitForNotification();
- absl::SleepFor(absl::Seconds(1.0));
+ absl::SleepFor(absl::Seconds(1));
// Write some data and close the writer fd.
fd1.reset();
diff --git a/test/syscalls/linux/prctl.cc b/test/syscalls/linux/prctl.cc
index 19a57d353..25b0e63d4 100644
--- a/test/syscalls/linux/prctl.cc
+++ b/test/syscalls/linux/prctl.cc
@@ -101,11 +101,11 @@ TEST(PrctlTest, NoNewPrivsPreservedAcrossCloneForkAndExecve) {
int no_new_privs;
ASSERT_THAT(no_new_privs = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceeds());
- ScopedThread([] {
+ ScopedThread thread = ScopedThread([] {
ASSERT_THAT(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), SyscallSucceeds());
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(1));
- ScopedThread([] {
+ ScopedThread threadInner = ScopedThread([] {
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(1));
// Note that these ASSERT_*s failing will only return from this thread,
@@ -129,9 +129,11 @@ TEST(PrctlTest, NoNewPrivsPreservedAcrossCloneForkAndExecve) {
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(1));
});
+ threadInner.Join();
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(1));
});
+ thread.Join();
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(no_new_privs));
}
@@ -141,7 +143,7 @@ TEST(PrctlTest, PDeathSig) {
// Make the new process' parent a separate thread since the parent death
// signal fires when the parent *thread* exits.
- ScopedThread([&] {
+ ScopedThread thread = ScopedThread([&] {
child_pid = fork();
TEST_CHECK(child_pid >= 0);
if (child_pid == 0) {
@@ -172,6 +174,7 @@ TEST(PrctlTest, PDeathSig) {
// Suppress the SIGSTOP and detach from the child.
ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
});
+ thread.Join();
// The child should have been killed by its parent death SIGKILL.
int status;
diff --git a/test/syscalls/linux/priority.cc b/test/syscalls/linux/priority.cc
index 1d9bdfa70..e381248e4 100644
--- a/test/syscalls/linux/priority.cc
+++ b/test/syscalls/linux/priority.cc
@@ -72,7 +72,8 @@ TEST(SetpriorityTest, Implemented) {
// No need to clear errno for setpriority():
// "The setpriority() call returns 0 if there is no error, or -1 if there is"
- EXPECT_THAT(setpriority(PRIO_PROCESS, /*who=*/0, /*nice=*/16),
+ EXPECT_THAT(setpriority(PRIO_PROCESS, /*who=*/0,
+ /*nice=*/16), // NOLINT(bugprone-argument-comment)
SyscallSucceeds());
}
@@ -80,7 +81,8 @@ TEST(SetpriorityTest, Implemented) {
TEST(Setpriority, InvalidWhich) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
- EXPECT_THAT(setpriority(/*which=*/3, /*who=*/0, /*nice=*/16),
+ EXPECT_THAT(setpriority(/*which=*/3, /*who=*/0,
+ /*nice=*/16), // NOLINT(bugprone-argument-comment)
SyscallFailsWithErrno(EINVAL));
}
@@ -88,7 +90,8 @@ TEST(Setpriority, InvalidWhich) {
TEST(SetpriorityTest, ValidWho) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/16),
+ EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(),
+ /*nice=*/16), // NOLINT(bugprone-argument-comment)
SyscallSucceeds());
}
@@ -142,22 +145,26 @@ TEST(SetpriorityTest, OutsideRange) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
// Set niceval > 19
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/100),
+ EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(),
+ /*nice=*/100), // NOLINT(bugprone-argument-comment)
SyscallSucceeds());
errno = 0;
// Test niceval truncated to 19
EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
- SyscallSucceedsWithValue(/*maxnice=*/19));
+ SyscallSucceedsWithValue(
+ /*maxnice=*/19)); // NOLINT(bugprone-argument-comment)
// Set niceval < -20
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/-100),
+ EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(),
+ /*nice=*/-100), // NOLINT(bugprone-argument-comment)
SyscallSucceeds());
errno = 0;
// Test niceval truncated to -20
EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
- SyscallSucceedsWithValue(/*minnice=*/-20));
+ SyscallSucceedsWithValue(
+ /*minnice=*/-20)); // NOLINT(bugprone-argument-comment)
}
// Process is not found when which=PRIO_PROCESS
@@ -167,7 +174,7 @@ TEST(SetpriorityTest, InvalidWho) {
// Flaky, but it's tough to avoid a race condition when finding an unused pid
EXPECT_THAT(setpriority(PRIO_PROCESS,
/*who=*/INT_MAX - 1,
- /*nice=*/16),
+ /*nice=*/16), // NOLINT(bugprone-argument-comment)
SyscallFailsWithErrno(ESRCH));
}
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index 24928d876..78aa73edc 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -2364,6 +2364,15 @@ TEST(ProcSysKernelHostname, MatchesUname) {
EXPECT_EQ(procfs_hostname, hostname);
}
+TEST(ProcSysVmMaxmapCount, HasNumericValue) {
+ const std::string val_str =
+ ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/vm/max_map_count"));
+ int32_t val;
+ EXPECT_TRUE(absl::SimpleAtoi(val_str, &val))
+ << "/proc/sys/vm/max_map_count does not contain a numeric value: "
+ << val_str;
+}
+
TEST(ProcSysVmMmapMinAddr, HasNumericValue) {
const std::string mmap_min_addr_str =
ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/vm/mmap_min_addr"));
diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc
index 8d15c491e..5ff1f12a0 100644
--- a/test/syscalls/linux/pty.cc
+++ b/test/syscalls/linux/pty.cc
@@ -40,6 +40,7 @@
#include "test/util/file_descriptor.h"
#include "test/util/posix_error.h"
#include "test/util/pty_util.h"
+#include "test/util/signal_util.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
@@ -387,6 +388,22 @@ PosixErrorOr<size_t> PollAndReadFd(int fd, void* buf, size_t count,
}
TEST(PtyTrunc, Truncate) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ // setsid either puts us in a new session or fails because we're already the
+ // session leader. Either way, this ensures we're the session leader and have
+ // no controlling terminal.
+ ASSERT_THAT(setsid(), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EPERM)));
+
+ // Make sure we're ignoring SIGHUP, which will be sent to this process once we
+ // disconnect the TTY.
+ struct sigaction sa = {};
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ const Cleanup cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGHUP, sa));
+
// Opening PTYs with O_TRUNC shouldn't cause an error, but calls to
// (f)truncate should.
FileDescriptor master =
@@ -395,6 +412,7 @@ TEST(PtyTrunc, Truncate) {
std::string spath = absl::StrCat("/dev/pts/", n);
FileDescriptor replica =
ASSERT_NO_ERRNO_AND_VALUE(Open(spath, O_RDWR | O_NONBLOCK | O_TRUNC));
+ ASSERT_THAT(ioctl(replica.get(), TIOCNOTTY), SyscallSucceeds());
EXPECT_THAT(truncate(kMasterPath, 0), SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(truncate(spath.c_str(), 0), SyscallFailsWithErrno(EINVAL));
@@ -464,10 +482,10 @@ TEST(BasicPtyTest, OpenSetsControllingTTY) {
SKIP_IF(IsRunningWithVFS1());
// setsid either puts us in a new session or fails because we're already the
// session leader. Either way, this ensures we're the session leader.
- setsid();
+ ASSERT_THAT(setsid(), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EPERM)));
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sa = {};
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
@@ -491,7 +509,7 @@ TEST(BasicPtyTest, OpenMasterDoesNotSetsControllingTTY) {
SKIP_IF(IsRunningWithVFS1());
// setsid either puts us in a new session or fails because we're already the
// session leader. Either way, this ensures we're the session leader.
- setsid();
+ ASSERT_THAT(setsid(), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EPERM)));
FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR));
// Opening master does not set the controlling TTY, and therefore we are
@@ -503,7 +521,7 @@ TEST(BasicPtyTest, OpenNOCTTY) {
SKIP_IF(IsRunningWithVFS1());
// setsid either puts us in a new session or fails because we're already the
// session leader. Either way, this ensures we're the session leader.
- setsid();
+ ASSERT_THAT(setsid(), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EPERM)));
FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR));
FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(
OpenReplica(master, O_NOCTTY | O_NONBLOCK | O_RDWR));
@@ -1405,7 +1423,7 @@ TEST_F(JobControlTest, ReleaseTTY) {
ASSERT_THAT(ioctl(replica_.get(), TIOCSCTTY, 0), SyscallSucceeds());
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sa = {};
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
@@ -1526,7 +1544,7 @@ TEST_F(JobControlTest, ReleaseTTYSignals) {
EXPECT_THAT(setpgid(diff_pgrp_child, diff_pgrp_child), SyscallSucceeds());
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sighup_sa = {};
sighup_sa.sa_handler = SIG_IGN;
sighup_sa.sa_flags = 0;
diff --git a/test/syscalls/linux/read.cc b/test/syscalls/linux/read.cc
index 7056342d7..7756af24d 100644
--- a/test/syscalls/linux/read.cc
+++ b/test/syscalls/linux/read.cc
@@ -157,7 +157,8 @@ TEST_F(ReadTest, PartialReadSIGSEGV) {
.iov_len = size,
},
};
- EXPECT_THAT(preadv(fd.get(), iov, ABSL_ARRAYSIZE(iov), 0),
+ EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceeds());
+ EXPECT_THAT(readv(fd.get(), iov, ABSL_ARRAYSIZE(iov)),
SyscallSucceedsWithValue(size));
}
diff --git a/test/syscalls/linux/rename.cc b/test/syscalls/linux/rename.cc
index 76a8da65f..561eed99f 100644
--- a/test/syscalls/linux/rename.cc
+++ b/test/syscalls/linux/rename.cc
@@ -26,6 +26,8 @@
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
+using ::testing::AnyOf;
+
namespace gvisor {
namespace testing {
@@ -438,6 +440,60 @@ TEST(RenameTest, SysfsDirectoryToSelf) {
EXPECT_THAT(rename(path.c_str(), path.c_str()), SyscallSucceeds());
}
+#ifndef SYS_renameat2
+#if defined(__x86_64__)
+#define SYS_renameat2 316
+#elif defined(__aarch64__)
+#define SYS_renameat2 276
+#else
+#error "Unknown architecture"
+#endif
+#endif // SYS_renameat2
+
+#ifndef RENAME_NOREPLACE
+#define RENAME_NOREPLACE (1 << 0)
+#endif // RENAME_NOREPLACE
+
+int renameat2(int olddirfd, const char* oldpath, int newdirfd,
+ const char* newpath, unsigned int flags) {
+ return syscall(SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, flags);
+}
+
+TEST(Renameat2Test, NoReplaceSuccess) {
+ auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ std::string const newpath = NewTempAbsPath();
+ // renameat2 may fail with ENOSYS (if the syscall is unsupported) or EINVAL
+ // (if flags are unsupported), or succeed (if RENAME_NOREPLACE is operating
+ // correctly).
+ EXPECT_THAT(
+ renameat2(AT_FDCWD, f.path().c_str(), AT_FDCWD, newpath.c_str(),
+ RENAME_NOREPLACE),
+ AnyOf(SyscallFailsWithErrno(AnyOf(ENOSYS, EINVAL)), SyscallSucceeds()));
+}
+
+TEST(Renameat2Test, NoReplaceExisting) {
+ auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ auto f2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ // renameat2 may fail with ENOSYS (if the syscall is unsupported), EINVAL (if
+ // flags are unsupported), or EEXIST (if RENAME_NOREPLACE is operating
+ // correctly).
+ EXPECT_THAT(renameat2(AT_FDCWD, f1.path().c_str(), AT_FDCWD,
+ f2.path().c_str(), RENAME_NOREPLACE),
+ SyscallFailsWithErrno(AnyOf(ENOSYS, EINVAL, EEXIST)));
+}
+
+TEST(Renameat2Test, NoReplaceDot) {
+ auto d1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ auto d2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ // renameat2 may fail with ENOSYS (if the syscall is unsupported), EINVAL (if
+ // flags are unsupported), or EEXIST (if RENAME_NOREPLACE is operating
+ // correctly).
+ EXPECT_THAT(
+ renameat2(AT_FDCWD, d1.path().c_str(), AT_FDCWD,
+ absl::StrCat(d2.path(), "/.").c_str(), RENAME_NOREPLACE),
+ SyscallFailsWithErrno(AnyOf(ENOSYS, EINVAL, EEXIST)));
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc
index 2ce8f836c..f72957f89 100644
--- a/test/syscalls/linux/semaphore.cc
+++ b/test/syscalls/linux/semaphore.cc
@@ -49,6 +49,9 @@ constexpr int kSemAem = 32767;
class AutoSem {
public:
+ // Creates a new private semaphore.
+ AutoSem() : id_(semget(IPC_PRIVATE, 1, 0)) {}
+
explicit AutoSem(int id) : id_(id) {}
~AutoSem() {
if (id_ >= 0) {
@@ -101,6 +104,20 @@ TEST(SemaphoreTest, SemGet) {
EXPECT_NE(sem3.get(), sem2.get());
}
+// Tests system-wide limits for semget.
+TEST(SemaphoreTest, SemGetSystemLimits) {
+ // Disable save so that we don't trigger save/restore too many times.
+ const DisableSave ds;
+
+ // Exceed number of semaphores per set.
+ EXPECT_THAT(semget(IPC_PRIVATE, kSemMsl + 1, 0),
+ SyscallFailsWithErrno(EINVAL));
+
+ // Exceed system-wide limit for semaphore sets by 1.
+ AutoSem sems[kSemMni];
+ EXPECT_THAT(semget(IPC_PRIVATE, 1, 0), SyscallFailsWithErrno(ENOSPC));
+}
+
// Tests simple operations that shouldn't block in a single-thread.
TEST(SemaphoreTest, SemOpSingleNoBlock) {
AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
diff --git a/test/syscalls/linux/setgid.cc b/test/syscalls/linux/setgid.cc
index ce61bc36d..6278c4fab 100644
--- a/test/syscalls/linux/setgid.cc
+++ b/test/syscalls/linux/setgid.cc
@@ -115,8 +115,6 @@ class SetgidDirTest : public ::testing::Test {
void SetUp() override {
original_gid_ = getegid();
- SKIP_IF(IsRunningWithVFS1());
-
// If we can't find two usable groups, we're in an unsupporting environment.
// Skip the test.
PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups();
@@ -294,8 +292,8 @@ TEST_F(SetgidDirTest, ChownFileClears) {
EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), 0);
}
-// Chowning a file with setgid enabled, but not the group exec bit, does not
-// clear the setgid bit. Such files are mandatory locked.
+// Chowning a file with setgid enabled, but not the group exec bit, clears the
+// setuid bit and not the setgid bit. Such files are mandatory locked.
TEST_F(SetgidDirTest, ChownNoExecFileDoesNotClear) {
// Set group to G1, create a directory, and enable setgid.
auto g1owned = JoinPath(temp_dir_.path(), "g1owned/");
@@ -345,7 +343,6 @@ struct FileModeTestcase {
class FileModeTest : public ::testing::TestWithParam<FileModeTestcase> {};
TEST_P(FileModeTest, WriteToFile) {
- SKIP_IF(IsRunningWithVFS1());
PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups();
SKIP_IF(!groups.ok());
@@ -372,7 +369,6 @@ TEST_P(FileModeTest, WriteToFile) {
}
TEST_P(FileModeTest, TruncateFile) {
- SKIP_IF(IsRunningWithVFS1());
PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups();
SKIP_IF(!groups.ok());
diff --git a/test/syscalls/linux/sigstop.cc b/test/syscalls/linux/sigstop.cc
index b2fcedd62..8de03b4dc 100644
--- a/test/syscalls/linux/sigstop.cc
+++ b/test/syscalls/linux/sigstop.cc
@@ -123,6 +123,55 @@ void SleepIgnoreStopped(absl::Duration d) {
}
}
+TEST(SigstopTest, RestartSyscall) {
+ pid_t pid;
+ constexpr absl::Duration kStopDelay = absl::Seconds(5);
+ constexpr absl::Duration kSleepDelay = absl::Seconds(15);
+ constexpr absl::Duration kStartupDelay = absl::Seconds(5);
+ constexpr absl::Duration kErrorDelay = absl::Seconds(3);
+
+ const DisableSave ds; // Timing-related.
+
+ pid = fork();
+ if (pid == 0) {
+ struct timespec ts = {.tv_sec = kSleepDelay / absl::Seconds(1)};
+ auto start = absl::Now();
+ TEST_CHECK(nanosleep(&ts, nullptr) == 0);
+ auto finish = absl::Now();
+ // Check that time spent stopped is counted as time spent sleeping.
+ TEST_CHECK(finish - start >= kSleepDelay);
+ TEST_CHECK(finish - start < kSleepDelay + kErrorDelay);
+ _exit(kChildMainThreadExitCode);
+ }
+ ASSERT_THAT(pid, SyscallSucceeds());
+
+ // Wait for the child subprocess to start sleeping before stopping it.
+ absl::SleepFor(kStartupDelay);
+ ASSERT_THAT(kill(pid, SIGSTOP), SyscallSucceeds());
+ int status;
+ EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, WUNTRACED),
+ SyscallSucceedsWithValue(pid));
+ EXPECT_TRUE(WIFSTOPPED(status));
+ EXPECT_EQ(SIGSTOP, WSTOPSIG(status));
+
+ // Sleep for shorter than the sleep in the child subprocess.
+ absl::SleepFor(kStopDelay);
+ ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, WNOHANG),
+ SyscallSucceedsWithValue(0));
+
+ // Resume the child.
+ ASSERT_THAT(kill(pid, SIGCONT), SyscallSucceeds());
+
+ EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, WCONTINUED),
+ SyscallSucceedsWithValue(pid));
+ EXPECT_TRUE(WIFCONTINUED(status));
+
+ // Expect it to die.
+ ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceeds());
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), kChildMainThreadExitCode);
+}
+
void RunChild() {
// Start another thread that attempts to call exit_group with a different
// error code, in order to verify that SIGSTOP stops this thread as well.
diff --git a/test/syscalls/linux/socket_bind_to_device_util.cc b/test/syscalls/linux/socket_bind_to_device_util.cc
index f4ee775bd..ce5f63938 100644
--- a/test/syscalls/linux/socket_bind_to_device_util.cc
+++ b/test/syscalls/linux/socket_bind_to_device_util.cc
@@ -58,8 +58,10 @@ PosixErrorOr<std::unique_ptr<Tunnel>> Tunnel::New(string tunnel_name) {
}
std::unordered_set<string> GetInterfaceNames() {
- struct if_nameindex* interfaces = if_nameindex();
std::unordered_set<string> names;
+#ifndef ANDROID
+ // Android does not support if_nameindex in r22.
+ struct if_nameindex* interfaces = if_nameindex();
if (interfaces == nullptr) {
return names;
}
@@ -68,6 +70,7 @@ std::unordered_set<string> GetInterfaceNames() {
names.insert(interface->if_name);
}
if_freenameindex(interfaces);
+#endif
return names;
}
diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc
index 9a6b089f6..6b369d5b7 100644
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ b/test/syscalls/linux/socket_inet_loopback.cc
@@ -368,7 +368,8 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdown) {
TestAddress const& connector = param.connector;
constexpr int kBacklog = 2;
- constexpr int kFDs = kBacklog + 1;
+ // See the comment in TCPBacklog for why this isn't kBacklog + 1.
+ constexpr int kFDs = kBacklog;
// Create the listening socket.
FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
@@ -454,6 +455,8 @@ TEST_P(SocketInetLoopbackTest, TCPListenClose) {
uint16_t const port =
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
+ // Connect repeatedly, keeping each connection open. After kBacklog
+ // connections, we'll start getting EINPROGRESS.
sockaddr_storage conn_addr = connector.addr;
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
std::vector<FileDescriptor> clients;
@@ -472,6 +475,77 @@ TEST_P(SocketInetLoopbackTest, TCPListenClose) {
}
}
+// Test the protocol state information returned by TCPINFO.
+TEST_P(SocketInetLoopbackTest, TCPInfoState) {
+ auto const& param = GetParam();
+ TestAddress const& listener = param.listener;
+ TestAddress const& connector = param.connector;
+
+ // Create the listening socket.
+ FileDescriptor const listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
+
+ auto state = [](int fd) -> int {
+ struct tcp_info opt = {};
+ socklen_t optLen = sizeof(opt);
+ EXPECT_THAT(getsockopt(fd, SOL_TCP, TCP_INFO, &opt, &optLen),
+ SyscallSucceeds());
+ return opt.tcpi_state;
+ };
+ ASSERT_EQ(state(listen_fd.get()), TCP_CLOSE);
+
+ sockaddr_storage listen_addr = listener.addr;
+ ASSERT_THAT(
+ bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len),
+ SyscallSucceeds());
+ ASSERT_EQ(state(listen_fd.get()), TCP_CLOSE);
+
+ ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
+ ASSERT_EQ(state(listen_fd.get()), TCP_LISTEN);
+
+ // Get the port bound by the listening socket.
+ socklen_t addrlen = listener.addr_len;
+ ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen),
+ SyscallSucceeds());
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
+
+ // Connect to the listening socket.
+ FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
+ sockaddr_storage conn_addr = connector.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+ ASSERT_EQ(state(conn_fd.get()), TCP_CLOSE);
+ ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
+ connector.addr_len),
+ SyscallSucceeds());
+ ASSERT_EQ(state(conn_fd.get()), TCP_ESTABLISHED);
+
+ auto accepted =
+ ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
+ ASSERT_EQ(state(accepted.get()), TCP_ESTABLISHED);
+
+ ASSERT_THAT(close(accepted.release()), SyscallSucceeds());
+
+ struct pollfd pfd = {
+ .fd = conn_fd.get(),
+ .events = POLLIN | POLLRDHUP,
+ };
+ constexpr int kTimeout = 10000;
+ int n = poll(&pfd, 1, kTimeout);
+ ASSERT_GE(n, 0) << strerror(errno);
+ ASSERT_EQ(n, 1);
+ if (IsRunningOnGvisor() && GvisorPlatform() != Platform::kFuchsia) {
+ // TODO(gvisor.dev/issue/6015): Notify POLLRDHUP on incoming FIN.
+ ASSERT_EQ(pfd.revents, POLLIN);
+ } else {
+ ASSERT_EQ(pfd.revents, POLLIN | POLLRDHUP);
+ }
+
+ ASSERT_THAT(state(conn_fd.get()), TCP_CLOSE_WAIT);
+ ASSERT_THAT(close(conn_fd.release()), SyscallSucceeds());
+}
+
void TestHangupDuringConnect(const TestParam& param,
void (*hangup)(FileDescriptor&)) {
TestAddress const& listener = param.listener;
@@ -537,6 +611,8 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownDuringConnect) {
void TestListenHangupConnectingRead(const TestParam& param,
void (*hangup)(FileDescriptor&)) {
+ constexpr int kTimeout = 10000;
+
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -566,14 +642,33 @@ void TestListenHangupConnectingRead(const TestParam& param,
sockaddr_storage conn_addr = connector.addr;
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
FileDescriptor established_client = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(connect(established_client.get(), AsSockAddr(&conn_addr),
- connector.addr_len),
- SyscallSucceeds());
+ Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
+ int ret = connect(established_client.get(), AsSockAddr(&conn_addr),
+ connector.addr_len);
+ if (ret != 0) {
+ EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS));
+ }
- // Ensure that the accept queue has the completed connection.
- constexpr int kTimeout = 10000;
+ // On some kernels a backlog of 0 means no backlog, while on others it means a
+ // backlog of 1. See commit c609e6aae4efcf383fe86b195d1b060befcb3666 for more
+ // explanation.
+ //
+ // If we timeout connecting to loopback, we're on a kernel with no backlog.
pollfd pfd = {
+ .fd = established_client.get(),
+ .events = POLLIN | POLLOUT,
+ };
+ if (!poll(&pfd, 1, kTimeout)) {
+ // We're on one of those kernels. It should be impossible to establish the
+ // connection, so connect will always return EALREADY.
+ EXPECT_THAT(connect(established_client.get(), AsSockAddr(&conn_addr),
+ connector.addr_len),
+ SyscallFailsWithErrno(EALREADY));
+ return;
+ }
+
+ // Ensure that the accept queue has the completed connection.
+ pfd = {
.fd = listen_fd.get(),
.events = POLLIN,
};
@@ -583,8 +678,8 @@ void TestListenHangupConnectingRead(const TestParam& param,
FileDescriptor connecting_client = ASSERT_NO_ERRNO_AND_VALUE(
Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
// Keep the last client in connecting state.
- int ret = connect(connecting_client.get(), AsSockAddr(&conn_addr),
- connector.addr_len);
+ ret = connect(connecting_client.get(), AsSockAddr(&conn_addr),
+ connector.addr_len);
if (ret != 0) {
EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS));
}
@@ -621,6 +716,78 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownConnectingRead) {
});
}
+// Test close of a non-blocking connecting socket.
+TEST_P(SocketInetLoopbackTest, TCPNonBlockingConnectClose) {
+ TestParam const& param = GetParam();
+ TestAddress const& listener = param.listener;
+ TestAddress const& connector = param.connector;
+
+ // Create the listening socket.
+ FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(listener.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
+ sockaddr_storage listen_addr = listener.addr;
+ ASSERT_THAT(
+ bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(listen_fd.get(), 0), SyscallSucceeds());
+
+ // Get the port bound by the listening socket.
+ socklen_t addrlen = listener.addr_len;
+ ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen),
+ SyscallSucceeds());
+ ASSERT_EQ(addrlen, listener.addr_len);
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
+
+ sockaddr_storage conn_addr = connector.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+
+ // Try many iterations to catch a race with socket close and handshake
+ // completion.
+ for (int i = 0; i < 1000; ++i) {
+ FileDescriptor client = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
+ ASSERT_THAT(
+ connect(client.get(), AsSockAddr(&conn_addr), connector.addr_len),
+ SyscallFailsWithErrno(EINPROGRESS));
+ ASSERT_THAT(close(client.release()), SyscallSucceeds());
+
+ // Accept any connections and check if they were closed from the peer. Not
+ // all client connects would result in an acceptable connection as the
+ // client handshake might never complete if the socket close was processed
+ // sooner than the non-blocking connect OR the accept queue is full. We are
+ // only interested in the case where we do have an acceptable completed
+ // connection. The accept is non-blocking here, which means that at the time
+ // of listener close (after the loop ends), we could still have a completed
+ // connection (from connect of any previous iteration) in the accept queue.
+ // The listener close would clean up the accept queue.
+ int accepted_fd;
+ ASSERT_THAT(accepted_fd = accept(listen_fd.get(), nullptr, nullptr),
+ AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EWOULDBLOCK)));
+ if (accepted_fd < 0) {
+ continue;
+ }
+ FileDescriptor accepted(accepted_fd);
+ struct pollfd pfd = {
+ .fd = accepted.get(),
+ .events = POLLIN | POLLRDHUP,
+ };
+ // Use a large timeout to accomodate for retransmitted FINs.
+ constexpr int kTimeout = 30000;
+ int n = poll(&pfd, 1, kTimeout);
+ ASSERT_GE(n, 0) << strerror(errno);
+ ASSERT_EQ(n, 1);
+
+ if (IsRunningOnGvisor() && GvisorPlatform() != Platform::kFuchsia) {
+ // TODO(gvisor.dev/issue/6015): Notify POLLRDHUP on incoming FIN.
+ ASSERT_EQ(pfd.revents, POLLIN);
+ } else {
+ ASSERT_EQ(pfd.revents, POLLIN | POLLRDHUP);
+ }
+ ASSERT_THAT(close(accepted.release()), SyscallSucceeds());
+ }
+}
+
// TODO(b/157236388): Remove once bug is fixed. Test fails w/
// random save as established connections which can't be delivered to the accept
// queue because the queue is full are not correctly delivered after restore
@@ -652,7 +819,8 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptBacklogSizes) {
if (backlog < 0) {
expected_accepts = 1024;
} else {
- expected_accepts = backlog + 1;
+ // See the comment in TCPBacklog for why this isn't backlog + 1.
+ expected_accepts = backlog;
}
for (int i = 0; i < expected_accepts; i++) {
SCOPED_TRACE(absl::StrCat("i=", i));
@@ -753,7 +921,11 @@ TEST_P(SocketInetLoopbackTest, TCPBacklog) {
// enqueuing established connections to the accept queue, newer SYNs could
// still be replied to causing those client connections would be accepted as
// we start dequeuing the queue.
- ASSERT_GE(accepted_conns, kBacklogSize + 1);
+ //
+ // On some kernels this can value can be off by one, so we don't add 1 to
+ // kBacklogSize. See commit c609e6aae4efcf383fe86b195d1b060befcb3666 for more
+ // explanation.
+ ASSERT_GE(accepted_conns, kBacklogSize);
ASSERT_GE(client_conns, accepted_conns);
}
@@ -788,7 +960,9 @@ TEST_P(SocketInetLoopbackTest, TCPBacklogAcceptAll) {
// Fill up the accept queue and trigger more client connections which would be
// waiting to be accepted.
- std::array<FileDescriptor, kBacklog + 1> established_clients;
+ //
+ // See the comment in TCPBacklog for why this isn't backlog + 1.
+ std::array<FileDescriptor, kBacklog> established_clients;
for (auto& fd : established_clients) {
fd = ASSERT_NO_ERRNO_AND_VALUE(
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc
index 59b56dc1a..2f5743cda 100644
--- a/test/syscalls/linux/socket_ip_tcp_generic.cc
+++ b/test/syscalls/linux/socket_ip_tcp_generic.cc
@@ -1155,7 +1155,7 @@ TEST_P(TCPSocketPairTest, IpMulticastLoopDefault) {
TEST_P(TCPSocketPairTest, TCPResetDuringClose) {
DisableSave ds; // Too many syscalls.
- constexpr int kThreadCount = 1000;
+ constexpr int kThreadCount = 100;
std::unique_ptr<ScopedThread> instances[kThreadCount];
for (int i = 0; i < kThreadCount; i++) {
instances[i] = absl::make_unique<ScopedThread>([&]() {
diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc
index 8390f7c3b..09f070797 100644
--- a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc
+++ b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc
@@ -38,8 +38,8 @@ TEST_P(IPv6UDPUnboundExternalNetworkingSocketTest, TestJoinLeaveMulticast) {
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")),
+ .ipv6mr_interface = static_cast<decltype(ipv6_mreq::ipv6mr_interface)>(
+ ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"))),
};
ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
&group_req, sizeof(group_req)),
diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc
index 9e3a129cf..83c33ec8d 100644
--- a/test/syscalls/linux/socket_test_util.cc
+++ b/test/syscalls/linux/socket_test_util.cc
@@ -15,6 +15,7 @@
#include "test/syscalls/linux/socket_test_util.h"
#include <arpa/inet.h>
+#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
@@ -84,7 +85,8 @@ Creator<SocketPair> AcceptBindSocketPairCreator(bool abstract, int domain,
RETURN_ERROR_IF_SYSCALL_FAIL(
bind(bound, AsSockAddr(&bind_addr), sizeof(bind_addr)));
MaybeSave(); // Successful bind.
- RETURN_ERROR_IF_SYSCALL_FAIL(listen(bound, /* backlog = */ 5));
+ RETURN_ERROR_IF_SYSCALL_FAIL(
+ listen(bound, /* backlog = */ 5)); // NOLINT(bugprone-argument-comment)
MaybeSave(); // Successful listen.
int connected;
@@ -315,7 +317,8 @@ PosixErrorOr<T> BindIP(int fd, bool dual_stack) {
template <typename T>
PosixErrorOr<T> TCPBindAndListen(int fd, bool dual_stack) {
ASSIGN_OR_RETURN_ERRNO(T addr, BindIP<T>(fd, dual_stack));
- RETURN_ERROR_IF_SYSCALL_FAIL(listen(fd, /* backlog = */ 5));
+ RETURN_ERROR_IF_SYSCALL_FAIL(
+ listen(fd, /* backlog = */ 5)); // NOLINT(bugprone-argument-comment)
return addr;
}
@@ -798,84 +801,82 @@ TestAddress TestAddress::WithPort(uint16_t port) const {
return addr;
}
-TestAddress V4Any() {
- TestAddress t("V4Any");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = htonl(INADDR_ANY);
- return t;
-}
+namespace {
-TestAddress V4Loopback() {
- TestAddress t("V4Loopback");
+TestAddress V4Addr(std::string description, in_addr_t addr) {
+ TestAddress t(std::move(description));
t.addr.ss_family = AF_INET;
t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- htonl(INADDR_LOOPBACK);
+ reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = addr;
return t;
}
-TestAddress V4MappedAny() {
- TestAddress t("V4MappedAny");
+TestAddress V6Addr(std::string description, const in6_addr& addr) {
+ TestAddress t(std::move(description));
t.addr.ss_family = AF_INET6;
t.addr_len = sizeof(sockaddr_in6);
- inet_pton(AF_INET6, "::ffff:0.0.0.0",
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr);
+ reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = addr;
return t;
}
+} // namespace
+
+TestAddress V4AddrStr(std::string description, const char* addr) {
+ in_addr_t s_addr;
+ inet_pton(AF_INET, addr, &s_addr);
+ return V4Addr(description, s_addr);
+}
+
+TestAddress V6AddrStr(std::string description, const char* addr) {
+ struct in6_addr s_addr;
+ inet_pton(AF_INET6, addr, &s_addr);
+ return V6Addr(description, s_addr);
+}
+
+TestAddress V4Any() { return V4Addr("V4Any", htonl(INADDR_ANY)); }
+
+TestAddress V4Broadcast() {
+ return V4Addr("V4Broadcast", htonl(INADDR_BROADCAST));
+}
+
+TestAddress V4Loopback() {
+ return V4Addr("V4Loopback", htonl(INADDR_LOOPBACK));
+}
+
+TestAddress V4LoopbackSubnetBroadcast() {
+ return V4AddrStr("V4LoopbackSubnetBroadcast", "127.255.255.255");
+}
+
+TestAddress V4MappedAny() { return V6AddrStr("V4MappedAny", "::ffff:0.0.0.0"); }
+
TestAddress V4MappedLoopback() {
- TestAddress t("V4MappedLoopback");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- inet_pton(AF_INET6, "::ffff:127.0.0.1",
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr);
- return t;
+ return V6AddrStr("V4MappedLoopback", "::ffff:127.0.0.1");
}
TestAddress V4Multicast() {
- TestAddress t("V4Multicast");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- inet_addr(kMulticastAddress);
- return t;
+ return V4Addr("V4Multicast", inet_addr(kMulticastAddress));
}
-TestAddress V4Broadcast() {
- TestAddress t("V4Broadcast");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- htonl(INADDR_BROADCAST);
- return t;
+TestAddress V4MulticastAllHosts() {
+ return V4Addr("V4MulticastAllHosts", htonl(INADDR_ALLHOSTS_GROUP));
}
-TestAddress V6Any() {
- TestAddress t("V6Any");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = in6addr_any;
- return t;
+TestAddress V6Any() { return V6Addr("V6Any", in6addr_any); }
+
+TestAddress V6Loopback() { return V6Addr("V6Loopback", in6addr_loopback); }
+
+TestAddress V6Multicast() { return V6AddrStr("V6Multicast", "ff05::1234"); }
+
+TestAddress V6MulticastInterfaceLocalAllNodes() {
+ return V6AddrStr("V6MulticastInterfaceLocalAllNodes", "ff01::1");
}
-TestAddress V6Loopback() {
- TestAddress t("V6Loopback");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = in6addr_loopback;
- return t;
+TestAddress V6MulticastLinkLocalAllNodes() {
+ return V6AddrStr("V6MulticastLinkLocalAllNodes", "ff02::1");
}
-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;
+TestAddress V6MulticastLinkLocalAllRouters() {
+ return V6AddrStr("V6MulticastLinkLocalAllRouters", "ff02::2");
}
// Checksum computes the internet checksum of a buffer.
diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h
index f7ba90130..76dc090e0 100644
--- a/test/syscalls/linux/socket_test_util.h
+++ b/test/syscalls/linux/socket_test_util.h
@@ -499,15 +499,45 @@ struct TestAddress {
constexpr char kMulticastAddress[] = "224.0.2.1";
constexpr char kBroadcastAddress[] = "255.255.255.255";
+// Returns a TestAddress with `addr` parsed as an IPv4 address described by
+// `description`.
+TestAddress V4AddrStr(std::string description, const char* addr);
+// Returns a TestAddress with `addr` parsed as an IPv6 address described by
+// `description`.
+TestAddress V6AddrStr(std::string description, const char* addr);
+
+// Returns a TestAddress for the IPv4 any address.
TestAddress V4Any();
+// Returns a TestAddress for the IPv4 limited broadcast address.
TestAddress V4Broadcast();
+// Returns a TestAddress for the IPv4 loopback address.
TestAddress V4Loopback();
+// Returns a TestAddress for the subnet broadcast of the IPv4 loopback address.
+TestAddress V4LoopbackSubnetBroadcast();
+// Returns a TestAddress for the IPv4-mapped IPv6 any address.
TestAddress V4MappedAny();
+// Returns a TestAddress for the IPv4-mapped IPv6 loopback address.
TestAddress V4MappedLoopback();
+// Returns a TestAddress for a IPv4 multicast address.
TestAddress V4Multicast();
+// Returns a TestAddress for the IPv4 all-hosts multicast group address.
+TestAddress V4MulticastAllHosts();
+
+// Returns a TestAddress for the IPv6 any address.
TestAddress V6Any();
+// Returns a TestAddress for the IPv6 loopback address.
TestAddress V6Loopback();
+// Returns a TestAddress for a IPv6 multicast address.
TestAddress V6Multicast();
+// Returns a TestAddress for the IPv6 interface-local all-nodes multicast group
+// address.
+TestAddress V6MulticastInterfaceLocalAllNodes();
+// Returns a TestAddress for the IPv6 link-local all-nodes multicast group
+// address.
+TestAddress V6MulticastLinkLocalAllNodes();
+// Returns a TestAddress for the IPv6 link-local all-routers multicast group
+// address.
+TestAddress V6MulticastLinkLocalAllRouters();
// Compute the internet checksum of an IP header.
uint16_t IPChecksum(struct iphdr ip);
diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc
index 93a98adb1..bc12dd4af 100644
--- a/test/syscalls/linux/timers.cc
+++ b/test/syscalls/linux/timers.cc
@@ -26,6 +26,7 @@
#include "absl/flags/flag.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
+#include "benchmark/benchmark.h"
#include "test/util/cleanup.h"
#include "test/util/logging.h"
#include "test/util/multiprocess_util.h"
@@ -92,6 +93,8 @@ TEST(TimerTest, ProcessKilledOnCPUSoftLimit) {
TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0);
MaybeSave();
for (;;) {
+ int x = 0;
+ benchmark::DoNotOptimize(x); // Don't optimize this loop away.
}
}
ASSERT_THAT(pid, SyscallSucceeds());
@@ -151,6 +154,8 @@ TEST(TimerTest, ProcessPingedRepeatedlyAfterCPUSoftLimit) {
TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0);
MaybeSave();
for (;;) {
+ int x = 0;
+ benchmark::DoNotOptimize(x); // Don't optimize this loop away.
}
}
ASSERT_THAT(pid, SyscallSucceeds());
@@ -197,6 +202,8 @@ TEST(TimerTest, ProcessKilledOnCPUHardLimit) {
TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0);
MaybeSave();
for (;;) {
+ int x = 0;
+ benchmark::DoNotOptimize(x); // Don't optimize this loop away.
}
}
ASSERT_THAT(pid, SyscallSucceeds());
diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc
index 29e174f71..2b687c198 100644
--- a/test/syscalls/linux/udp_socket.cc
+++ b/test/syscalls/linux/udp_socket.cc
@@ -791,14 +791,14 @@ TEST_P(UdpSocketTest, RecvErrorConnRefused) {
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));
+ std::vector<char> control_buf(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_control = control_buf.data();
msg.msg_controllen = control_buf_len;
msg.msg_name = reinterpret_cast<void*>(&remote);
msg.msg_namelen = addrlen_;
diff --git a/test/syscalls/linux/uidgid.cc b/test/syscalls/linux/uidgid.cc
index 4139a18d8..d95a3e010 100644
--- a/test/syscalls/linux/uidgid.cc
+++ b/test/syscalls/linux/uidgid.cc
@@ -170,7 +170,9 @@ TEST(UidGidRootTest, SetgidNotFromThreadGroupLeader) {
const gid_t gid = absl::GetFlag(FLAGS_scratch_gid1);
// NOTE(b/64676707): Do setgid in a separate thread so that we can test if
// info.si_pid is set correctly.
- ScopedThread([gid] { ASSERT_THAT(setgid(gid), SyscallSucceeds()); });
+ ScopedThread thread =
+ ScopedThread([gid] { ASSERT_THAT(setgid(gid), SyscallSucceeds()); });
+ thread.Join();
EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid));
#pragma pop_macro("allow_setgid")
diff --git a/test/syscalls/linux/uname.cc b/test/syscalls/linux/uname.cc
index 759ea4f53..c52abef5c 100644
--- a/test/syscalls/linux/uname.cc
+++ b/test/syscalls/linux/uname.cc
@@ -88,7 +88,7 @@ TEST(UnameTest, UnshareUTS) {
struct utsname init;
ASSERT_THAT(uname(&init), SyscallSucceeds());
- ScopedThread([&]() {
+ ScopedThread thread = ScopedThread([&]() {
EXPECT_THAT(unshare(CLONE_NEWUTS), SyscallSucceeds());
constexpr char kHostname[] = "wubbalubba";
@@ -97,6 +97,7 @@ TEST(UnameTest, UnshareUTS) {
char hostname[65];
EXPECT_THAT(gethostname(hostname, sizeof(hostname)), SyscallSucceeds());
});
+ thread.Join();
struct utsname after;
EXPECT_THAT(uname(&after), SyscallSucceeds());
diff --git a/test/syscalls/linux/verity_getdents.cc b/test/syscalls/linux/verity_getdents.cc
new file mode 100644
index 000000000..093595dd3
--- /dev/null
+++ b/test/syscalls/linux/verity_getdents.cc
@@ -0,0 +1,95 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <dirent.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/util/capability_util.h"
+#include "test/util/fs_util.h"
+#include "test/util/temp_path.h"
+#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+class GetDentsTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Verity is implemented in VFS2.
+ SKIP_IF(IsRunningWithVFS1());
+
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
+ // Mount a tmpfs file system, to be wrapped by a verity fs.
+ tmpfs_dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ ASSERT_THAT(mount("", tmpfs_dir_.path().c_str(), "tmpfs", 0, ""),
+ SyscallSucceeds());
+
+ // Create a new file in the tmpfs mount.
+ file_ = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(tmpfs_dir_.path(), kContents, 0777));
+ filename_ = Basename(file_.path());
+ }
+
+ TempPath tmpfs_dir_;
+ TempPath file_;
+ std::string filename_;
+};
+
+TEST_F(GetDentsTest, GetDents) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ std::vector<std::string> expect = {".", "..", filename_};
+ EXPECT_NO_ERRNO(DirContains(verity_dir, expect, /*exclude=*/{}));
+}
+
+TEST_F(GetDentsTest, Deleted) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()),
+ SyscallSucceeds());
+
+ EXPECT_THAT(DirContains(verity_dir, /*expect=*/{}, /*exclude=*/{}),
+ PosixErrorIs(EIO, ::testing::_));
+}
+
+TEST_F(GetDentsTest, Renamed) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ std::string new_file_name = "renamed-" + filename_;
+ EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(),
+ JoinPath(tmpfs_dir_.path(), new_file_name).c_str()),
+ SyscallSucceeds());
+
+ EXPECT_THAT(DirContains(verity_dir, /*expect=*/{}, /*exclude=*/{}),
+ PosixErrorIs(EIO, ::testing::_));
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/verity_ioctl.cc b/test/syscalls/linux/verity_ioctl.cc
index 822e16f3c..be91b23d0 100644
--- a/test/syscalls/linux/verity_ioctl.cc
+++ b/test/syscalls/linux/verity_ioctl.cc
@@ -28,40 +28,13 @@
#include "test/util/mount_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
namespace gvisor {
namespace testing {
namespace {
-#ifndef FS_IOC_ENABLE_VERITY
-#define FS_IOC_ENABLE_VERITY 1082156677
-#endif
-
-#ifndef FS_IOC_MEASURE_VERITY
-#define FS_IOC_MEASURE_VERITY 3221513862
-#endif
-
-#ifndef FS_VERITY_FL
-#define FS_VERITY_FL 1048576
-#endif
-
-#ifndef FS_IOC_GETFLAGS
-#define FS_IOC_GETFLAGS 2148034049
-#endif
-
-struct fsverity_digest {
- __u16 digest_algorithm;
- __u16 digest_size; /* input/output */
- __u8 digest[];
-};
-
-constexpr int kMaxDigestSize = 64;
-constexpr int kDefaultDigestSize = 32;
-constexpr char kContents[] = "foobarbaz";
-constexpr char kMerklePrefix[] = ".merkle.verity.";
-constexpr char kMerkleRootPrefix[] = ".merkleroot.verity.";
-
class IoctlTest : public ::testing::Test {
protected:
void SetUp() override {
@@ -85,80 +58,6 @@ class IoctlTest : public ::testing::Test {
std::string filename_;
};
-// Provide a function to convert bytes to hex string, since
-// absl::BytesToHexString does not seem to be compatible with golang
-// hex.DecodeString used in verity due to zero-padding.
-std::string BytesToHexString(uint8_t bytes[], int size) {
- std::stringstream ss;
- ss << std::hex;
- for (int i = 0; i < size; ++i) {
- ss << std::setw(2) << std::setfill('0') << static_cast<int>(bytes[i]);
- }
- return ss.str();
-}
-
-std::string MerklePath(absl::string_view path) {
- return JoinPath(Dirname(path),
- std::string(kMerklePrefix) + std::string(Basename(path)));
-}
-
-std::string MerkleRootPath(absl::string_view path) {
- return JoinPath(Dirname(path),
- std::string(kMerkleRootPrefix) + std::string(Basename(path)));
-}
-
-// Flip a random bit in the file represented by fd.
-PosixError FlipRandomBit(int fd, int size) {
- // Generate a random offset in the file.
- srand(time(nullptr));
- unsigned int seed = 0;
- int random_offset = rand_r(&seed) % size;
-
- // Read a random byte and flip a bit in it.
- char buf[1];
- RETURN_ERROR_IF_SYSCALL_FAIL(PreadFd(fd, buf, 1, random_offset));
- buf[0] ^= 1;
- RETURN_ERROR_IF_SYSCALL_FAIL(PwriteFd(fd, buf, 1, random_offset));
- return NoError();
-}
-
-// Mount a verity on the tmpfs and enable both the file and the direcotry. Then
-// mount a new verity with measured root hash.
-PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir,
- std::string filename) {
- // Mount a verity fs on the existing tmpfs mount.
- std::string mount_opts = "lower_path=" + tmpfs_dir;
- ASSIGN_OR_RETURN_ERRNO(TempPath verity_dir, TempPath::CreateDir());
- RETURN_ERROR_IF_SYSCALL_FAIL(
- mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str()));
-
- // Enable both the file and the directory.
- ASSIGN_OR_RETURN_ERRNO(
- auto fd, Open(JoinPath(verity_dir.path(), filename), O_RDONLY, 0777));
- RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(fd.get(), FS_IOC_ENABLE_VERITY));
- ASSIGN_OR_RETURN_ERRNO(auto dir_fd, Open(verity_dir.path(), O_RDONLY, 0777));
- RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(dir_fd.get(), FS_IOC_ENABLE_VERITY));
-
- // Measure the root hash.
- uint8_t digest_array[sizeof(struct fsverity_digest) + kMaxDigestSize] = {0};
- struct fsverity_digest* digest =
- reinterpret_cast<struct fsverity_digest*>(digest_array);
- digest->digest_size = kMaxDigestSize;
- RETURN_ERROR_IF_SYSCALL_FAIL(
- ioctl(dir_fd.get(), FS_IOC_MEASURE_VERITY, digest));
-
- // Mount a verity fs with specified root hash.
- mount_opts +=
- ",root_hash=" + BytesToHexString(digest->digest, digest->digest_size);
- ASSIGN_OR_RETURN_ERRNO(TempPath verity_with_hash_dir, TempPath::CreateDir());
- RETURN_ERROR_IF_SYSCALL_FAIL(mount("", verity_with_hash_dir.path().c_str(),
- "verity", 0, mount_opts.c_str()));
- // Verity directories should not be deleted. Release the TempPath objects to
- // prevent those directories from being deleted by the destructor.
- verity_dir.release();
- return verity_with_hash_dir.release();
-}
-
TEST_F(IoctlTest, Enable) {
// Mount a verity fs on the existing tmpfs mount.
std::string mount_opts = "lower_path=" + tmpfs_dir_.path();
diff --git a/test/syscalls/linux/verity_mmap.cc b/test/syscalls/linux/verity_mmap.cc
new file mode 100644
index 000000000..dde74cc91
--- /dev/null
+++ b/test/syscalls/linux/verity_mmap.cc
@@ -0,0 +1,158 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/util/capability_util.h"
+#include "test/util/fs_util.h"
+#include "test/util/memory_util.h"
+#include "test/util/temp_path.h"
+#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+class MmapTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Verity is implemented in VFS2.
+ SKIP_IF(IsRunningWithVFS1());
+
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
+ // Mount a tmpfs file system, to be wrapped by a verity fs.
+ tmpfs_dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ ASSERT_THAT(mount("", tmpfs_dir_.path().c_str(), "tmpfs", 0, ""),
+ SyscallSucceeds());
+
+ // Create a new file in the tmpfs mount.
+ file_ = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(tmpfs_dir_.path(), kContents, 0777));
+ filename_ = Basename(file_.path());
+ }
+
+ TempPath tmpfs_dir_;
+ TempPath file_;
+ std::string filename_;
+};
+
+TEST_F(MmapTest, MmapRead) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ // Make sure the file can be open and mmapped in the mounted verity fs.
+ auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777));
+
+ Mapping const m =
+ ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, sizeof(kContents) - 1, PROT_READ,
+ MAP_SHARED, verity_fd.get(), 0));
+ EXPECT_THAT(std::string(m.view()), ::testing::StrEq(kContents));
+}
+
+TEST_F(MmapTest, ModifiedBeforeMmap) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ // Modify the file and check verification failure upon mmapping.
+ auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(tmpfs_dir_.path(), filename_), O_RDWR, 0777));
+ ASSERT_NO_ERRNO(FlipRandomBit(fd.get(), sizeof(kContents) - 1));
+
+ auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777));
+ Mapping const m =
+ ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, sizeof(kContents) - 1, PROT_READ,
+ MAP_SHARED, verity_fd.get(), 0));
+
+ // Memory fault is expected when Translate fails.
+ EXPECT_EXIT(std::string(m.view()), ::testing::KilledBySignal(SIGSEGV), "");
+}
+
+TEST_F(MmapTest, ModifiedAfterMmap) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777));
+ Mapping const m =
+ ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, sizeof(kContents) - 1, PROT_READ,
+ MAP_SHARED, verity_fd.get(), 0));
+
+ // Modify the file after mapping and check verification failure upon mmapping.
+ auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(tmpfs_dir_.path(), filename_), O_RDWR, 0777));
+ ASSERT_NO_ERRNO(FlipRandomBit(fd.get(), sizeof(kContents) - 1));
+
+ // Memory fault is expected when Translate fails.
+ EXPECT_EXIT(std::string(m.view()), ::testing::KilledBySignal(SIGSEGV), "");
+}
+
+class MmapParamTest
+ : public MmapTest,
+ public ::testing::WithParamInterface<std::tuple<int, int>> {
+ protected:
+ int prot() const { return std::get<0>(GetParam()); }
+ int flags() const { return std::get<1>(GetParam()); }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ WriteExecNoneSharedPrivate, MmapParamTest,
+ ::testing::Combine(::testing::ValuesIn({
+ PROT_WRITE,
+ PROT_EXEC,
+ PROT_NONE,
+ }),
+ ::testing::ValuesIn({MAP_SHARED, MAP_PRIVATE})));
+
+TEST_P(MmapParamTest, Mmap) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ // Make sure the file can be open and mmapped in the mounted verity fs.
+ auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777));
+
+ if (prot() == PROT_WRITE && flags() == MAP_SHARED) {
+ // Verity file system is read-only.
+ EXPECT_THAT(
+ reinterpret_cast<intptr_t>(mmap(nullptr, sizeof(kContents) - 1, prot(),
+ flags(), verity_fd.get(), 0)),
+ SyscallFailsWithErrno(EACCES));
+ } else {
+ Mapping const m = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
+ nullptr, sizeof(kContents) - 1, prot(), flags(), verity_fd.get(), 0));
+ if (prot() == PROT_NONE) {
+ // Memory mapped by MAP_NONE cannot be accessed.
+ EXPECT_EXIT(std::string(m.view()), ::testing::KilledBySignal(SIGSEGV),
+ "");
+ } else {
+ EXPECT_THAT(std::string(m.view()), ::testing::StrEq(kContents));
+ }
+ }
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/verity_mount.cc b/test/syscalls/linux/verity_mount.cc
index e73dd5599..d6bfcb46d 100644
--- a/test/syscalls/linux/verity_mount.cc
+++ b/test/syscalls/linux/verity_mount.cc
@@ -22,13 +22,14 @@
#include "test/util/capability_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
namespace gvisor {
namespace testing {
namespace {
-// Mount verity file system on an existing gofer mount.
+// Mount verity file system on an existing tmpfs mount.
TEST(MountTest, MountExisting) {
// Verity is implemented in VFS2.
SKIP_IF(IsRunningWithVFS1());
@@ -43,8 +44,11 @@ TEST(MountTest, MountExisting) {
// Mount a verity file system on the existing gofer mount.
auto const verity_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
std::string opts = "lower_path=" + tmpfs_dir.path();
- EXPECT_THAT(mount("", verity_dir.path().c_str(), "verity", 0, opts.c_str()),
+ ASSERT_THAT(mount("", verity_dir.path().c_str(), "verity", 0, opts.c_str()),
SyscallSucceeds());
+ auto const fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(verity_dir.path(), O_RDONLY, 0777));
+ EXPECT_THAT(ioctl(fd.get(), FS_IOC_ENABLE_VERITY), SyscallSucceeds());
}
} // namespace