xref: /aosp_15_r20/external/cronet/base/process/launch_win.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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