1 // Copyright 2016 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 "services/service_manager/service_process_launcher.h"
6
7 #include <utility>
8
9 #include "base/base_paths.h"
10 #include "base/base_switches.h"
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/feature_list.h"
14 #include "base/location.h"
15 #include "base/logging.h"
16 #include "base/memory/ptr_util.h"
17 #include "base/path_service.h"
18 #include "base/process/kill.h"
19 #include "base/process/launch.h"
20 #include "base/rand_util.h"
21 #include "base/run_loop.h"
22 #include "base/sequence_checker.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/synchronization/lock.h"
25 #include "base/task/post_task.h"
26 #include "base/task/thread_pool.h"
27 #include "base/task_runner_util.h"
28 #include "base/threading/thread.h"
29 #include "build/build_config.h"
30 #include "build/chromeos_buildflags.h"
31 #include "mojo/public/cpp/bindings/interface_ptr_info.h"
32 #include "mojo/public/cpp/platform/platform_channel.h"
33 #include "mojo/public/cpp/system/core.h"
34 #include "mojo/public/cpp/system/invitation.h"
35 #include "sandbox/policy/switches.h"
36 #include "services/service_manager/public/cpp/service_executable/switches.h"
37 #include "services/service_manager/public/mojom/service.mojom.h"
38 #include "services/service_manager/switches.h"
39
40 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
41 #include "sandbox/linux/services/namespace_sandbox.h"
42 #endif
43
44 #if defined(OS_WIN)
45 #include "base/win/windows_version.h"
46 #endif
47
48 namespace service_manager {
49
50 // Thread-safe owner of state related to a service process. This facilitates
51 // safely scheduling the launching and joining of a service process in the
52 // background.
53 class ServiceProcessLauncher::ProcessState
54 : public base::RefCountedThreadSafe<ProcessState> {
55 public:
ProcessState()56 ProcessState() { DETACH_FROM_SEQUENCE(sequence_checker_); }
57
58 base::ProcessId LaunchInBackground(
59 const Identity& target,
60 sandbox::policy::SandboxType sandbox_type,
61 std::unique_ptr<base::CommandLine> child_command_line,
62 mojo::PlatformChannel::HandlePassingInfo handle_passing_info,
63 mojo::PlatformChannel channel,
64 mojo::OutgoingInvitation invitation);
65
66 void StopInBackground();
67
68 private:
69 friend class base::RefCountedThreadSafe<ProcessState>;
70
71 ~ProcessState() = default;
72
73 base::Process child_process_;
74 SEQUENCE_CHECKER(sequence_checker_);
75
76 DISALLOW_COPY_AND_ASSIGN(ProcessState);
77 };
78
ServiceProcessLauncher(ServiceProcessLauncherDelegate * delegate,const base::FilePath & service_path)79 ServiceProcessLauncher::ServiceProcessLauncher(
80 ServiceProcessLauncherDelegate* delegate,
81 const base::FilePath& service_path)
82 : delegate_(delegate),
83 service_path_(service_path.empty()
84 ? base::CommandLine::ForCurrentProcess()->GetProgram()
85 : service_path),
86 background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
87 {base::TaskPriority::USER_VISIBLE, base::WithBaseSyncPrimitives(),
88 base::MayBlock()})) {}
89
~ServiceProcessLauncher()90 ServiceProcessLauncher::~ServiceProcessLauncher() {
91 // We don't really need to wait for the process to be joined, as long as it
92 // eventually happens. Schedule a task to do it, and move on.
93 background_task_runner_->PostTask(
94 FROM_HERE, base::BindOnce(&ProcessState::StopInBackground, state_));
95 }
96
Start(const Identity & target,sandbox::policy::SandboxType sandbox_type,ProcessReadyCallback callback)97 mojo::PendingRemote<mojom::Service> ServiceProcessLauncher::Start(
98 const Identity& target,
99 sandbox::policy::SandboxType sandbox_type,
100 ProcessReadyCallback callback) {
101 DCHECK(!state_);
102
103 const base::CommandLine& parent_command_line =
104 *base::CommandLine::ForCurrentProcess();
105
106 std::unique_ptr<base::CommandLine> child_command_line(
107 new base::CommandLine(service_path_));
108
109 child_command_line->AppendArguments(parent_command_line, false);
110
111 // Add enabled/disabled features from base::FeatureList. These will take
112 // precedence over existing ones (if there is any copied from the
113 // |parent_command_line| above) as they appear later in the arguments list.
114 std::string enabled_features;
115 std::string disabled_features;
116 base::FeatureList::GetInstance()->GetFeatureOverrides(&enabled_features,
117 &disabled_features);
118 if (!enabled_features.empty()) {
119 child_command_line->AppendSwitchASCII(::switches::kEnableFeatures,
120 enabled_features);
121 }
122 if (!disabled_features.empty()) {
123 child_command_line->AppendSwitchASCII(::switches::kDisableFeatures,
124 disabled_features);
125 }
126
127 child_command_line->AppendSwitchASCII(switches::kServiceName, target.name());
128 #ifndef NDEBUG
129 child_command_line->AppendSwitchASCII("g",
130 target.instance_group().ToString());
131 #endif
132
133 if (!IsUnsandboxedSandboxType(sandbox_type)) {
134 child_command_line->AppendSwitchASCII(
135 sandbox::policy::switches::kServiceSandboxType,
136 StringFromUtilitySandboxType(sandbox_type));
137 }
138
139 mojo::PlatformChannel::HandlePassingInfo handle_passing_info;
140 mojo::PlatformChannel channel;
141 channel.PrepareToPassRemoteEndpoint(&handle_passing_info,
142 child_command_line.get());
143 mojo::OutgoingInvitation invitation;
144 auto client =
145 PassServiceRequestOnCommandLine(&invitation, child_command_line.get());
146
147 if (delegate_) {
148 delegate_->AdjustCommandLineArgumentsForTarget(target,
149 child_command_line.get());
150 }
151
152 state_ = base::WrapRefCounted(new ProcessState);
153 base::PostTaskAndReplyWithResult(
154 background_task_runner_.get(), FROM_HERE,
155 base::BindOnce(&ProcessState::LaunchInBackground, state_, target,
156 sandbox_type, std::move(child_command_line),
157 std::move(handle_passing_info), std::move(channel),
158 std::move(invitation)),
159 std::move(callback));
160
161 return client;
162 }
163
164 // static
165 mojo::PendingRemote<mojom::Service>
PassServiceRequestOnCommandLine(mojo::OutgoingInvitation * invitation,base::CommandLine * command_line)166 ServiceProcessLauncher::PassServiceRequestOnCommandLine(
167 mojo::OutgoingInvitation* invitation,
168 base::CommandLine* command_line) {
169 const auto attachment_name = base::NumberToString(base::RandUint64());
170 command_line->AppendSwitchASCII(switches::kServiceRequestAttachmentName,
171 attachment_name);
172 return mojo::PendingRemote<mojom::Service>(
173 invitation->AttachMessagePipe(attachment_name), 0);
174 }
175
LaunchInBackground(const Identity & target,sandbox::policy::SandboxType sandbox_type,std::unique_ptr<base::CommandLine> child_command_line,mojo::PlatformChannel::HandlePassingInfo handle_passing_info,mojo::PlatformChannel channel,mojo::OutgoingInvitation invitation)176 base::ProcessId ServiceProcessLauncher::ProcessState::LaunchInBackground(
177 const Identity& target,
178 sandbox::policy::SandboxType sandbox_type,
179 std::unique_ptr<base::CommandLine> child_command_line,
180 mojo::PlatformChannel::HandlePassingInfo handle_passing_info,
181 mojo::PlatformChannel channel,
182 mojo::OutgoingInvitation invitation) {
183 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
184 base::LaunchOptions options;
185 #if defined(OS_WIN)
186 options.handles_to_inherit = handle_passing_info;
187 options.stdin_handle = INVALID_HANDLE_VALUE;
188 options.stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
189 options.stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
190 // Always inherit stdout/stderr as a pair.
191 if (!options.stdout_handle || !options.stdin_handle)
192 options.stdin_handle = options.stdout_handle = nullptr;
193
194 // Pseudo handles are used when stdout and stderr redirect to the console. In
195 // that case, they're automatically inherited by child processes. See
196 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682075.aspx
197 // Trying to add them to the list of handles to inherit causes CreateProcess
198 // to fail. When this process is launched from Python then a real handle is
199 // used. In that case, we do want to add it to the list of handles that is
200 // inherited.
201 if (options.stdout_handle &&
202 GetFileType(options.stdout_handle) != FILE_TYPE_CHAR) {
203 options.handles_to_inherit.push_back(options.stdout_handle);
204 }
205 if (options.stderr_handle &&
206 GetFileType(options.stderr_handle) != FILE_TYPE_CHAR &&
207 options.stdout_handle != options.stderr_handle) {
208 options.handles_to_inherit.push_back(options.stderr_handle);
209 }
210 #elif defined(OS_FUCHSIA)
211 // LaunchProcess will share stdin/out/err with the child process by default.
212 if (!IsUnsandboxedSandboxType(sandbox_type))
213 NOTIMPLEMENTED();
214 options.handles_to_transfer = std::move(handle_passing_info);
215 #elif defined(OS_POSIX)
216 const base::FileHandleMappingVector fd_mapping{
217 {STDIN_FILENO, STDIN_FILENO},
218 {STDOUT_FILENO, STDOUT_FILENO},
219 {STDERR_FILENO, STDERR_FILENO},
220 };
221 #if defined(OS_MAC)
222 options.fds_to_remap = fd_mapping;
223 options.mach_ports_for_rendezvous = handle_passing_info;
224 #else
225 handle_passing_info.insert(handle_passing_info.end(), fd_mapping.begin(),
226 fd_mapping.end());
227 options.fds_to_remap = handle_passing_info;
228 #endif // defined(OS_MAC)
229 #endif
230 DVLOG(2) << "Launching child with command line: "
231 << child_command_line->GetCommandLineString();
232 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
233 if (!IsUnsandboxedSandboxType(sandbox_type)) {
234 child_process_ =
235 sandbox::NamespaceSandbox::LaunchProcess(*child_command_line, options);
236 if (!child_process_.IsValid())
237 LOG(ERROR) << "Starting the process with a sandbox failed.";
238 } else
239 #endif
240 {
241 child_process_ = base::LaunchProcess(*child_command_line, options);
242 }
243
244 channel.RemoteProcessLaunchAttempted();
245 if (!child_process_.IsValid()) {
246 LOG(ERROR) << "Failed to start child process for service: "
247 << target.name();
248 return base::kNullProcessId;
249 }
250
251 #if BUILDFLAG(IS_ASH)
252 // Always log instead of DVLOG because knowing which pid maps to which
253 // service is vital for interpreting crashes after-the-fact and Chrome OS
254 // devices generally run release builds, even in development.
255 VLOG(0)
256 #else
257 DVLOG(0)
258 #endif
259 << "Launched child process pid=" << child_process_.Pid()
260 << " id=" << target.ToString();
261
262 mojo::OutgoingInvitation::Send(std::move(invitation), child_process_.Handle(),
263 channel.TakeLocalEndpoint());
264
265 return child_process_.Pid();
266 }
267
StopInBackground()268 void ServiceProcessLauncher::ProcessState::StopInBackground() {
269 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
270 if (!child_process_.IsValid())
271 return;
272
273 int rv = -1;
274 LOG_IF(ERROR, !child_process_.WaitForExit(&rv))
275 << "Failed to wait for child process";
276 child_process_.Close();
277 }
278
279 } // namespace service_manager
280