1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
|
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
#pragma allow_unsafe_libc_calls
#endif
#include <sched.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/files/scoped_file.h"
#include "base/notreached.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/unix_domain_socket.h"
#include "base/process/process.h"
#include "sandbox/linux/services/syscall_wrappers.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::Process process = base::Process::Open(pid);
CHECK(process.IsValid());
base::ProcessId ret = base::GetParentProcessId(process.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(base::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];
std::vector<base::ScopedFD> message_fds;
ssize_t n = base::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)
std::swap(*write_pipe, 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(base::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(base::UnixDomainSocket::EnableReceiveProcessId(recv_sock.get()));
const pid_t pid = sys_clone(CLONE_NEWPID | SIGCHLD, 0, 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, sys_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(base::UnixDomainSocket::EnableReceiveProcessId(recv_sock.get()));
const pid_t pid = sys_clone(CLONE_NEWPID | SIGCHLD, 0, 0, 0, 0);
CHECK_NE(-1, pid);
if (pid == 0) {
// Child process.
recv_sock.reset();
const pid_t pid2 = sys_clone(CLONE_NEWPID | SIGCHLD, 0, 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, sys_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(base::UnixDomainSocket::EnableReceiveProcessId(recv_sock.get()));
const pid_t pid = sys_clone(CLONE_NEWPID | SIGCHLD, 0, 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
|