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 "content/browser/utility_process_host_impl.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/command_line.h"
10#include "base/lazy_instance.h"
11#include "base/message_loop/message_loop.h"
12#include "base/run_loop.h"
13#include "base/sequenced_task_runner.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/synchronization/lock.h"
16#include "base/synchronization/waitable_event.h"
17#include "content/browser/browser_child_process_host_impl.h"
18#include "content/browser/renderer_host/render_process_host_impl.h"
19#include "content/common/child_process_host_impl.h"
20#include "content/common/utility_messages.h"
21#include "content/public/browser/browser_thread.h"
22#include "content/public/browser/content_browser_client.h"
23#include "content/public/browser/utility_process_host_client.h"
24#include "content/public/common/content_switches.h"
25#include "content/public/common/process_type.h"
26#include "content/public/common/sandboxed_process_launcher_delegate.h"
27#include "ipc/ipc_switches.h"
28#include "ui/base/ui_base_switches.h"
29
30namespace content {
31
32// NOTE: changes to this class need to be reviewed by the security team.
33class UtilitySandboxedProcessLauncherDelegate
34    : public SandboxedProcessLauncherDelegate {
35 public:
36  UtilitySandboxedProcessLauncherDelegate(const base::FilePath& exposed_dir,
37                                          bool launch_elevated, bool no_sandbox,
38                                          base::EnvironmentMap& env,
39                                          ChildProcessHost* host)
40      : exposed_dir_(exposed_dir),
41#if defined(OS_WIN)
42        launch_elevated_(launch_elevated)
43#elif defined(OS_POSIX)
44        env_(env),
45        no_sandbox_(no_sandbox),
46        ipc_fd_(host->TakeClientFileDescriptor())
47#endif  // OS_WIN
48  {}
49
50  virtual ~UtilitySandboxedProcessLauncherDelegate() {}
51
52#if defined(OS_WIN)
53  virtual bool ShouldLaunchElevated() OVERRIDE {
54    return launch_elevated_;
55  }
56  virtual void PreSandbox(bool* disable_default_policy,
57                          base::FilePath* exposed_dir) OVERRIDE {
58    *exposed_dir = exposed_dir_;
59  }
60#elif defined(OS_POSIX)
61
62  virtual bool ShouldUseZygote() OVERRIDE {
63    return !no_sandbox_ && exposed_dir_.empty();
64  }
65  virtual base::EnvironmentMap GetEnvironment() OVERRIDE {
66    return env_;
67  }
68  virtual int GetIpcFd() OVERRIDE {
69    return ipc_fd_;
70  }
71#endif  // OS_WIN
72
73 private:
74
75 base::FilePath exposed_dir_;
76
77#if defined(OS_WIN)
78  bool launch_elevated_;
79#elif defined(OS_POSIX)
80  base::EnvironmentMap env_;
81  bool no_sandbox_;
82  int ipc_fd_;
83#endif  // OS_WIN
84};
85
86UtilityMainThreadFactoryFunction g_utility_main_thread_factory = NULL;
87
88UtilityProcessHost* UtilityProcessHost::Create(
89    const scoped_refptr<UtilityProcessHostClient>& client,
90    const scoped_refptr<base::SequencedTaskRunner>& client_task_runner) {
91  return new UtilityProcessHostImpl(client, client_task_runner);
92}
93
94void UtilityProcessHostImpl::RegisterUtilityMainThreadFactory(
95    UtilityMainThreadFactoryFunction create) {
96  g_utility_main_thread_factory = create;
97}
98
99UtilityProcessHostImpl::UtilityProcessHostImpl(
100    const scoped_refptr<UtilityProcessHostClient>& client,
101    const scoped_refptr<base::SequencedTaskRunner>& client_task_runner)
102    : client_(client),
103      client_task_runner_(client_task_runner),
104      is_batch_mode_(false),
105      is_mdns_enabled_(false),
106      no_sandbox_(false),
107      run_elevated_(false),
108#if defined(OS_LINUX)
109      child_flags_(ChildProcessHost::CHILD_ALLOW_SELF),
110#else
111      child_flags_(ChildProcessHost::CHILD_NORMAL),
112#endif
113      started_(false) {
114}
115
116UtilityProcessHostImpl::~UtilityProcessHostImpl() {
117  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
118  if (is_batch_mode_)
119    EndBatchMode();
120}
121
122bool UtilityProcessHostImpl::Send(IPC::Message* message) {
123  if (!StartProcess())
124    return false;
125
126  return process_->Send(message);
127}
128
129bool UtilityProcessHostImpl::StartBatchMode()  {
130  CHECK(!is_batch_mode_);
131  is_batch_mode_ = StartProcess();
132  Send(new UtilityMsg_BatchMode_Started());
133  return is_batch_mode_;
134}
135
136void UtilityProcessHostImpl::EndBatchMode()  {
137  CHECK(is_batch_mode_);
138  is_batch_mode_ = false;
139  Send(new UtilityMsg_BatchMode_Finished());
140}
141
142void UtilityProcessHostImpl::SetExposedDir(const base::FilePath& dir) {
143  exposed_dir_ = dir;
144}
145
146void UtilityProcessHostImpl::EnableMDns() {
147  is_mdns_enabled_ = true;
148}
149
150void UtilityProcessHostImpl::DisableSandbox() {
151  no_sandbox_ = true;
152}
153
154#if defined(OS_WIN)
155void UtilityProcessHostImpl::ElevatePrivileges() {
156  no_sandbox_ = true;
157  run_elevated_ = true;
158}
159#endif
160
161const ChildProcessData& UtilityProcessHostImpl::GetData() {
162  return process_->GetData();
163}
164
165#if defined(OS_POSIX)
166
167void UtilityProcessHostImpl::SetEnv(const base::EnvironmentMap& env) {
168  env_ = env;
169}
170
171#endif  // OS_POSIX
172
173bool UtilityProcessHostImpl::StartProcess() {
174  if (started_)
175    return true;
176  started_ = true;
177
178  if (is_batch_mode_)
179    return true;
180
181  // Name must be set or metrics_service will crash in any test which
182  // launches a UtilityProcessHost.
183  process_.reset(new BrowserChildProcessHostImpl(PROCESS_TYPE_UTILITY, this));
184  process_->SetName(base::ASCIIToUTF16("utility process"));
185
186  std::string channel_id = process_->GetHost()->CreateChannel();
187  if (channel_id.empty())
188    return false;
189
190  if (RenderProcessHost::run_renderer_in_process()) {
191    DCHECK(g_utility_main_thread_factory);
192    // See comment in RenderProcessHostImpl::Init() for the background on why we
193    // support single process mode this way.
194    in_process_thread_.reset(g_utility_main_thread_factory(channel_id));
195    in_process_thread_->Start();
196  } else {
197    const base::CommandLine& browser_command_line =
198        *base::CommandLine::ForCurrentProcess();
199    int child_flags = child_flags_;
200
201#if defined(OS_POSIX)
202    bool has_cmd_prefix = browser_command_line.HasSwitch(
203        switches::kUtilityCmdPrefix);
204
205    // When running under gdb, forking /proc/self/exe ends up forking the gdb
206    // executable instead of Chromium. It is almost safe to assume that no
207    // updates will happen while a developer is running with
208    // |switches::kUtilityCmdPrefix|. See ChildProcessHost::GetChildPath() for
209    // a similar case with Valgrind.
210    if (has_cmd_prefix)
211      child_flags = ChildProcessHost::CHILD_NORMAL;
212#endif
213
214    base::FilePath exe_path = ChildProcessHost::GetChildPath(child_flags);
215    if (exe_path.empty()) {
216      NOTREACHED() << "Unable to get utility process binary name.";
217      return false;
218    }
219
220    base::CommandLine* cmd_line = new base::CommandLine(exe_path);
221    cmd_line->AppendSwitchASCII(switches::kProcessType,
222                                switches::kUtilityProcess);
223    cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
224    std::string locale = GetContentClient()->browser()->GetApplicationLocale();
225    cmd_line->AppendSwitchASCII(switches::kLang, locale);
226
227    if (no_sandbox_ || browser_command_line.HasSwitch(switches::kNoSandbox))
228      cmd_line->AppendSwitch(switches::kNoSandbox);
229#if defined(OS_MACOSX)
230    if (browser_command_line.HasSwitch(switches::kEnableSandboxLogging))
231      cmd_line->AppendSwitch(switches::kEnableSandboxLogging);
232#endif
233    if (browser_command_line.HasSwitch(switches::kDebugPluginLoading))
234      cmd_line->AppendSwitch(switches::kDebugPluginLoading);
235
236#if defined(OS_POSIX)
237    if (has_cmd_prefix) {
238      // Launch the utility child process with some prefix
239      // (usually "xterm -e gdb --args").
240      cmd_line->PrependWrapper(browser_command_line.GetSwitchValueNative(
241          switches::kUtilityCmdPrefix));
242    }
243
244    if (!exposed_dir_.empty()) {
245      cmd_line->AppendSwitchPath(switches::kUtilityProcessAllowedDir,
246                                 exposed_dir_);
247    }
248#endif
249
250    if (is_mdns_enabled_)
251      cmd_line->AppendSwitch(switches::kUtilityProcessEnableMDns);
252
253#if defined(OS_WIN)
254    // Let the utility process know if it is intended to be elevated.
255    if (run_elevated_)
256      cmd_line->AppendSwitch(switches::kUtilityProcessRunningElevated);
257#endif
258
259    process_->Launch(
260        new UtilitySandboxedProcessLauncherDelegate(exposed_dir_,
261                                                    run_elevated_,
262                                                    no_sandbox_, env_,
263                                                    process_->GetHost()),
264        cmd_line);
265  }
266
267  return true;
268}
269
270bool UtilityProcessHostImpl::OnMessageReceived(const IPC::Message& message) {
271  if (!client_.get())
272    return true;
273
274  client_task_runner_->PostTask(
275      FROM_HERE,
276      base::Bind(
277          base::IgnoreResult(&UtilityProcessHostClient::OnMessageReceived),
278          client_.get(),
279          message));
280
281  return true;
282}
283
284void UtilityProcessHostImpl::OnProcessLaunchFailed() {
285  if (!client_.get())
286    return;
287
288  client_task_runner_->PostTask(
289      FROM_HERE,
290      base::Bind(&UtilityProcessHostClient::OnProcessLaunchFailed,
291                 client_.get()));
292}
293
294void UtilityProcessHostImpl::OnProcessCrashed(int exit_code) {
295  if (!client_.get())
296    return;
297
298  client_task_runner_->PostTask(
299      FROM_HERE,
300      base::Bind(&UtilityProcessHostClient::OnProcessCrashed, client_.get(),
301            exit_code));
302}
303
304}  // namespace content
305