1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "components/nacl/zygote/nacl_fork_delegate_linux.h" 6 7 #include <signal.h> 8 #include <stdlib.h> 9 #include <sys/resource.h> 10 #include <sys/socket.h> 11 12 #include <set> 13 14 #include "base/basictypes.h" 15 #include "base/command_line.h" 16 #include "base/cpu.h" 17 #include "base/files/file_path.h" 18 #include "base/files/scoped_file.h" 19 #include "base/logging.h" 20 #include "base/memory/scoped_ptr.h" 21 #include "base/memory/scoped_vector.h" 22 #include "base/path_service.h" 23 #include "base/pickle.h" 24 #include "base/posix/eintr_wrapper.h" 25 #include "base/posix/global_descriptors.h" 26 #include "base/posix/unix_domain_socket_linux.h" 27 #include "base/process/kill.h" 28 #include "base/process/launch.h" 29 #include "base/strings/string_split.h" 30 #include "base/third_party/dynamic_annotations/dynamic_annotations.h" 31 #include "build/build_config.h" 32 #include "components/nacl/common/nacl_nonsfi_util.h" 33 #include "components/nacl/common/nacl_paths.h" 34 #include "components/nacl/common/nacl_switches.h" 35 #include "components/nacl/loader/nacl_helper_linux.h" 36 #include "content/public/common/content_descriptors.h" 37 #include "content/public/common/content_switches.h" 38 #include "sandbox/linux/suid/client/setuid_sandbox_client.h" 39 #include "sandbox/linux/suid/common/sandbox.h" 40 41 namespace { 42 43 // Note these need to match up with their counterparts in nacl_helper_linux.c 44 // and nacl_helper_bootstrap_linux.c. 45 const char kNaClHelperReservedAtZero[] = 46 "--reserved_at_zero=0xXXXXXXXXXXXXXXXX"; 47 const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX"; 48 49 // This is an environment variable which controls which (if any) other 50 // environment variables are passed through to NaCl processes. e.g., 51 // NACL_ENV_PASSTHROUGH="PATH,CWD" would pass both $PATH and $CWD to the child 52 // process. 53 const char kNaClEnvPassthrough[] = "NACL_ENV_PASSTHROUGH"; 54 char kNaClEnvPassthroughDelimiter = ','; 55 56 // The following environment variables are always passed through if they exist 57 // in the parent process. 58 const char kNaClExeStderr[] = "NACL_EXE_STDERR"; 59 const char kNaClExeStdout[] = "NACL_EXE_STDOUT"; 60 const char kNaClVerbosity[] = "NACLVERBOSITY"; 61 62 #if defined(ARCH_CPU_X86) 63 bool NonZeroSegmentBaseIsSlow() { 64 base::CPU cpuid; 65 // Using a non-zero segment base is known to be very slow on Intel 66 // Atom CPUs. See "Segmentation-based Memory Protection Mechanism 67 // on Intel Atom Microarchitecture: Coding Optimizations" (Leonardo 68 // Potenza, Intel). 69 // 70 // The following list of CPU model numbers is taken from: 71 // "Intel 64 and IA-32 Architectures Software Developer's Manual" 72 // (http://download.intel.com/products/processor/manual/325462.pdf), 73 // "Table 35-1. CPUID Signature Values of DisplayFamily_DisplayModel" 74 // (Volume 3C, 35-1), which contains: 75 // "06_36H - Intel Atom S Processor Family 76 // 06_1CH, 06_26H, 06_27H, 06_35, 06_36 - Intel Atom Processor Family" 77 if (cpuid.family() == 6) { 78 switch (cpuid.model()) { 79 case 0x1c: 80 case 0x26: 81 case 0x27: 82 case 0x35: 83 case 0x36: 84 return true; 85 } 86 } 87 return false; 88 } 89 #endif 90 91 // Send an IPC request on |ipc_channel|. The request is contained in 92 // |request_pickle| and can have file descriptors attached in |attached_fds|. 93 // |reply_data_buffer| must be allocated by the caller and will contain the 94 // reply. The size of the reply will be written to |reply_size|. 95 // This code assumes that only one thread can write to |ipc_channel| to make 96 // requests. 97 bool SendIPCRequestAndReadReply(int ipc_channel, 98 const std::vector<int>& attached_fds, 99 const Pickle& request_pickle, 100 char* reply_data_buffer, 101 size_t reply_data_buffer_size, 102 ssize_t* reply_size) { 103 DCHECK_LE(static_cast<size_t>(kNaClMaxIPCMessageLength), 104 reply_data_buffer_size); 105 DCHECK(reply_size); 106 107 if (!UnixDomainSocket::SendMsg(ipc_channel, request_pickle.data(), 108 request_pickle.size(), attached_fds)) { 109 LOG(ERROR) << "SendIPCRequestAndReadReply: SendMsg failed"; 110 return false; 111 } 112 113 // Then read the remote reply. 114 ScopedVector<base::ScopedFD> received_fds; 115 const ssize_t msg_len = 116 UnixDomainSocket::RecvMsg(ipc_channel, reply_data_buffer, 117 reply_data_buffer_size, &received_fds); 118 if (msg_len <= 0) { 119 LOG(ERROR) << "SendIPCRequestAndReadReply: RecvMsg failed"; 120 return false; 121 } 122 *reply_size = msg_len; 123 return true; 124 } 125 126 } // namespace. 127 128 namespace nacl { 129 130 void AddNaClZygoteForkDelegates( 131 ScopedVector<content::ZygoteForkDelegate>* delegates) { 132 delegates->push_back(new NaClForkDelegate(false /* nonsfi_mode */)); 133 delegates->push_back(new NaClForkDelegate(true /* nonsfi_mode */)); 134 } 135 136 NaClForkDelegate::NaClForkDelegate(bool nonsfi_mode) 137 : nonsfi_mode_(nonsfi_mode), status_(kNaClHelperUnused), fd_(-1) { 138 } 139 140 void NaClForkDelegate::Init(const int sandboxdesc, 141 const bool enable_layer1_sandbox) { 142 VLOG(1) << "NaClForkDelegate::Init()"; 143 144 // Only launch the non-SFI helper process if non-SFI mode is enabled. 145 if (nonsfi_mode_ && !IsNonSFIModeEnabled()) { 146 return; 147 } 148 149 scoped_ptr<sandbox::SetuidSandboxClient> setuid_sandbox_client( 150 sandbox::SetuidSandboxClient::Create()); 151 152 // For communications between the NaCl loader process and 153 // the SUID sandbox. 154 int nacl_sandbox_descriptor = 155 base::GlobalDescriptors::kBaseDescriptor + kSandboxIPCChannel; 156 // Confirm a hard-wired assumption. 157 DCHECK_EQ(sandboxdesc, nacl_sandbox_descriptor); 158 159 int fds[2]; 160 PCHECK(0 == socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds)); 161 base::FileHandleMappingVector fds_to_map; 162 fds_to_map.push_back(std::make_pair(fds[1], kNaClZygoteDescriptor)); 163 fds_to_map.push_back(std::make_pair(sandboxdesc, nacl_sandbox_descriptor)); 164 165 bool use_nacl_bootstrap = false; 166 // For non-SFI mode, we do not use fixed address space. 167 if (!nonsfi_mode_) { 168 // Using nacl_helper_bootstrap is not necessary on x86-64 because 169 // NaCl's x86-64 sandbox is not zero-address-based. Starting 170 // nacl_helper through nacl_helper_bootstrap works on x86-64, but it 171 // leaves nacl_helper_bootstrap mapped at a fixed address at the 172 // bottom of the address space, which is undesirable because it 173 // effectively defeats ASLR. 174 #if defined(ARCH_CPU_X86_64) 175 use_nacl_bootstrap = false; 176 #elif defined(ARCH_CPU_X86) 177 // Performance vs. security trade-off: We prefer using a 178 // non-zero-address-based sandbox on x86-32 because it provides some 179 // ASLR and so is more secure. However, on Atom CPUs, using a 180 // non-zero segment base is very slow, so we use a zero-based 181 // sandbox on those. 182 use_nacl_bootstrap = NonZeroSegmentBaseIsSlow(); 183 #else 184 use_nacl_bootstrap = true; 185 #endif 186 } 187 188 status_ = kNaClHelperUnused; 189 base::FilePath helper_exe; 190 base::FilePath helper_bootstrap_exe; 191 if (!PathService::Get(nacl::FILE_NACL_HELPER, &helper_exe)) { 192 status_ = kNaClHelperMissing; 193 } else if (use_nacl_bootstrap && 194 !PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP, 195 &helper_bootstrap_exe)) { 196 status_ = kNaClHelperBootstrapMissing; 197 } else if (RunningOnValgrind()) { 198 status_ = kNaClHelperValgrind; 199 } else { 200 CommandLine::StringVector argv_to_launch; 201 { 202 CommandLine cmd_line(CommandLine::NO_PROGRAM); 203 if (use_nacl_bootstrap) 204 cmd_line.SetProgram(helper_bootstrap_exe); 205 else 206 cmd_line.SetProgram(helper_exe); 207 208 // Append any switches that need to be forwarded to the NaCl helper. 209 static const char* kForwardSwitches[] = { 210 switches::kDisableSeccompFilterSandbox, 211 switches::kNaClDangerousNoSandboxNonSfi, 212 switches::kNoSandbox, 213 }; 214 const CommandLine& current_cmd_line = *CommandLine::ForCurrentProcess(); 215 cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches, 216 arraysize(kForwardSwitches)); 217 218 // The command line needs to be tightly controlled to use 219 // |helper_bootstrap_exe|. So from now on, argv_to_launch should be 220 // modified directly. 221 argv_to_launch = cmd_line.argv(); 222 } 223 if (use_nacl_bootstrap) { 224 // Arguments to the bootstrap helper which need to be at the start 225 // of the command line, right after the helper's path. 226 CommandLine::StringVector bootstrap_prepend; 227 bootstrap_prepend.push_back(helper_exe.value()); 228 bootstrap_prepend.push_back(kNaClHelperReservedAtZero); 229 bootstrap_prepend.push_back(kNaClHelperRDebug); 230 argv_to_launch.insert(argv_to_launch.begin() + 1, 231 bootstrap_prepend.begin(), 232 bootstrap_prepend.end()); 233 } 234 235 base::LaunchOptions options; 236 237 base::ScopedFD dummy_fd; 238 if (enable_layer1_sandbox) { 239 // NaCl needs to keep tight control of the cmd_line, so prepend the 240 // setuid sandbox wrapper manually. 241 base::FilePath sandbox_path = 242 setuid_sandbox_client->GetSandboxBinaryPath(); 243 argv_to_launch.insert(argv_to_launch.begin(), sandbox_path.value()); 244 setuid_sandbox_client->SetupLaunchOptions( 245 &options, &fds_to_map, &dummy_fd); 246 setuid_sandbox_client->SetupLaunchEnvironment(); 247 } 248 249 options.fds_to_remap = &fds_to_map; 250 251 // The NaCl processes spawned may need to exceed the ambient soft limit 252 // on RLIMIT_AS to allocate the untrusted address space and its guard 253 // regions. The nacl_helper itself cannot just raise its own limit, 254 // because the existing limit may prevent the initial exec of 255 // nacl_helper_bootstrap from succeeding, with its large address space 256 // reservation. 257 std::vector<int> max_these_limits; 258 max_these_limits.push_back(RLIMIT_AS); 259 options.maximize_rlimits = &max_these_limits; 260 261 // To avoid information leaks in Non-SFI mode, clear the environment for 262 // the NaCl Helper process. 263 options.clear_environ = true; 264 AddPassthroughEnvToOptions(&options); 265 266 if (!base::LaunchProcess(argv_to_launch, options, NULL)) 267 status_ = kNaClHelperLaunchFailed; 268 // parent and error cases are handled below 269 270 if (enable_layer1_sandbox) { 271 // Sanity check that dummy_fd was kept alive for LaunchProcess. 272 DCHECK(dummy_fd.is_valid()); 273 } 274 } 275 if (IGNORE_EINTR(close(fds[1])) != 0) 276 LOG(ERROR) << "close(fds[1]) failed"; 277 if (status_ == kNaClHelperUnused) { 278 const ssize_t kExpectedLength = strlen(kNaClHelperStartupAck); 279 char buf[kExpectedLength]; 280 281 // Wait for ack from nacl_helper, indicating it is ready to help 282 const ssize_t nread = HANDLE_EINTR(read(fds[0], buf, sizeof(buf))); 283 if (nread == kExpectedLength && 284 memcmp(buf, kNaClHelperStartupAck, nread) == 0) { 285 // all is well 286 status_ = kNaClHelperSuccess; 287 fd_ = fds[0]; 288 return; 289 } 290 291 status_ = kNaClHelperAckFailed; 292 LOG(ERROR) << "Bad NaCl helper startup ack (" << nread << " bytes)"; 293 } 294 // TODO(bradchen): Make this LOG(ERROR) when the NaCl helper 295 // becomes the default. 296 fd_ = -1; 297 if (IGNORE_EINTR(close(fds[0])) != 0) 298 LOG(ERROR) << "close(fds[0]) failed"; 299 } 300 301 void NaClForkDelegate::InitialUMA(std::string* uma_name, 302 int* uma_sample, 303 int* uma_boundary_value) { 304 *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.InitState" 305 : "NaCl.Client.Helper.InitState"; 306 *uma_sample = status_; 307 *uma_boundary_value = kNaClHelperStatusBoundary; 308 } 309 310 NaClForkDelegate::~NaClForkDelegate() { 311 // side effect of close: delegate process will terminate 312 if (status_ == kNaClHelperSuccess) { 313 if (IGNORE_EINTR(close(fd_)) != 0) 314 LOG(ERROR) << "close(fd_) failed"; 315 } 316 } 317 318 bool NaClForkDelegate::CanHelp(const std::string& process_type, 319 std::string* uma_name, 320 int* uma_sample, 321 int* uma_boundary_value) { 322 // We can only help with a specific process type depending on nonsfi_mode_. 323 const char* helpable_process_type = nonsfi_mode_ 324 ? switches::kNaClLoaderNonSfiProcess 325 : switches::kNaClLoaderProcess; 326 if (process_type != helpable_process_type) 327 return false; 328 *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.StateOnFork" 329 : "NaCl.Client.Helper.StateOnFork"; 330 *uma_sample = status_; 331 *uma_boundary_value = kNaClHelperStatusBoundary; 332 return true; 333 } 334 335 pid_t NaClForkDelegate::Fork(const std::string& process_type, 336 const std::vector<int>& fds, 337 const std::string& channel_id) { 338 VLOG(1) << "NaClForkDelegate::Fork"; 339 340 DCHECK(fds.size() == kNumPassedFDs); 341 342 if (status_ != kNaClHelperSuccess) { 343 LOG(ERROR) << "Cannot launch NaCl process: nacl_helper failed to start"; 344 return -1; 345 } 346 347 // First, send a remote fork request. 348 Pickle write_pickle; 349 write_pickle.WriteInt(nacl::kNaClForkRequest); 350 // TODO(hamaji): When we split the helper binary for non-SFI mode 351 // from nacl_helper, stop sending this information. 352 write_pickle.WriteBool(nonsfi_mode_); 353 write_pickle.WriteString(channel_id); 354 355 char reply_buf[kNaClMaxIPCMessageLength]; 356 ssize_t reply_size = 0; 357 bool got_reply = 358 SendIPCRequestAndReadReply(fd_, fds, write_pickle, 359 reply_buf, sizeof(reply_buf), &reply_size); 360 if (!got_reply) { 361 LOG(ERROR) << "Could not perform remote fork."; 362 return -1; 363 } 364 365 // Now see if the other end managed to fork. 366 Pickle reply_pickle(reply_buf, reply_size); 367 PickleIterator iter(reply_pickle); 368 pid_t nacl_child; 369 if (!iter.ReadInt(&nacl_child)) { 370 LOG(ERROR) << "NaClForkDelegate::Fork: pickle failed"; 371 return -1; 372 } 373 VLOG(1) << "nacl_child is " << nacl_child; 374 return nacl_child; 375 } 376 377 bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead, 378 base::TerminationStatus* status, 379 int* exit_code) { 380 VLOG(1) << "NaClForkDelegate::GetTerminationStatus"; 381 DCHECK(status); 382 DCHECK(exit_code); 383 384 Pickle write_pickle; 385 write_pickle.WriteInt(nacl::kNaClGetTerminationStatusRequest); 386 write_pickle.WriteInt(pid); 387 write_pickle.WriteBool(known_dead); 388 389 const std::vector<int> empty_fds; 390 char reply_buf[kNaClMaxIPCMessageLength]; 391 ssize_t reply_size = 0; 392 bool got_reply = 393 SendIPCRequestAndReadReply(fd_, empty_fds, write_pickle, 394 reply_buf, sizeof(reply_buf), &reply_size); 395 if (!got_reply) { 396 LOG(ERROR) << "Could not perform remote GetTerminationStatus."; 397 return false; 398 } 399 400 Pickle reply_pickle(reply_buf, reply_size); 401 PickleIterator iter(reply_pickle); 402 int termination_status; 403 if (!iter.ReadInt(&termination_status) || 404 termination_status < 0 || 405 termination_status >= base::TERMINATION_STATUS_MAX_ENUM) { 406 LOG(ERROR) << "GetTerminationStatus: pickle failed"; 407 return false; 408 } 409 410 int remote_exit_code; 411 if (!iter.ReadInt(&remote_exit_code)) { 412 LOG(ERROR) << "GetTerminationStatus: pickle failed"; 413 return false; 414 } 415 416 *status = static_cast<base::TerminationStatus>(termination_status); 417 *exit_code = remote_exit_code; 418 return true; 419 } 420 421 // static 422 void NaClForkDelegate::AddPassthroughEnvToOptions( 423 base::LaunchOptions* options) { 424 scoped_ptr<base::Environment> env(base::Environment::Create()); 425 std::string pass_through_string; 426 std::vector<std::string> pass_through_vars; 427 if (env->GetVar(kNaClEnvPassthrough, &pass_through_string)) { 428 base::SplitString( 429 pass_through_string, kNaClEnvPassthroughDelimiter, &pass_through_vars); 430 } 431 pass_through_vars.push_back(kNaClExeStderr); 432 pass_through_vars.push_back(kNaClExeStdout); 433 pass_through_vars.push_back(kNaClVerbosity); 434 pass_through_vars.push_back(sandbox::kSandboxEnvironmentApiRequest); 435 for (size_t i = 0; i < pass_through_vars.size(); ++i) { 436 std::string temp; 437 if (env->GetVar(pass_through_vars[i].c_str(), &temp)) 438 options->environ[pass_through_vars[i]] = temp; 439 } 440 } 441 442 } // namespace nacl 443