// Copyright (c) 2012 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 "components/nacl/zygote/nacl_fork_delegate_linux.h" #include #include #include #include #include #include "base/basictypes.h" #include "base/command_line.h" #include "base/cpu.h" #include "base/files/file_path.h" #include "base/files/scoped_file.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" #include "base/path_service.h" #include "base/pickle.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/process/launch.h" #include "base/strings/string_split.h" #include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "build/build_config.h" #include "components/nacl/common/nacl_nonsfi_util.h" #include "components/nacl/common/nacl_paths.h" #include "components/nacl/common/nacl_switches.h" #include "components/nacl/loader/nacl_helper_linux.h" #include "content/public/common/content_descriptors.h" #include "content/public/common/content_switches.h" #include "sandbox/linux/services/namespace_sandbox.h" #include "sandbox/linux/suid/client/setuid_sandbox_client.h" #include "sandbox/linux/suid/client/setuid_sandbox_host.h" #include "sandbox/linux/suid/common/sandbox.h" namespace { // Note these need to match up with their counterparts in nacl_helper_linux.c // and nacl_helper_bootstrap_linux.c. const char kNaClHelperReservedAtZero[] = "--reserved_at_zero=0xXXXXXXXXXXXXXXXX"; const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX"; // This is an environment variable which controls which (if any) other // environment variables are passed through to NaCl processes. e.g., // NACL_ENV_PASSTHROUGH="PATH,CWD" would pass both $PATH and $CWD to the child // process. const char kNaClEnvPassthrough[] = "NACL_ENV_PASSTHROUGH"; char kNaClEnvPassthroughDelimiter = ','; // The following environment variables are always passed through if they exist // in the parent process. const char kNaClExeStderr[] = "NACL_EXE_STDERR"; const char kNaClExeStdout[] = "NACL_EXE_STDOUT"; const char kNaClVerbosity[] = "NACLVERBOSITY"; #if defined(ARCH_CPU_X86) bool NonZeroSegmentBaseIsSlow() { base::CPU cpuid; // Using a non-zero segment base is known to be very slow on Intel // Atom CPUs. See "Segmentation-based Memory Protection Mechanism // on Intel Atom Microarchitecture: Coding Optimizations" (Leonardo // Potenza, Intel). // // The following list of CPU model numbers is taken from: // "Intel 64 and IA-32 Architectures Software Developer's Manual" // (http://download.intel.com/products/processor/manual/325462.pdf), // "Table 35-1. CPUID Signature Values of DisplayFamily_DisplayModel" // (Volume 3C, 35-1), which contains: // "06_36H - Intel Atom S Processor Family // 06_1CH, 06_26H, 06_27H, 06_35, 06_36 - Intel Atom Processor Family" if (cpuid.family() == 6) { switch (cpuid.model()) { case 0x1c: case 0x26: case 0x27: case 0x35: case 0x36: return true; } } return false; } #endif // Send an IPC request on |ipc_channel|. The request is contained in // |request_pickle| and can have file descriptors attached in |attached_fds|. // |reply_data_buffer| must be allocated by the caller and will contain the // reply. The size of the reply will be written to |reply_size|. // This code assumes that only one thread can write to |ipc_channel| to make // requests. bool SendIPCRequestAndReadReply(int ipc_channel, const std::vector& attached_fds, const base::Pickle& request_pickle, char* reply_data_buffer, size_t reply_data_buffer_size, ssize_t* reply_size) { DCHECK_LE(static_cast(kNaClMaxIPCMessageLength), reply_data_buffer_size); DCHECK(reply_size); if (!base::UnixDomainSocket::SendMsg(ipc_channel, request_pickle.data(), request_pickle.size(), attached_fds)) { LOG(ERROR) << "SendIPCRequestAndReadReply: SendMsg failed"; return false; } // Then read the remote reply. ScopedVector received_fds; const ssize_t msg_len = base::UnixDomainSocket::RecvMsg(ipc_channel, reply_data_buffer, reply_data_buffer_size, &received_fds); if (msg_len <= 0) { LOG(ERROR) << "SendIPCRequestAndReadReply: RecvMsg failed"; return false; } *reply_size = msg_len; return true; } } // namespace. namespace nacl { void AddNaClZygoteForkDelegates( ScopedVector* delegates) { delegates->push_back(new NaClForkDelegate(false /* nonsfi_mode */)); delegates->push_back(new NaClForkDelegate(true /* nonsfi_mode */)); } NaClForkDelegate::NaClForkDelegate(bool nonsfi_mode) : nonsfi_mode_(nonsfi_mode), status_(kNaClHelperUnused), fd_(-1) { } void NaClForkDelegate::Init(const int sandboxdesc, const bool enable_layer1_sandbox) { VLOG(1) << "NaClForkDelegate::Init()"; // Only launch the non-SFI helper process if non-SFI mode is enabled. if (nonsfi_mode_ && !IsNonSFIModeEnabled()) { return; } // TODO(rickyz): Make IsSuidSandboxChild a static function. scoped_ptr setuid_sandbox_client( sandbox::SetuidSandboxClient::Create()); const bool using_setuid_sandbox = setuid_sandbox_client->IsSuidSandboxChild(); const bool using_namespace_sandbox = sandbox::NamespaceSandbox::InNewUserNamespace(); CHECK(!(using_setuid_sandbox && using_namespace_sandbox)); if (enable_layer1_sandbox) { CHECK(using_setuid_sandbox || using_namespace_sandbox); } scoped_ptr setuid_sandbox_host( sandbox::SetuidSandboxHost::Create()); // For communications between the NaCl loader process and // the browser process. int nacl_sandbox_descriptor = base::GlobalDescriptors::kBaseDescriptor + kSandboxIPCChannel; // Confirm a hard-wired assumption. DCHECK_EQ(sandboxdesc, nacl_sandbox_descriptor); int fds[2]; PCHECK(0 == socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds)); base::FileHandleMappingVector fds_to_map; fds_to_map.push_back(std::make_pair(fds[1], kNaClZygoteDescriptor)); fds_to_map.push_back(std::make_pair(sandboxdesc, nacl_sandbox_descriptor)); bool use_nacl_bootstrap = false; // For non-SFI mode, we do not use fixed address space. if (!nonsfi_mode_) { // Using nacl_helper_bootstrap is not necessary on x86-64 because // NaCl's x86-64 sandbox is not zero-address-based. Starting // nacl_helper through nacl_helper_bootstrap works on x86-64, but it // leaves nacl_helper_bootstrap mapped at a fixed address at the // bottom of the address space, which is undesirable because it // effectively defeats ASLR. #if defined(ARCH_CPU_X86_64) use_nacl_bootstrap = false; #elif defined(ARCH_CPU_X86) // Performance vs. security trade-off: We prefer using a // non-zero-address-based sandbox on x86-32 because it provides some // ASLR and so is more secure. However, on Atom CPUs, using a // non-zero segment base is very slow, so we use a zero-based // sandbox on those. use_nacl_bootstrap = NonZeroSegmentBaseIsSlow(); #else use_nacl_bootstrap = true; #endif } status_ = kNaClHelperUnused; base::FilePath helper_exe; base::FilePath helper_bootstrap_exe; if (!PathService::Get( nonsfi_mode_ ? nacl::FILE_NACL_HELPER_NONSFI : nacl::FILE_NACL_HELPER, &helper_exe)) { status_ = kNaClHelperMissing; } else if (use_nacl_bootstrap && !PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP, &helper_bootstrap_exe)) { status_ = kNaClHelperBootstrapMissing; } else if (RunningOnValgrind()) { status_ = kNaClHelperValgrind; } else { base::CommandLine::StringVector argv_to_launch; { base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM); if (use_nacl_bootstrap) cmd_line.SetProgram(helper_bootstrap_exe); else cmd_line.SetProgram(helper_exe); // Append any switches that need to be forwarded to the NaCl helper. static const char* kForwardSwitches[] = { switches::kAllowSandboxDebugging, switches::kDisableSeccompFilterSandbox, switches::kEnableNaClDebug, switches::kNaClDangerousNoSandboxNonSfi, switches::kNoSandbox, }; const base::CommandLine& current_cmd_line = *base::CommandLine::ForCurrentProcess(); cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches, arraysize(kForwardSwitches)); // The command line needs to be tightly controlled to use // |helper_bootstrap_exe|. So from now on, argv_to_launch should be // modified directly. argv_to_launch = cmd_line.argv(); } if (use_nacl_bootstrap) { // Arguments to the bootstrap helper which need to be at the start // of the command line, right after the helper's path. base::CommandLine::StringVector bootstrap_prepend; bootstrap_prepend.push_back(helper_exe.value()); bootstrap_prepend.push_back(kNaClHelperReservedAtZero); bootstrap_prepend.push_back(kNaClHelperRDebug); argv_to_launch.insert(argv_to_launch.begin() + 1, bootstrap_prepend.begin(), bootstrap_prepend.end()); } base::LaunchOptions options; base::ScopedFD dummy_fd; if (using_setuid_sandbox) { // NaCl needs to keep tight control of the cmd_line, so prepend the // setuid sandbox wrapper manually. base::FilePath sandbox_path = setuid_sandbox_host->GetSandboxBinaryPath(); argv_to_launch.insert(argv_to_launch.begin(), sandbox_path.value()); setuid_sandbox_host->SetupLaunchOptions(&options, &fds_to_map, &dummy_fd); setuid_sandbox_host->SetupLaunchEnvironment(); } options.fds_to_remap = &fds_to_map; // The NaCl processes spawned may need to exceed the ambient soft limit // on RLIMIT_AS to allocate the untrusted address space and its guard // regions. The nacl_helper itself cannot just raise its own limit, // because the existing limit may prevent the initial exec of // nacl_helper_bootstrap from succeeding, with its large address space // reservation. std::vector max_these_limits; max_these_limits.push_back(RLIMIT_AS); options.maximize_rlimits = &max_these_limits; // To avoid information leaks in Non-SFI mode, clear the environment for // the NaCl Helper process. options.clear_environ = true; AddPassthroughEnvToOptions(&options); base::Process process = using_namespace_sandbox ? sandbox::NamespaceSandbox::LaunchProcess(argv_to_launch, options) : base::LaunchProcess(argv_to_launch, options); if (!process.IsValid()) status_ = kNaClHelperLaunchFailed; // parent and error cases are handled below if (using_setuid_sandbox) { // Sanity check that dummy_fd was kept alive for LaunchProcess. DCHECK(dummy_fd.is_valid()); } } if (IGNORE_EINTR(close(fds[1])) != 0) LOG(ERROR) << "close(fds[1]) failed"; if (status_ == kNaClHelperUnused) { const ssize_t kExpectedLength = strlen(kNaClHelperStartupAck); char buf[kExpectedLength]; // Wait for ack from nacl_helper, indicating it is ready to help const ssize_t nread = HANDLE_EINTR(read(fds[0], buf, sizeof(buf))); if (nread == kExpectedLength && memcmp(buf, kNaClHelperStartupAck, nread) == 0) { // all is well status_ = kNaClHelperSuccess; fd_ = fds[0]; return; } status_ = kNaClHelperAckFailed; LOG(ERROR) << "Bad NaCl helper startup ack (" << nread << " bytes)"; } // TODO(bradchen): Make this LOG(ERROR) when the NaCl helper // becomes the default. fd_ = -1; if (IGNORE_EINTR(close(fds[0])) != 0) LOG(ERROR) << "close(fds[0]) failed"; } void NaClForkDelegate::InitialUMA(std::string* uma_name, int* uma_sample, int* uma_boundary_value) { *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.InitState" : "NaCl.Client.Helper.InitState"; *uma_sample = status_; *uma_boundary_value = kNaClHelperStatusBoundary; } NaClForkDelegate::~NaClForkDelegate() { // side effect of close: delegate process will terminate if (status_ == kNaClHelperSuccess) { if (IGNORE_EINTR(close(fd_)) != 0) LOG(ERROR) << "close(fd_) failed"; } } bool NaClForkDelegate::CanHelp(const std::string& process_type, std::string* uma_name, int* uma_sample, int* uma_boundary_value) { // We can only help with a specific process type depending on nonsfi_mode_. const char* helpable_process_type = nonsfi_mode_ ? switches::kNaClLoaderNonSfiProcess : switches::kNaClLoaderProcess; if (process_type != helpable_process_type) return false; *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.StateOnFork" : "NaCl.Client.Helper.StateOnFork"; *uma_sample = status_; *uma_boundary_value = kNaClHelperStatusBoundary; return true; } pid_t NaClForkDelegate::Fork(const std::string& process_type, const std::vector& fds, const std::string& channel_id) { VLOG(1) << "NaClForkDelegate::Fork"; DCHECK(fds.size() == kNumPassedFDs); if (status_ != kNaClHelperSuccess) { LOG(ERROR) << "Cannot launch NaCl process: nacl_helper failed to start"; return -1; } // First, send a remote fork request. base::Pickle write_pickle; write_pickle.WriteInt(nacl::kNaClForkRequest); // TODO(hamaji): When we split the helper binary for non-SFI mode // from nacl_helper, stop sending this information. write_pickle.WriteBool(nonsfi_mode_); write_pickle.WriteString(channel_id); char reply_buf[kNaClMaxIPCMessageLength]; ssize_t reply_size = 0; bool got_reply = SendIPCRequestAndReadReply(fd_, fds, write_pickle, reply_buf, sizeof(reply_buf), &reply_size); if (!got_reply) { LOG(ERROR) << "Could not perform remote fork."; return -1; } // Now see if the other end managed to fork. base::Pickle reply_pickle(reply_buf, reply_size); base::PickleIterator iter(reply_pickle); pid_t nacl_child; if (!iter.ReadInt(&nacl_child)) { LOG(ERROR) << "NaClForkDelegate::Fork: pickle failed"; return -1; } VLOG(1) << "nacl_child is " << nacl_child; return nacl_child; } bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead, base::TerminationStatus* status, int* exit_code) { VLOG(1) << "NaClForkDelegate::GetTerminationStatus"; DCHECK(status); DCHECK(exit_code); base::Pickle write_pickle; write_pickle.WriteInt(nacl::kNaClGetTerminationStatusRequest); write_pickle.WriteInt(pid); write_pickle.WriteBool(known_dead); const std::vector empty_fds; char reply_buf[kNaClMaxIPCMessageLength]; ssize_t reply_size = 0; bool got_reply = SendIPCRequestAndReadReply(fd_, empty_fds, write_pickle, reply_buf, sizeof(reply_buf), &reply_size); if (!got_reply) { LOG(ERROR) << "Could not perform remote GetTerminationStatus."; return false; } base::Pickle reply_pickle(reply_buf, reply_size); base::PickleIterator iter(reply_pickle); int termination_status; if (!iter.ReadInt(&termination_status) || termination_status < 0 || termination_status >= base::TERMINATION_STATUS_MAX_ENUM) { LOG(ERROR) << "GetTerminationStatus: pickle failed"; return false; } int remote_exit_code; if (!iter.ReadInt(&remote_exit_code)) { LOG(ERROR) << "GetTerminationStatus: pickle failed"; return false; } *status = static_cast(termination_status); *exit_code = remote_exit_code; return true; } // static void NaClForkDelegate::AddPassthroughEnvToOptions( base::LaunchOptions* options) { scoped_ptr env(base::Environment::Create()); std::string pass_through_string; std::vector pass_through_vars; if (env->GetVar(kNaClEnvPassthrough, &pass_through_string)) { pass_through_vars = base::SplitString( pass_through_string, std::string(1, kNaClEnvPassthroughDelimiter), base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); } pass_through_vars.push_back(kNaClExeStderr); pass_through_vars.push_back(kNaClExeStdout); pass_through_vars.push_back(kNaClVerbosity); pass_through_vars.push_back(sandbox::kSandboxEnvironmentApiRequest); for (size_t i = 0; i < pass_through_vars.size(); ++i) { std::string temp; if (env->GetVar(pass_through_vars[i].c_str(), &temp)) options->environ[pass_through_vars[i]] = temp; } } } // namespace nacl