1 // Copyright 2012 The Chromium Authors
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 "base/process/launch.h"
6
7 #include <fcntl.h>
8 #include <io.h>
9
10 // windows.h must be included before shellapi.h
11 #include <windows.h>
12
13 #include <psapi.h>
14 #include <shellapi.h>
15 #include <userenv.h>
16
17 #include <ios>
18 #include <limits>
19
20 #include "base/debug/alias.h"
21 #include "base/debug/stack_trace.h"
22 #include "base/functional/bind.h"
23 #include "base/functional/callback_helpers.h"
24 #include "base/logging.h"
25 #include "base/metrics/histogram.h"
26 #include "base/process/environment_internal.h"
27 #include "base/process/kill.h"
28 #include "base/strings/string_util.h"
29 #include "base/strings/utf_string_conversions.h"
30 #include "base/system/sys_info.h"
31 #include "base/threading/scoped_blocking_call.h"
32 #include "base/threading/scoped_thread_priority.h"
33 #include "base/trace_event/base_tracing.h"
34 #include "base/win/scoped_handle.h"
35 #include "base/win/scoped_process_information.h"
36 #include "base/win/startup_information.h"
37 #include "base/win/windows_version.h"
38
39 namespace base {
40
41 namespace {
42
GetAppOutputInternal(CommandLine::StringPieceType cl,bool include_stderr,std::string * output,int * exit_code)43 bool GetAppOutputInternal(CommandLine::StringPieceType cl,
44 bool include_stderr,
45 std::string* output,
46 int* exit_code) {
47 TRACE_EVENT0("base", "GetAppOutput");
48
49 HANDLE out_read = nullptr;
50 HANDLE out_write = nullptr;
51
52 SECURITY_ATTRIBUTES sa_attr;
53 // Set the bInheritHandle flag so pipe handles are inherited.
54 sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
55 sa_attr.bInheritHandle = TRUE;
56 sa_attr.lpSecurityDescriptor = nullptr;
57
58 // Create the pipe for the child process's STDOUT.
59 if (!CreatePipe(&out_read, &out_write, &sa_attr, 0)) {
60 DPLOG(ERROR) << "Failed to create pipe";
61 return false;
62 }
63
64 // Ensure we don't leak the handles.
65 win::ScopedHandle scoped_out_read(out_read);
66 win::ScopedHandle scoped_out_write(out_write);
67
68 // Ensure the read handles to the pipes are not inherited.
69 if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) {
70 DPLOG(ERROR) << "Failed to disabled pipe inheritance";
71 return false;
72 }
73
74 FilePath::StringType writable_command_line_string(cl);
75
76 STARTUPINFO start_info = {};
77
78 start_info.cb = sizeof(STARTUPINFO);
79 start_info.hStdOutput = out_write;
80 // Keep the normal stdin.
81 start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
82 if (include_stderr) {
83 start_info.hStdError = out_write;
84 } else {
85 start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
86 }
87 start_info.dwFlags |= STARTF_USESTDHANDLES;
88
89 // Create the child process.
90 PROCESS_INFORMATION temp_process_info = {};
91 if (!CreateProcess(nullptr, data(writable_command_line_string), nullptr,
92 nullptr,
93 TRUE, // Handles are inherited.
94 0, nullptr, nullptr, &start_info, &temp_process_info)) {
95 DPLOG(ERROR) << "Failed to start process";
96 return false;
97 }
98
99 win::ScopedProcessInformation proc_info(temp_process_info);
100
101 // Close our writing end of pipe now. Otherwise later read would not be able
102 // to detect end of child's output.
103 scoped_out_write.Close();
104
105 // Read output from the child process's pipe for STDOUT
106 const int kBufferSize = 1024;
107 char buffer[kBufferSize];
108
109 for (;;) {
110 DWORD bytes_read = 0;
111 BOOL success =
112 ::ReadFile(out_read, buffer, kBufferSize, &bytes_read, nullptr);
113 if (!success || bytes_read == 0)
114 break;
115 output->append(buffer, bytes_read);
116 }
117
118 // Let's wait for the process to finish.
119 {
120 // It is okay to allow this process to wait on the launched process as a
121 // process launched with GetAppOutput*() shouldn't wait back on the process
122 // that launched it.
123 internal::GetAppOutputScopedAllowBaseSyncPrimitives allow_wait;
124 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
125 WaitForSingleObject(proc_info.process_handle(), INFINITE);
126 }
127
128 TerminationStatus status =
129 GetTerminationStatus(proc_info.process_handle(), exit_code);
130 return status != TERMINATION_STATUS_PROCESS_CRASHED &&
131 status != TERMINATION_STATUS_ABNORMAL_TERMINATION;
132 }
133
LaunchElevatedProcess(const CommandLine & cmdline,bool start_hidden,bool wait)134 Process LaunchElevatedProcess(const CommandLine& cmdline,
135 bool start_hidden,
136 bool wait) {
137 TRACE_EVENT0("base", "LaunchElevatedProcess");
138 const FilePath::StringType file = cmdline.GetProgram().value();
139 const CommandLine::StringType arguments = cmdline.GetArgumentsString();
140
141 SHELLEXECUTEINFO shex_info = {};
142 shex_info.cbSize = sizeof(shex_info);
143 shex_info.fMask = SEE_MASK_NOCLOSEPROCESS;
144 shex_info.hwnd = GetActiveWindow();
145 shex_info.lpVerb = L"runas";
146 shex_info.lpFile = file.c_str();
147 shex_info.lpParameters = arguments.c_str();
148 shex_info.lpDirectory = nullptr;
149 shex_info.nShow = start_hidden ? SW_HIDE : SW_SHOWNORMAL;
150 shex_info.hInstApp = nullptr;
151
152 if (!ShellExecuteEx(&shex_info)) {
153 DPLOG(ERROR);
154 return Process();
155 }
156
157 if (wait) {
158 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
159 WaitForSingleObject(shex_info.hProcess, INFINITE);
160 }
161
162 return Process(shex_info.hProcess);
163 }
164
165 } // namespace
166
RouteStdioToConsole(bool create_console_if_not_found)167 void RouteStdioToConsole(bool create_console_if_not_found) {
168 // Don't change anything if stdout or stderr already point to a
169 // valid stream.
170 //
171 // If we are running under Buildbot or under Cygwin's default
172 // terminal (mintty), stderr and stderr will be pipe handles. In
173 // that case, we don't want to open CONOUT$, because its output
174 // likely does not go anywhere.
175 //
176 // We don't use GetStdHandle() to check stdout/stderr here because
177 // it can return dangling IDs of handles that were never inherited
178 // by this process. These IDs could have been reused by the time
179 // this function is called. The CRT checks the validity of
180 // stdout/stderr on startup (before the handle IDs can be reused).
181 // _fileno(stdout) will return -2 (_NO_CONSOLE_FILENO) if stdout was
182 // invalid.
183 if (_fileno(stdout) >= 0 || _fileno(stderr) >= 0) {
184 // _fileno was broken for SUBSYSTEM:WINDOWS from VS2010 to VS2012/2013.
185 // http://crbug.com/358267. Confirm that the underlying HANDLE is valid
186 // before aborting.
187
188 intptr_t stdout_handle = _get_osfhandle(_fileno(stdout));
189 intptr_t stderr_handle = _get_osfhandle(_fileno(stderr));
190 if (stdout_handle >= 0 || stderr_handle >= 0)
191 return;
192 }
193
194 if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
195 unsigned int result = GetLastError();
196 // Was probably already attached.
197 if (result == ERROR_ACCESS_DENIED)
198 return;
199 // Don't bother creating a new console for each child process if the
200 // parent process is invalid (eg: crashed).
201 if (result == ERROR_GEN_FAILURE)
202 return;
203 if (create_console_if_not_found) {
204 // Make a new console if attaching to parent fails with any other error.
205 // It should be ERROR_INVALID_HANDLE at this point, which means the
206 // browser was likely not started from a console.
207 AllocConsole();
208 } else {
209 return;
210 }
211 }
212
213 // Arbitrary byte count to use when buffering output lines. More
214 // means potential waste, less means more risk of interleaved
215 // log-lines in output.
216 enum { kOutputBufferSize = 64 * 1024 };
217
218 if (freopen("CONOUT$", "w", stdout)) {
219 setvbuf(stdout, nullptr, _IOLBF, kOutputBufferSize);
220 // Overwrite FD 1 for the benefit of any code that uses this FD
221 // directly. This is safe because the CRT allocates FDs 0, 1 and
222 // 2 at startup even if they don't have valid underlying Windows
223 // handles. This means we won't be overwriting an FD created by
224 // _open() after startup.
225 _dup2(_fileno(stdout), 1);
226 }
227 if (freopen("CONOUT$", "w", stderr)) {
228 setvbuf(stderr, nullptr, _IOLBF, kOutputBufferSize);
229 _dup2(_fileno(stderr), 2);
230 }
231
232 // Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog.
233 std::ios::sync_with_stdio();
234 }
235
LaunchProcess(const CommandLine & cmdline,const LaunchOptions & options)236 Process LaunchProcess(const CommandLine& cmdline,
237 const LaunchOptions& options) {
238 if (options.elevated)
239 return LaunchElevatedProcess(cmdline, options.start_hidden, options.wait);
240 return LaunchProcess(cmdline.GetCommandLineString(), options);
241 }
242
LaunchProcess(const CommandLine::StringType & cmdline,const LaunchOptions & options)243 Process LaunchProcess(const CommandLine::StringType& cmdline,
244 const LaunchOptions& options) {
245 // Retain the command line on the stack for investigating shutdown hangs
246 // tracked in https://crbug.com/1431378
247 DEBUG_ALIAS_FOR_WCHARCSTR(cmdline_for_debugging, cmdline.c_str(), 200);
248
249 if (options.elevated) {
250 return LaunchElevatedProcess(base::CommandLine::FromString(cmdline),
251 options.start_hidden, options.wait);
252 }
253 TRACE_EVENT0("base", "LaunchProcess");
254 // Mitigate the issues caused by loading DLLs on a background thread
255 // (http://crbug/973868).
256 SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
257
258 // |process_mitigations| must outlive |startup_info_wrapper|.
259 DWORD64 process_mitigations[2]{0, 0};
260 win::StartupInformation startup_info_wrapper;
261 STARTUPINFO* startup_info = startup_info_wrapper.startup_info();
262 DWORD flags = 0;
263
264 // Count extended attributes before reserving space.
265 DWORD attribute_count = 0;
266 // Count PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY.
267 if (options.disable_cetcompat &&
268 base::win::GetVersion() >= base::win::Version::WIN10_20H1) {
269 ++attribute_count;
270 }
271
272 // Count PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
273 if (!options.handles_to_inherit.empty())
274 ++attribute_count;
275
276 // Reserve space for attributes.
277 if (attribute_count > 0) {
278 if (!startup_info_wrapper.InitializeProcThreadAttributeList(
279 attribute_count)) {
280 DPLOG(ERROR);
281 return Process();
282 }
283 flags |= EXTENDED_STARTUPINFO_PRESENT;
284 }
285
286 // Set PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY.
287 if (options.disable_cetcompat &&
288 base::win::GetVersion() >= base::win::Version::WIN10_20H1) {
289 DCHECK_GT(attribute_count, 0u);
290 process_mitigations[1] |=
291 PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_OFF;
292 if (!startup_info_wrapper.UpdateProcThreadAttribute(
293 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &process_mitigations[0],
294 sizeof(process_mitigations))) {
295 return Process();
296 }
297 }
298
299 // Set PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
300 bool inherit_handles = options.inherit_mode == LaunchOptions::Inherit::kAll;
301 if (!options.handles_to_inherit.empty()) {
302 DCHECK_GT(attribute_count, 0u);
303 DCHECK_EQ(options.inherit_mode, LaunchOptions::Inherit::kSpecific);
304
305 if (options.handles_to_inherit.size() >
306 std::numeric_limits<DWORD>::max() / sizeof(HANDLE)) {
307 DLOG(ERROR) << "Too many handles to inherit.";
308 return Process();
309 }
310
311 // Ensure the handles can be inherited.
312 for (HANDLE handle : options.handles_to_inherit) {
313 BOOL result = SetHandleInformation(handle, HANDLE_FLAG_INHERIT,
314 HANDLE_FLAG_INHERIT);
315 PCHECK(result);
316 }
317
318 if (!startup_info_wrapper.UpdateProcThreadAttribute(
319 PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
320 const_cast<HANDLE*>(&options.handles_to_inherit[0]),
321 static_cast<DWORD>(options.handles_to_inherit.size() *
322 sizeof(HANDLE)))) {
323 DPLOG(ERROR);
324 return Process();
325 }
326
327 inherit_handles = true;
328 }
329
330 if (options.feedback_cursor_off)
331 startup_info->dwFlags |= STARTF_FORCEOFFFEEDBACK;
332 if (options.empty_desktop_name)
333 startup_info->lpDesktop = const_cast<wchar_t*>(L"");
334 startup_info->dwFlags |= STARTF_USESHOWWINDOW;
335 startup_info->wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOWNORMAL;
336
337 if (options.stdin_handle || options.stdout_handle || options.stderr_handle) {
338 DCHECK(inherit_handles);
339 // If an explicit handle inheritance list is not set, require that all
340 // stdio handle values be explicitly specified.
341 if (options.handles_to_inherit.empty()) {
342 CHECK(options.stdin_handle);
343 CHECK(options.stdout_handle);
344 CHECK(options.stderr_handle);
345 }
346 startup_info->dwFlags |= STARTF_USESTDHANDLES;
347 startup_info->hStdInput = options.stdin_handle;
348 startup_info->hStdOutput = options.stdout_handle;
349 startup_info->hStdError = options.stderr_handle;
350 }
351
352 if (options.force_breakaway_from_job_)
353 flags |= CREATE_BREAKAWAY_FROM_JOB;
354
355 PROCESS_INFORMATION temp_process_info = {};
356
357 LPCTSTR current_directory = options.current_directory.empty()
358 ? nullptr
359 : options.current_directory.value().c_str();
360
361 auto writable_cmdline(cmdline);
362 DCHECK(!(flags & CREATE_SUSPENDED))
363 << "Creating a suspended process can lead to hung processes if the "
364 << "launching process is killed before it assigns the process to the"
365 << "job. https://crbug.com/820996";
366 if (options.as_user) {
367 flags |= CREATE_UNICODE_ENVIRONMENT;
368 void* environment_block = nullptr;
369
370 if (!CreateEnvironmentBlock(&environment_block, options.as_user, FALSE)) {
371 DPLOG(ERROR);
372 return Process();
373 }
374
375 // Environment options are not implemented for use with |as_user|.
376 DCHECK(!options.clear_environment);
377 DCHECK(options.environment.empty());
378
379 BOOL launched = CreateProcessAsUser(
380 options.as_user, nullptr, data(writable_cmdline), nullptr, nullptr,
381 inherit_handles, flags, environment_block, current_directory,
382 startup_info, &temp_process_info);
383 DestroyEnvironmentBlock(environment_block);
384 if (!launched) {
385 DPLOG(ERROR) << "Command line:" << std::endl
386 << WideToUTF8(cmdline) << std::endl;
387 return Process();
388 }
389 } else {
390 wchar_t* new_environment = nullptr;
391 std::wstring env_storage;
392 if (options.clear_environment || !options.environment.empty()) {
393 if (options.clear_environment) {
394 static const wchar_t kEmptyEnvironment[] = {0};
395 env_storage =
396 internal::AlterEnvironment(kEmptyEnvironment, options.environment);
397 } else {
398 wchar_t* old_environment = GetEnvironmentStrings();
399 if (!old_environment) {
400 DPLOG(ERROR);
401 return Process();
402 }
403 env_storage =
404 internal::AlterEnvironment(old_environment, options.environment);
405 FreeEnvironmentStrings(old_environment);
406 }
407 new_environment = data(env_storage);
408 flags |= CREATE_UNICODE_ENVIRONMENT;
409 }
410
411 if (!CreateProcess(nullptr, data(writable_cmdline), nullptr, nullptr,
412 inherit_handles, flags, new_environment,
413 current_directory, startup_info, &temp_process_info)) {
414 DPLOG(ERROR) << "Command line:" << std::endl << cmdline << std::endl;
415 return Process();
416 }
417 }
418 win::ScopedProcessInformation process_info(temp_process_info);
419
420 if (options.job_handle &&
421 !AssignProcessToJobObject(options.job_handle,
422 process_info.process_handle())) {
423 DPLOG(ERROR) << "Could not AssignProcessToObject";
424 Process scoped_process(process_info.TakeProcessHandle());
425 scoped_process.Terminate(win::kProcessKilledExitCode, true);
426 return Process();
427 }
428
429 if (options.grant_foreground_privilege &&
430 !AllowSetForegroundWindow(GetProcId(process_info.process_handle()))) {
431 DPLOG(ERROR) << "Failed to grant foreground privilege to launched process";
432 }
433
434 if (options.wait) {
435 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
436 WaitForSingleObject(process_info.process_handle(), INFINITE);
437 }
438
439 return Process(process_info.TakeProcessHandle());
440 }
441
SetJobObjectLimitFlags(HANDLE job_object,DWORD limit_flags)442 bool SetJobObjectLimitFlags(HANDLE job_object, DWORD limit_flags) {
443 JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {};
444 limit_info.BasicLimitInformation.LimitFlags = limit_flags;
445 return 0 != SetInformationJobObject(
446 job_object,
447 JobObjectExtendedLimitInformation,
448 &limit_info,
449 sizeof(limit_info));
450 }
451
GetAppOutput(const CommandLine & cl,std::string * output)452 bool GetAppOutput(const CommandLine& cl, std::string* output) {
453 return GetAppOutput(cl.GetCommandLineString(), output);
454 }
455
GetAppOutputAndError(const CommandLine & cl,std::string * output)456 bool GetAppOutputAndError(const CommandLine& cl, std::string* output) {
457 int exit_code;
458 return GetAppOutputInternal(
459 cl.GetCommandLineString(), true, output, &exit_code);
460 }
461
GetAppOutputWithExitCode(const CommandLine & cl,std::string * output,int * exit_code)462 bool GetAppOutputWithExitCode(const CommandLine& cl,
463 std::string* output,
464 int* exit_code) {
465 return GetAppOutputInternal(
466 cl.GetCommandLineString(), false, output, exit_code);
467 }
468
GetAppOutput(CommandLine::StringPieceType cl,std::string * output)469 bool GetAppOutput(CommandLine::StringPieceType cl, std::string* output) {
470 int exit_code;
471 return GetAppOutputInternal(cl, false, output, &exit_code);
472 }
473
RaiseProcessToHighPriority()474 void RaiseProcessToHighPriority() {
475 SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
476 }
477
478 } // namespace base
479