diff options
Diffstat (limited to 'test/syscalls/linux/proc.cc')
-rw-r--r-- | test/syscalls/linux/proc.cc | 553 |
1 files changed, 539 insertions, 14 deletions
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index 63642880a..7a0f33dff 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -16,6 +16,7 @@ #include <errno.h> #include <fcntl.h> #include <limits.h> +#include <linux/magic.h> #include <sched.h> #include <signal.h> #include <stddef.h> @@ -25,7 +26,9 @@ #include <string.h> #include <sys/mman.h> #include <sys/prctl.h> +#include <sys/ptrace.h> #include <sys/stat.h> +#include <sys/statfs.h> #include <sys/utsname.h> #include <syscall.h> #include <unistd.h> @@ -45,6 +48,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/container/node_hash_set.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" @@ -61,6 +65,7 @@ #include "test/util/fs_util.h" #include "test/util/memory_util.h" #include "test/util/posix_error.h" +#include "test/util/proc_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -508,6 +513,414 @@ TEST(ProcSelfAuxv, EntryValues) { EXPECT_EQ(i, proc_auxv.size()); } +// Just open and read a part of /proc/self/mem, check that we can read an item. +TEST(ProcPidMem, Read) { + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + char input[] = "hello-world"; + char output[sizeof(input)]; + ASSERT_THAT(pread(memfd.get(), output, sizeof(output), + reinterpret_cast<off_t>(input)), + SyscallSucceedsWithValue(sizeof(input))); + ASSERT_STREQ(input, output); +} + +// Perform read on an unmapped region. +TEST(ProcPidMem, Unmapped) { + // Strategy: map then unmap, so we have a guaranteed unmapped region + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE( + MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); + // Fill it with things + memset(mapping.ptr(), 'x', mapping.len()); + char expected = 'x', output; + ASSERT_THAT(pread(memfd.get(), &output, sizeof(output), + reinterpret_cast<off_t>(mapping.ptr())), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(expected, output); + + // Unmap region again + ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds()); + + // Now we want EIO error + ASSERT_THAT(pread(memfd.get(), &output, sizeof(output), + reinterpret_cast<off_t>(mapping.ptr())), + SyscallFailsWithErrno(EIO)); +} + +// Perform read repeatedly to verify offset change. +TEST(ProcPidMem, RepeatedRead) { + auto const num_reads = 3; + char expected[] = "01234567890abcdefghijkl"; + char output[sizeof(expected) / num_reads]; + + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET), + SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected))); + for (auto i = 0; i < num_reads; i++) { + ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(strncmp(&expected[i * sizeof(output)], output, sizeof(output)), + 0); + } +} + +// Perform seek operations repeatedly. +TEST(ProcPidMem, RepeatedSeek) { + auto const num_reads = 3; + char expected[] = "01234567890abcdefghijkl"; + char output[sizeof(expected) / num_reads]; + + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET), + SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected))); + // Read from start + ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0); + // Skip ahead one read + ASSERT_THAT(lseek(memfd.get(), sizeof(output), SEEK_CUR), + SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected) + + sizeof(output) * 2)); + // Do read again + ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(strncmp(&expected[2 * sizeof(output)], output, sizeof(output)), 0); + // Skip back three reads + ASSERT_THAT(lseek(memfd.get(), -3 * sizeof(output), SEEK_CUR), + SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected))); + // Do read again + ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), + SyscallSucceedsWithValue(sizeof(output))); + ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0); + // Check that SEEK_END does not work + ASSERT_THAT(lseek(memfd.get(), 0, SEEK_END), SyscallFailsWithErrno(EINVAL)); +} + +// Perform read past an allocated memory region. +TEST(ProcPidMem, PartialRead) { + // Strategy: map large region, then do unmap and remap smaller region + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); + + Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE( + MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); + ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds()); + Mapping smaller_mapping = ASSERT_NO_ERRNO_AND_VALUE( + Mmap(mapping.ptr(), kPageSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + + // Fill it with things + memset(smaller_mapping.ptr(), 'x', smaller_mapping.len()); + + // Now we want no error + char expected[] = {'x'}; + std::unique_ptr<char[]> output(new char[kPageSize]); + off_t read_offset = + reinterpret_cast<off_t>(smaller_mapping.ptr()) + kPageSize - 1; + ASSERT_THAT( + pread(memfd.get(), output.get(), sizeof(output.get()), read_offset), + SyscallSucceedsWithValue(sizeof(expected))); + // Since output is larger, than expected we have to do manual compare + ASSERT_EQ(expected[0], (output).get()[0]); +} + +// Perform read on /proc/[pid]/mem after exit. +TEST(ProcPidMem, AfterExit) { + int pfd1[2] = {}; + int pfd2[2] = {}; + + char expected[] = "hello-world"; + + ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); + ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); + + // Create child process + pid_t const child_pid = fork(); + if (child_pid == 0) { + // Close reading end of first pipe + close(pfd1[0]); + + // Tell parent about location of input + char ok = 1; + TEST_CHECK(WriteFd(pfd1[1], &ok, sizeof(ok)) == sizeof(ok)); + TEST_PCHECK(close(pfd1[1]) == 0); + + // Close writing end of second pipe + TEST_PCHECK(close(pfd2[1]) == 0); + + // Await parent OK to die + ok = 0; + TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); + + // Close rest pipes + TEST_PCHECK(close(pfd2[0]) == 0); + _exit(0); + } + + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + + // Close writing end of first pipe + EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); + + // Wait for child to be alive and well + char ok = 0; + EXPECT_THAT(ReadFd(pfd1[0], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + // Close reading end of first pipe + EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); + + // Open /proc/pid/mem fd + std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY)); + + // Expect that we can read + char output[sizeof(expected)]; + EXPECT_THAT(pread(memfd.get(), &output, sizeof(output), + reinterpret_cast<off_t>(&expected)), + SyscallSucceedsWithValue(sizeof(output))); + EXPECT_STREQ(expected, output); + + // Tell proc its ok to go + EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); + ok = 1; + EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); + + // Expect termination + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); + + // Expect that we can't read anymore + EXPECT_THAT(pread(memfd.get(), &output, sizeof(output), + reinterpret_cast<off_t>(&expected)), + SyscallSucceedsWithValue(0)); +} + +// Read from /proc/[pid]/mem with different UID/GID and attached state. +TEST(ProcPidMem, DifferentUserAttached) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_DAC_OVERRIDE))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_PTRACE))); + + int pfd1[2] = {}; + int pfd2[2] = {}; + + ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); + ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); + + // Create child process + pid_t const child_pid = fork(); + if (child_pid == 0) { + // Close reading end of first pipe + close(pfd1[0]); + + // Tell parent about location of input + char input[] = "hello-world"; + off_t input_location = reinterpret_cast<off_t>(input); + TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) == + sizeof(input_location)); + TEST_PCHECK(close(pfd1[1]) == 0); + + // Close writing end of second pipe + TEST_PCHECK(close(pfd2[1]) == 0); + + // Await parent OK to die + char ok = 0; + TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); + + // Close rest pipes + TEST_PCHECK(close(pfd2[0]) == 0); + _exit(0); + } + + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + + // Close writing end of first pipe + EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); + + // Read target location from child + off_t target_location; + EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)), + SyscallSucceedsWithValue(sizeof(target_location))); + // Close reading end of first pipe + EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); + + ScopedThread([&] { + // Attach to child subprocess without stopping it + EXPECT_THAT(ptrace(PTRACE_SEIZE, child_pid, NULL, NULL), SyscallSucceeds()); + + // Keep capabilities after setuid + EXPECT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds()); + constexpr int kNobody = 65534; + EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds()); + + // Only restore CAP_SYS_PTRACE and CAP_DAC_OVERRIDE + EXPECT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, true)); + EXPECT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, true)); + + // Open /proc/pid/mem fd + std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY)); + char expected[] = "hello-world"; + char output[sizeof(expected)]; + EXPECT_THAT(pread(memfd.get(), output, sizeof(output), + reinterpret_cast<off_t>(target_location)), + SyscallSucceedsWithValue(sizeof(output))); + EXPECT_STREQ(expected, output); + + // Tell proc its ok to go + EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); + char ok = 1; + EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); + + // Expect termination + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) + << " status " << status; + }); +} + +// Attempt to read from /proc/[pid]/mem with different UID/GID. +TEST(ProcPidMem, DifferentUser) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); + + int pfd1[2] = {}; + int pfd2[2] = {}; + + ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); + ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); + + // Create child process + pid_t const child_pid = fork(); + if (child_pid == 0) { + // Close reading end of first pipe + close(pfd1[0]); + + // Tell parent about location of input + char input[] = "hello-world"; + off_t input_location = reinterpret_cast<off_t>(input); + TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) == + sizeof(input_location)); + TEST_PCHECK(close(pfd1[1]) == 0); + + // Close writing end of second pipe + TEST_PCHECK(close(pfd2[1]) == 0); + + // Await parent OK to die + char ok = 0; + TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); + + // Close rest pipes + TEST_PCHECK(close(pfd2[0]) == 0); + _exit(0); + } + + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + + // Close writing end of first pipe + EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); + + // Read target location from child + off_t target_location; + EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)), + SyscallSucceedsWithValue(sizeof(target_location))); + // Close reading end of first pipe + EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); + + ScopedThread([&] { + constexpr int kNobody = 65534; + EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds()); + + // Attempt to open /proc/[child_pid]/mem + std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); + EXPECT_THAT(open(mempath.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); + + // Tell proc its ok to go + EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); + char ok = 1; + EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); + + // Expect termination + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); + }); +} + +// Perform read on /proc/[pid]/mem with same UID/GID. +TEST(ProcPidMem, SameUser) { + int pfd1[2] = {}; + int pfd2[2] = {}; + + ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); + ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); + + // Create child process + pid_t const child_pid = fork(); + if (child_pid == 0) { + // Close reading end of first pipe + close(pfd1[0]); + + // Tell parent about location of input + char input[] = "hello-world"; + off_t input_location = reinterpret_cast<off_t>(input); + TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) == + sizeof(input_location)); + TEST_PCHECK(close(pfd1[1]) == 0); + + // Close writing end of second pipe + TEST_PCHECK(close(pfd2[1]) == 0); + + // Await parent OK to die + char ok = 0; + TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); + + // Close rest pipes + TEST_PCHECK(close(pfd2[0]) == 0); + _exit(0); + } + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + + // Close writing end of first pipe + EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); + + // Read target location from child + off_t target_location; + EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)), + SyscallSucceedsWithValue(sizeof(target_location))); + // Close reading end of first pipe + EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); + + // Open /proc/pid/mem fd + std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); + auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY)); + char expected[] = "hello-world"; + char output[sizeof(expected)]; + EXPECT_THAT(pread(memfd.get(), output, sizeof(output), + reinterpret_cast<off_t>(target_location)), + SyscallSucceedsWithValue(sizeof(output))); + EXPECT_STREQ(expected, output); + + // Tell proc its ok to go + EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); + char ok = 1; + EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), + SyscallSucceedsWithValue(sizeof(ok))); + EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); + + // Expect termination + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); +} + // Just open and read /proc/self/maps, check that we can find [stack] TEST(ProcSelfMaps, Basic) { auto proc_self_maps = @@ -670,6 +1083,23 @@ TEST(ProcSelfMaps, Mprotect) { 3 * kPageSize, PROT_READ))); } +TEST(ProcSelfMaps, SharedAnon) { + const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( + MmapAnon(kPageSize, PROT_READ, MAP_SHARED | MAP_ANONYMOUS)); + + const auto proc_self_maps = + ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); + for (const auto& line : absl::StrSplit(proc_self_maps, '\n')) { + const auto entry = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMapsLine(line)); + if (entry.start <= m.addr() && m.addr() < entry.end) { + // cf. proc(5), "/proc/[pid]/map_files/" + EXPECT_EQ(entry.filename, "/dev/zero (deleted)"); + return; + } + } + FAIL() << "no maps entry containing mapping at " << m.ptr(); +} + TEST(ProcSelfFd, OpenFd) { int pipe_fds[2]; ASSERT_THAT(pipe2(pipe_fds, O_CLOEXEC), SyscallSucceeds()); @@ -692,6 +1122,30 @@ TEST(ProcSelfFd, OpenFd) { ASSERT_THAT(close(pipe_fds[1]), SyscallSucceeds()); } +static void CheckFdDirGetdentsDuplicates(const std::string& path) { + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_RDONLY | O_DIRECTORY)); + // Open a FD whose value is supposed to be much larger than + // the number of FDs opened by current process. + auto newfd = fcntl(fd.get(), F_DUPFD, 1024); + EXPECT_GE(newfd, 1024); + auto fd_closer = Cleanup([newfd]() { close(newfd); }); + auto fd_files = ASSERT_NO_ERRNO_AND_VALUE(ListDir(path.c_str(), false)); + absl::node_hash_set<std::string> fd_files_dedup(fd_files.begin(), + fd_files.end()); + EXPECT_EQ(fd_files.size(), fd_files_dedup.size()); +} + +// This is a regression test for gvisor.dev/issues/3894 +TEST(ProcSelfFd, GetdentsDuplicates) { + CheckFdDirGetdentsDuplicates("/proc/self/fd"); +} + +// This is a regression test for gvisor.dev/issues/3894 +TEST(ProcSelfFdInfo, GetdentsDuplicates) { + CheckFdDirGetdentsDuplicates("/proc/self/fdinfo"); +} + TEST(ProcSelfFdInfo, CorrectFds) { // Make sure there is at least one open file. auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); @@ -735,8 +1189,12 @@ TEST(ProcSelfFdInfo, Flags) { } TEST(ProcSelfExe, Absolute) { - auto exe = ASSERT_NO_ERRNO_AND_VALUE( - ReadLink(absl::StrCat("/proc/", getpid(), "/exe"))); + auto exe = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe")); + EXPECT_EQ(exe[0], '/'); +} + +TEST(ProcSelfCwd, Absolute) { + auto exe = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/cwd")); EXPECT_EQ(exe[0], '/'); } @@ -754,8 +1212,42 @@ TEST(ProcCpuinfo, RequiredFieldsArePresent) { } } -TEST(ProcCpuinfo, DeniesWrite) { - EXPECT_THAT(open("/proc/cpuinfo", O_WRONLY), SyscallFailsWithErrno(EACCES)); +TEST(ProcCpuinfo, DeniesWriteNonRoot) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER))); + + // Do setuid in a separate thread so that after finishing this test, the + // process can still open files the test harness created before starting this + // test. Otherwise, the files are created by root (UID before the test), but + // cannot be opened by the `uid` set below after the test. After calling + // setuid(non-zero-UID), there is no way to get root privileges back. + ScopedThread([&] { + // Use syscall instead of glibc setuid wrapper because we want this setuid + // call to only apply to this task. POSIX threads, however, require that all + // threads have the same UIDs, so using the setuid wrapper sets all threads' + // real UID. + // Also drops capabilities. + constexpr int kNobody = 65534; + EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds()); + EXPECT_THAT(open("/proc/cpuinfo", O_WRONLY), SyscallFailsWithErrno(EACCES)); + EXPECT_THAT(truncate("/proc/cpuinfo", 123), SyscallFailsWithErrno(EACCES)); + }); +} + +// With root privileges, it is possible to open /proc/cpuinfo with write mode, +// but all write operations should fail. +TEST(ProcCpuinfo, DeniesWriteRoot) { + // VFS1 does not behave differently for root/non-root. + SKIP_IF(IsRunningWithVFS1()); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER))); + + int fd; + EXPECT_THAT(fd = open("/proc/cpuinfo", O_WRONLY), SyscallSucceeds()); + if (fd > 0) { + // Truncate is not tested--it may succeed on some kernels without doing + // anything. + EXPECT_THAT(write(fd, "x", 1), SyscallFails()); + EXPECT_THAT(pwrite(fd, "x", 1, 123), SyscallFails()); + } } // Sanity checks that uptime is present. @@ -1394,6 +1886,16 @@ TEST(ProcPidExe, Subprocess) { EXPECT_EQ(actual, expected_absolute_path); } +// /proc/PID/cwd points to the correct directory. +TEST(ProcPidCwd, Subprocess) { + auto want = ASSERT_NO_ERRNO_AND_VALUE(GetCWD()); + + char got[PATH_MAX + 1] = {}; + ASSERT_THAT(ReadlinkWhileRunning("cwd", got, sizeof(got)), + SyscallSucceedsWithValue(Gt(0))); + EXPECT_EQ(got, want); +} + // Test whether /proc/PID/ files can be read for a running process. TEST(ProcPidFile, SubprocessRunning) { char buf[1]; @@ -1921,7 +2423,7 @@ TEST(ProcSelfMountinfo, RequiredFieldsArePresent) { AllOf( // Root mount. ContainsRegex( - R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ / / (rw|ro).*- \S+ \S+ (rw|ro)\S*)"), + R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ /\S* / (rw|ro).*- \S+ \S+ (rw|ro)\S*)"), // Proc mount - always rw. ContainsRegex( R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ / /proc rw.*- \S+ \S+ rw\S*)"))); @@ -1967,30 +2469,41 @@ void CheckDuplicatesRecursively(std::string path) { errno = 0; struct dirent* dp = readdir(dir); if (dp == nullptr) { + // Linux will return EINVAL when calling getdents on a /proc/tid/net + // file corresponding to a zombie task. + // See fs/proc/proc_net.c:proc_tgid_net_readdir(). + // + // We just ignore the directory in this case. + if (errno == EINVAL && absl::StartsWith(path, "/proc/") && + absl::EndsWith(path, "/net")) { + break; + } + + // Otherwise, no errors are allowed. ASSERT_EQ(errno, 0) << path; break; // We're done. } - if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { + const std::string name = dp->d_name; + + if (name == "." || name == "..") { continue; } // Ignore a duplicate entry if it isn't the last attempt. if (i == max_attempts - 1) { - ASSERT_EQ(children.find(std::string(dp->d_name)), children.end()) - << absl::StrCat(path, "/", dp->d_name); - } else if (children.find(std::string(dp->d_name)) != children.end()) { + ASSERT_EQ(children.find(name), children.end()) + << absl::StrCat(path, "/", name); + } else if (children.find(name) != children.end()) { std::cerr << "Duplicate entry: " << i << ":" - << absl::StrCat(path, "/", dp->d_name) << std::endl; + << absl::StrCat(path, "/", name) << std::endl; success = false; break; } - children.insert(std::string(dp->d_name)); - - ASSERT_NE(dp->d_type, DT_UNKNOWN); + children.insert(name); if (dp->d_type == DT_DIR) { - child_dirs.push_back(std::string(dp->d_name)); + child_dirs.push_back(name); } } if (success) { @@ -2103,6 +2616,18 @@ TEST(Proc, PidTidIOAccounting) { noop.Join(); } +TEST(Proc, Statfs) { + struct statfs st; + EXPECT_THAT(statfs("/proc", &st), SyscallSucceeds()); + if (IsRunningWithVFS1()) { + EXPECT_EQ(st.f_type, ANON_INODE_FS_MAGIC); + } else { + EXPECT_EQ(st.f_type, PROC_SUPER_MAGIC); + } + EXPECT_EQ(st.f_bsize, getpagesize()); + EXPECT_EQ(st.f_namelen, NAME_MAX); +} + } // namespace } // namespace testing } // namespace gvisor |