Skip to content

Commit

Permalink
Support a new remote IPC for for GetTerminationStatus.
Browse files Browse the repository at this point in the history
This does three things:

1. Clean-up a lot of the code in the Linux NaCl loader.
2. Create a new IPC to the NaCl loader "Zygote" for the
main Zygote to query the termination status of processes.
3. Clean-up some code in the Zygote and fix "process tracking".

zygote_linux.cc:
 * Split GetTerminationStatus() out from HandleGetTerminationStatus().
 * Handle the case where we need to perform a remote IPC for GetTerminationStatus()
 * Use the new GetTerminationStatus() to support the remote case for HandleReapRequest().
 * Replace real_pids_to_sandbox_pids mapping with process_info_map_.
    * Update shortcut case in ForkWithRealPid() to fill this out.
    * Update GetTerminationStatus() to remove existing entries.

zygote_fork_delegate_linux.h:
 * Create a new GetTerminationStatus() interface.

nacl_helper_linux.cc:
 * Split HandleZygoteRequest() out from main().
 * Split ChildNaClLoaderInit() to handle the child side of a fork().
 * Handle a new IPC in HandleGetTerminationStatusRequest().

nacl_fork_delegate_linux.cc:
 * Implement the new GetTerminationStatus() interface.
 * Use Pickle for IPCs, make IPCs easier to write with SendIPCRequestAndReadReply().

BUG=133453
R=mseaborn@chromium.org, piman@chromium.org

Review URL: https://codereview.chromium.org/23020010

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@218584 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
jln@chromium.org committed Aug 21, 2013
1 parent c6c5057 commit 7d9c172
Show file tree
Hide file tree
Showing 7 changed files with 443 additions and 151 deletions.
292 changes: 193 additions & 99 deletions chrome/nacl/nacl_helper_linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <errno.h>
#include <fcntl.h>
#include <link.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
Expand All @@ -20,12 +21,12 @@

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/global_descriptors.h"
#include "base/posix/unix_domain_socket_linux.h"
#include "base/process/kill.h"
#include "base/rand_util.h"
#include "components/nacl/loader/nacl_listener.h"
#include "components/nacl/loader/nacl_sandbox_linux.h"
Expand All @@ -36,12 +37,16 @@

namespace {

struct NaClLoaderSystemInfo {
size_t prereserved_sandbox_size;
int number_of_cores;
};

// The child must mimic the behavior of zygote_main_linux.cc on the child
// side of the fork. See zygote_main_linux.cc:HandleForkRequest from
// if (!child) {
void BecomeNaClLoader(const std::vector<int>& child_fds,
size_t prereserved_sandbox_size,
int number_of_cores) {
const NaClLoaderSystemInfo& system_info) {
VLOG(1) << "NaCl loader: setting up IPC descriptor";
// don't need zygote FD any more
if (HANDLE_EINTR(close(kNaClZygoteDescriptor)) != 0)
Expand All @@ -56,75 +61,123 @@ void BecomeNaClLoader(const std::vector<int>& child_fds,

base::MessageLoopForIO main_message_loop;
NaClListener listener;
listener.set_prereserved_sandbox_size(prereserved_sandbox_size);
listener.set_number_of_cores(number_of_cores);
listener.set_prereserved_sandbox_size(system_info.prereserved_sandbox_size);
listener.set_number_of_cores(system_info.number_of_cores);
listener.Listen();
_exit(0);
}

// Start the NaCl loader in a child created by the NaCl loader Zygote.
void ChildNaClLoaderInit(const std::vector<int>& child_fds,
const NaClLoaderSystemInfo& system_info) {
bool validack = false;
const size_t kMaxReadSize = 1024;
char buffer[kMaxReadSize];
// Wait until the parent process has discovered our PID. We
// should not fork any child processes (which the seccomp
// sandbox does) until then, because that can interfere with the
// parent's discovery of our PID.
const int nread = HANDLE_EINTR(read(child_fds[kNaClParentFDIndex], buffer,
kMaxReadSize));
const std::string switch_prefix = std::string("--") +
switches::kProcessChannelID + std::string("=");
const size_t len = switch_prefix.length();

if (nread < 0) {
perror("read");
LOG(ERROR) << "read returned " << nread;
} else if (nread > static_cast<int>(len)) {
if (switch_prefix.compare(0, len, buffer, 0, len) == 0) {
VLOG(1) << "NaCl loader is synchronised with Chrome zygote";
CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kProcessChannelID,
std::string(&buffer[len], nread - len));
validack = true;
}
}
if (HANDLE_EINTR(close(child_fds[kNaClDummyFDIndex])) != 0)
LOG(ERROR) << "close(child_fds[kNaClDummyFDIndex]) failed";
if (HANDLE_EINTR(close(child_fds[kNaClParentFDIndex])) != 0)
LOG(ERROR) << "close(child_fds[kNaClParentFDIndex]) failed";
if (validack) {
BecomeNaClLoader(child_fds, system_info);
} else {
LOG(ERROR) << "Failed to synch with zygote";
}
_exit(1);
}

// Handle a fork request from the Zygote.
// Some of this code was lifted from
// content/browser/zygote_main_linux.cc:ForkWithRealPid()
void HandleForkRequest(const std::vector<int>& child_fds,
size_t prereserved_sandbox_size,
int number_of_cores) {
bool HandleForkRequest(const std::vector<int>& child_fds,
const NaClLoaderSystemInfo& system_info,
Pickle* output_pickle) {
if (kNaClParentFDIndex + 1 != child_fds.size()) {
LOG(ERROR) << "nacl_helper: unexpected number of fds, got "
<< child_fds.size();
return false;
}

VLOG(1) << "nacl_helper: forking";
pid_t childpid = fork();
if (childpid < 0) {
perror("fork");
LOG(ERROR) << "*** HandleForkRequest failed\n";
// fall through to parent case below
} else if (childpid == 0) { // In the child process.
bool validack = false;
const size_t kMaxReadSize = 1024;
char buffer[kMaxReadSize];
// Wait until the parent process has discovered our PID. We
// should not fork any child processes (which the seccomp
// sandbox does) until then, because that can interfere with the
// parent's discovery of our PID.
const int nread = HANDLE_EINTR(read(child_fds[kNaClParentFDIndex], buffer,
kMaxReadSize));
const std::string switch_prefix = std::string("--") +
switches::kProcessChannelID + std::string("=");
const size_t len = switch_prefix.length();

if (nread < 0) {
perror("read");
LOG(ERROR) << "read returned " << nread;
} else if (nread > static_cast<int>(len)) {
if (switch_prefix.compare(0, len, buffer, 0, len) == 0) {
VLOG(1) << "NaCl loader is synchronised with Chrome zygote";
CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kProcessChannelID,
std::string(&buffer[len], nread - len));
validack = true;
}
}
if (HANDLE_EINTR(close(child_fds[kNaClDummyFDIndex])) != 0)
LOG(ERROR) << "close(child_fds[kNaClDummyFDIndex]) failed";
if (HANDLE_EINTR(close(child_fds[kNaClParentFDIndex])) != 0)
LOG(ERROR) << "close(child_fds[kNaClParentFDIndex]) failed";
if (validack) {
BecomeNaClLoader(child_fds, prereserved_sandbox_size, number_of_cores);
} else {
LOG(ERROR) << "Failed to synch with zygote";
}
// NOTREACHED
return;
pid_t child_pid = fork();
if (child_pid < 0) {
PLOG(ERROR) << "*** fork() failed.";
}

if (child_pid == 0) {
ChildNaClLoaderInit(child_fds, system_info);
NOTREACHED();
}

// I am the parent.
// First, close the dummy_fd so the sandbox won't find me when
// looking for the child's pid in /proc. Also close other fds.
for (size_t i = 0; i < child_fds.size(); i++) {
if (HANDLE_EINTR(close(child_fds[i])) != 0)
LOG(ERROR) << "close(child_fds[i]) failed";
}
VLOG(1) << "nacl_helper: childpid is " << childpid;
// Now tell childpid to the Chrome zygote.
if (HANDLE_EINTR(send(kNaClZygoteDescriptor,
&childpid, sizeof(childpid), MSG_EOR))
!= sizeof(childpid)) {
LOG(ERROR) << "*** send() to zygote failed";
VLOG(1) << "nacl_helper: child_pid is " << child_pid;

// Now send child_pid (eventually -1 if fork failed) to the Chrome Zygote.
output_pickle->WriteInt(child_pid);
return true;
}

bool HandleGetTerminationStatusRequest(PickleIterator* input_iter,
Pickle* output_pickle) {
pid_t child_to_wait;
if (!input_iter->ReadInt(&child_to_wait)) {
LOG(ERROR) << "Could not read pid to wait for";
return false;
}

bool known_dead;
if (!input_iter->ReadBool(&known_dead)) {
LOG(ERROR) << "Could not read known_dead status";
return false;
}
// TODO(jln): With NaCl, known_dead seems to never be set to true (unless
// called from the Zygote's kZygoteCommandReap command). This means that we
// will sometimes detect the process as still running when it's not. Fix
// this!

int exit_code;
base::TerminationStatus status;
// See the comment in the Zygote about known_dead.
if (known_dead) {
// Make sure to not perform a blocking wait on something that
// could still be alive.
if (kill(child_to_wait, SIGKILL)) {
PLOG(ERROR) << "kill (" << child_to_wait << ")";
}
status = base::WaitForTerminationStatus(child_to_wait, &exit_code);
} else {
status = base::GetTerminationStatus(child_to_wait, &exit_code);
}
output_pickle->WriteInt(static_cast<int>(status));
output_pickle->WriteInt(exit_code);
return true;
}

// This is a poor man's check on whether we are sandboxed.
Expand All @@ -137,7 +190,76 @@ bool IsSandboxed() {
return true;
}

} // namespace
// Honor a command |command_type|. Eventual command parameters are
// available in |input_iter| and eventual file descriptors attached to
// the command are in |attached_fds|.
// Reply to the command on |reply_fds|.
bool HonorRequestAndReply(int reply_fd,
int command_type,
const std::vector<int>& attached_fds,
const NaClLoaderSystemInfo& system_info,
PickleIterator* input_iter) {
Pickle write_pickle;
bool have_to_reply = false;
// Commands must write anything to send back to |write_pickle|.
switch (command_type) {
case kNaClForkRequest:
have_to_reply = HandleForkRequest(attached_fds, system_info,
&write_pickle);
break;
case kNaClGetTerminationStatusRequest:
have_to_reply =
HandleGetTerminationStatusRequest(input_iter, &write_pickle);
break;
default:
LOG(ERROR) << "Unsupported command from Zygote";
return false;
}
if (!have_to_reply)
return false;
const std::vector<int> empty; // We never send file descriptors back.
if (!UnixDomainSocket::SendMsg(reply_fd, write_pickle.data(),
write_pickle.size(), empty)) {
LOG(ERROR) << "*** send() to zygote failed";
return false;
}
return true;
}

// Read a request from the Zygote from |zygote_ipc_fd| and handle it.
// Die on EOF from |zygote_ipc_fd|.
bool HandleZygoteRequest(int zygote_ipc_fd,
const NaClLoaderSystemInfo& system_info) {
std::vector<int> fds;
char buf[kNaClMaxIPCMessageLength];
const ssize_t msglen = UnixDomainSocket::RecvMsg(zygote_ipc_fd,
&buf, sizeof(buf), &fds);
// If the Zygote has started handling requests, we should be sandboxed via
// the setuid sandbox.
if (!IsSandboxed()) {
LOG(ERROR) << "NaCl helper process running without a sandbox!\n"
<< "Most likely you need to configure your SUID sandbox "
<< "correctly";
}
if (msglen == 0 || (msglen == -1 && errno == ECONNRESET)) {
// EOF from the browser. Goodbye!
_exit(0);
}
if (msglen < 0) {
PLOG(ERROR) << "nacl_helper: receive from zygote failed";
return false;
}

Pickle read_pickle(buf, msglen);
PickleIterator read_iter(read_pickle);
int command_type;
if (!read_iter.ReadInt(&command_type)) {
LOG(ERROR) << "Unable to read command from Zygote";
return false;
}
return HonorRequestAndReply(zygote_ipc_fd, command_type, fds, system_info,
&read_iter);
}

static const char kNaClHelperReservedAtZero[] = "reserved_at_zero";
static const char kNaClHelperRDebug[] = "r_debug";
Expand Down Expand Up @@ -206,6 +328,8 @@ static size_t CheckReservedAtZero() {
return prereserved_sandbox_size;
}

} // namespace

#if defined(ADDRESS_SANITIZER)
// Do not install the SIGSEGV handler in ASan. This should make the NaCl
// platform qualification test pass.
Expand Down Expand Up @@ -240,61 +364,31 @@ int main(int argc, char* argv[]) {
// NSS is needed to perform hashing for validation caching.
crypto::LoadNSSLibraries();
#endif
std::vector<int> empty; // for SendMsg() calls
size_t prereserved_sandbox_size = CheckReservedAtZero();
int number_of_cores = sysconf(_SC_NPROCESSORS_ONLN);
const NaClLoaderSystemInfo system_info = {
CheckReservedAtZero(),
sysconf(_SC_NPROCESSORS_ONLN)
};

CheckRDebug(argv[0]);

// Check that IsSandboxed() works. We should not be sandboxed at this point.
CHECK(!IsSandboxed()) << "Unexpectedly sandboxed!";

const std::vector<int> empty;
// Send the zygote a message to let it know we are ready to help
if (!UnixDomainSocket::SendMsg(kNaClZygoteDescriptor,
kNaClHelperStartupAck,
sizeof(kNaClHelperStartupAck), empty)) {
LOG(ERROR) << "*** send() to zygote failed";
}

// Now handle requests from the Zygote.
while (true) {
int badpid = -1;
std::vector<int> fds;
static const unsigned kMaxMessageLength = 2048;
char buf[kMaxMessageLength];
const ssize_t msglen = UnixDomainSocket::RecvMsg(kNaClZygoteDescriptor,
&buf, sizeof(buf), &fds);
// If the Zygote has started handling requests, we should be sandboxed via
// the setuid sandbox.
if (!IsSandboxed()) {
LOG(ERROR) << "NaCl helper process running without a sandbox!\n"
<< "Most likely you need to configure your SUID sandbox "
<< "correctly";
}
if (msglen == 0 || (msglen == -1 && errno == ECONNRESET)) {
// EOF from the browser. Goodbye!
_exit(0);
} else if (msglen < 0) {
LOG(ERROR) << "nacl_helper: receive from zygote failed, errno = "
<< errno;
} else if (msglen == sizeof(kNaClForkRequest) - 1 &&
memcmp(buf, kNaClForkRequest, msglen) == 0) {
if (kNaClParentFDIndex + 1 == fds.size()) {
HandleForkRequest(fds, prereserved_sandbox_size, number_of_cores);
continue; // fork succeeded. Note: child does not return
} else {
LOG(ERROR) << "nacl_helper: unexpected number of fds, got "
<< fds.size();
}
} else {
LOG(ERROR) << "nacl_helper unrecognized request: "
<< base::GetDoubleQuotedJson(std::string(buf, buf + msglen));
_exit(-1);
}
// if fork fails, send PID=-1 to zygote
if (!UnixDomainSocket::SendMsg(kNaClZygoteDescriptor, &badpid,
sizeof(badpid), empty)) {
LOG(ERROR) << "*** send() to zygote failed";
}
bool request_handled = HandleZygoteRequest(kNaClZygoteDescriptor,
system_info);
// Do not turn this into a CHECK() without thinking about robustness
// against malicious IPC requests.
DCHECK(request_handled);
}
CHECK(false); // This routine must not return
NOTREACHED();
}
9 changes: 7 additions & 2 deletions components/nacl/common/nacl_helper_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
// constants used to implement communication between the nacl_helper
// process and the Chrome zygote.

#define kNaClMaxIPCMessageLength 2048

// Used by Helper to tell Zygote it has started successfully.
#define kNaClHelperStartupAck "NACLHELPER_OK"
// Used by Zygote to ask Helper to fork a new NaCl loader.
#define kNaClForkRequest "NACLFORK"

enum NaClZygoteIPCCommand {
kNaClForkRequest,
kNaClGetTerminationStatusRequest,
};

// The next set of constants define global Linux file descriptors.
// For communications between NaCl loader and browser.
Expand Down
Loading

0 comments on commit 7d9c172

Please sign in to comment.