|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <sched.h> | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  | #include <sys/socket.h> | 
|  | #include <sys/syscall.h> | 
|  | #include <sys/wait.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/files/scoped_file.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/scoped_vector.h" | 
|  | #include "base/posix/eintr_wrapper.h" | 
|  | #include "base/posix/unix_domain_socket_linux.h" | 
|  | #include "base/process/process_handle.h" | 
|  | #include "sandbox/linux/tests/unit_tests.h" | 
|  |  | 
|  | // Additional tests for base's UnixDomainSocket to make sure it behaves | 
|  | // correctly in the presence of sandboxing functionality (e.g., receiving | 
|  | // PIDs across namespaces). | 
|  |  | 
|  | namespace sandbox { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kHello[] = "hello"; | 
|  |  | 
|  | // If the calling process isn't root, then try using unshare(CLONE_NEWUSER) | 
|  | // to fake it. | 
|  | void FakeRoot() { | 
|  | // If we're already root, then allow test to proceed. | 
|  | if (geteuid() == 0) | 
|  | return; | 
|  |  | 
|  | // Otherwise hope the kernel supports unprivileged namespaces. | 
|  | if (unshare(CLONE_NEWUSER) == 0) | 
|  | return; | 
|  |  | 
|  | printf("Permission to use CLONE_NEWPID missing; skipping test.\n"); | 
|  | UnitTests::IgnoreThisTest(); | 
|  | } | 
|  |  | 
|  | void WaitForExit(pid_t pid) { | 
|  | int status; | 
|  | CHECK_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0))); | 
|  | CHECK(WIFEXITED(status)); | 
|  | CHECK_EQ(0, WEXITSTATUS(status)); | 
|  | } | 
|  |  | 
|  | base::ProcessId GetParentProcessId(base::ProcessId pid) { | 
|  | // base::GetParentProcessId() is defined as taking a ProcessHandle instead of | 
|  | // a ProcessId, even though it's a POSIX-only function and IDs and Handles | 
|  | // are both simply pid_t on POSIX... :/ | 
|  | base::ProcessHandle handle; | 
|  | CHECK(base::OpenProcessHandle(pid, &handle)); | 
|  | base::ProcessId ret = base::GetParentProcessId(pid); | 
|  | base::CloseProcessHandle(handle); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | // SendHello sends a "hello" to socket fd, and then blocks until the recipient | 
|  | // acknowledges it by calling RecvHello. | 
|  | void SendHello(int fd) { | 
|  | int pipe_fds[2]; | 
|  | CHECK_EQ(0, pipe(pipe_fds)); | 
|  | base::ScopedFD read_pipe(pipe_fds[0]); | 
|  | base::ScopedFD write_pipe(pipe_fds[1]); | 
|  |  | 
|  | std::vector<int> send_fds; | 
|  | send_fds.push_back(write_pipe.get()); | 
|  | CHECK(UnixDomainSocket::SendMsg(fd, kHello, sizeof(kHello), send_fds)); | 
|  |  | 
|  | write_pipe.reset(); | 
|  |  | 
|  | // Block until receiver closes their end of the pipe. | 
|  | char ch; | 
|  | CHECK_EQ(0, HANDLE_EINTR(read(read_pipe.get(), &ch, 1))); | 
|  | } | 
|  |  | 
|  | // RecvHello receives and acknowledges a "hello" on socket fd, and returns the | 
|  | // process ID of the sender in sender_pid.  Optionally, write_pipe can be used | 
|  | // to return a file descriptor, and the acknowledgement will be delayed until | 
|  | // the descriptor is closed. | 
|  | // (Implementation details: SendHello allocates a new pipe, sends us the writing | 
|  | // end alongside the "hello" message, and then blocks until we close the writing | 
|  | // end of the pipe.) | 
|  | void RecvHello(int fd, | 
|  | base::ProcessId* sender_pid, | 
|  | base::ScopedFD* write_pipe = NULL) { | 
|  | // Extra receiving buffer space to make sure we really received only | 
|  | // sizeof(kHello) bytes and it wasn't just truncated to fit the buffer. | 
|  | char buf[sizeof(kHello) + 1]; | 
|  | ScopedVector<base::ScopedFD> message_fds; | 
|  | ssize_t n = UnixDomainSocket::RecvMsgWithPid( | 
|  | fd, buf, sizeof(buf), &message_fds, sender_pid); | 
|  | CHECK_EQ(sizeof(kHello), static_cast<size_t>(n)); | 
|  | CHECK_EQ(0, memcmp(buf, kHello, sizeof(kHello))); | 
|  | CHECK_EQ(1U, message_fds.size()); | 
|  | if (write_pipe) | 
|  | write_pipe->swap(*message_fds[0]); | 
|  | } | 
|  |  | 
|  | // Check that receiving PIDs works across a fork(). | 
|  | SANDBOX_TEST(UnixDomainSocketTest, Fork) { | 
|  | int fds[2]; | 
|  | CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); | 
|  | base::ScopedFD recv_sock(fds[0]); | 
|  | base::ScopedFD send_sock(fds[1]); | 
|  |  | 
|  | CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); | 
|  |  | 
|  | const pid_t pid = fork(); | 
|  | CHECK_NE(-1, pid); | 
|  | if (pid == 0) { | 
|  | // Child process. | 
|  | recv_sock.reset(); | 
|  | SendHello(send_sock.get()); | 
|  | _exit(0); | 
|  | } | 
|  |  | 
|  | // Parent process. | 
|  | send_sock.reset(); | 
|  |  | 
|  | base::ProcessId sender_pid; | 
|  | RecvHello(recv_sock.get(), &sender_pid); | 
|  | CHECK_EQ(pid, sender_pid); | 
|  |  | 
|  | WaitForExit(pid); | 
|  | } | 
|  |  | 
|  | // Similar to Fork above, but forking the child into a new pid namespace. | 
|  | SANDBOX_TEST(UnixDomainSocketTest, Namespace) { | 
|  | FakeRoot(); | 
|  |  | 
|  | int fds[2]; | 
|  | CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); | 
|  | base::ScopedFD recv_sock(fds[0]); | 
|  | base::ScopedFD send_sock(fds[1]); | 
|  |  | 
|  | CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); | 
|  |  | 
|  | const pid_t pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); | 
|  | CHECK_NE(-1, pid); | 
|  | if (pid == 0) { | 
|  | // Child process. | 
|  | recv_sock.reset(); | 
|  |  | 
|  | // Check that we think we're pid 1 in our new namespace. | 
|  | CHECK_EQ(1, syscall(__NR_getpid)); | 
|  |  | 
|  | SendHello(send_sock.get()); | 
|  | _exit(0); | 
|  | } | 
|  |  | 
|  | // Parent process. | 
|  | send_sock.reset(); | 
|  |  | 
|  | base::ProcessId sender_pid; | 
|  | RecvHello(recv_sock.get(), &sender_pid); | 
|  | CHECK_EQ(pid, sender_pid); | 
|  |  | 
|  | WaitForExit(pid); | 
|  | } | 
|  |  | 
|  | // Again similar to Fork, but now with nested PID namespaces. | 
|  | SANDBOX_TEST(UnixDomainSocketTest, DoubleNamespace) { | 
|  | FakeRoot(); | 
|  |  | 
|  | int fds[2]; | 
|  | CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); | 
|  | base::ScopedFD recv_sock(fds[0]); | 
|  | base::ScopedFD send_sock(fds[1]); | 
|  |  | 
|  | CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); | 
|  |  | 
|  | const pid_t pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); | 
|  | CHECK_NE(-1, pid); | 
|  | if (pid == 0) { | 
|  | // Child process. | 
|  | recv_sock.reset(); | 
|  |  | 
|  | const pid_t pid2 = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); | 
|  | CHECK_NE(-1, pid2); | 
|  |  | 
|  | if (pid2 != 0) { | 
|  | // Wait for grandchild to run to completion; see comments below. | 
|  | WaitForExit(pid2); | 
|  |  | 
|  | // Fallthrough once grandchild has sent its hello and exited. | 
|  | } | 
|  |  | 
|  | // Check that we think we're pid 1. | 
|  | CHECK_EQ(1, syscall(__NR_getpid)); | 
|  |  | 
|  | SendHello(send_sock.get()); | 
|  | _exit(0); | 
|  | } | 
|  |  | 
|  | // Parent process. | 
|  | send_sock.reset(); | 
|  |  | 
|  | // We have two messages to receive: first from the grand-child, | 
|  | // then from the child. | 
|  | for (unsigned iteration = 0; iteration < 2; ++iteration) { | 
|  | base::ProcessId sender_pid; | 
|  | base::ScopedFD pipe_fd; | 
|  | RecvHello(recv_sock.get(), &sender_pid, &pipe_fd); | 
|  |  | 
|  | // We need our child and grandchild processes to both be alive for | 
|  | // GetParentProcessId() to return a valid pid, hence the pipe trickery. | 
|  | // (On the first iteration, grandchild is blocked reading from the pipe | 
|  | // until we close it, and child is blocked waiting for grandchild to exit.) | 
|  | switch (iteration) { | 
|  | case 0:  // Grandchild's message | 
|  | // Check that sender_pid refers to our grandchild by checking that pid | 
|  | // (our child) is its parent. | 
|  | CHECK_EQ(pid, GetParentProcessId(sender_pid)); | 
|  | break; | 
|  | case 1:  // Child's message | 
|  | CHECK_EQ(pid, sender_pid); | 
|  | break; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | } | 
|  | } | 
|  |  | 
|  | WaitForExit(pid); | 
|  | } | 
|  |  | 
|  | // Tests that GetPeerPid() returns 0 if the peer does not exist in caller's | 
|  | // namespace. | 
|  | SANDBOX_TEST(UnixDomainSocketTest, ImpossiblePid) { | 
|  | FakeRoot(); | 
|  |  | 
|  | int fds[2]; | 
|  | CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); | 
|  | base::ScopedFD send_sock(fds[0]); | 
|  | base::ScopedFD recv_sock(fds[1]); | 
|  |  | 
|  | CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); | 
|  |  | 
|  | const pid_t pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); | 
|  | CHECK_NE(-1, pid); | 
|  | if (pid == 0) { | 
|  | // Child process. | 
|  | send_sock.reset(); | 
|  |  | 
|  | base::ProcessId sender_pid; | 
|  | RecvHello(recv_sock.get(), &sender_pid); | 
|  | CHECK_EQ(0, sender_pid); | 
|  | _exit(0); | 
|  | } | 
|  |  | 
|  | // Parent process. | 
|  | recv_sock.reset(); | 
|  | SendHello(send_sock.get()); | 
|  | WaitForExit(pid); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace sandbox |