From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- ipc/chromium/src/base/process_util_win.cc | 396 ++++++++++++++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 ipc/chromium/src/base/process_util_win.cc (limited to 'ipc/chromium/src/base/process_util_win.cc') diff --git a/ipc/chromium/src/base/process_util_win.cc b/ipc/chromium/src/base/process_util_win.cc new file mode 100644 index 0000000000..5bfbc3eee5 --- /dev/null +++ b/ipc/chromium/src/base/process_util_win.cc @@ -0,0 +1,396 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// We need extended process and thread attribute support +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 + +#include "base/process_util.h" + +#include +#include +#include +#include +#ifndef STDOUT_FILENO +# define STDOUT_FILENO 1 +#endif + +#include "base/command_line.h" +#include "base/histogram.h" +#include "base/logging.h" +#include "base/win_util.h" + +#include "mozilla/ipc/LaunchError.h" +#include "mozilla/Result.h" + +#include +#include +#include + +namespace { + +typedef BOOL(WINAPI* InitializeProcThreadAttributeListFn)( + LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, DWORD dwAttributeCount, + DWORD dwFlags, PSIZE_T lpSize); + +typedef BOOL(WINAPI* DeleteProcThreadAttributeListFn)( + LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList); + +typedef BOOL(WINAPI* UpdateProcThreadAttributeFn)( + LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, DWORD dwFlags, + DWORD_PTR Attribute, PVOID lpValue, SIZE_T cbSize, PVOID lpPreviousValue, + PSIZE_T lpReturnSize); + +static InitializeProcThreadAttributeListFn InitializeProcThreadAttributeListPtr; +static DeleteProcThreadAttributeListFn DeleteProcThreadAttributeListPtr; +static UpdateProcThreadAttributeFn UpdateProcThreadAttributePtr; + +static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG"); + +} // namespace + +namespace base { + +ProcessId GetCurrentProcId() { return ::GetCurrentProcessId(); } + +ProcessHandle GetCurrentProcessHandle() { return ::GetCurrentProcess(); } + +bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) { + // TODO(phajdan.jr): Take even more permissions out of this list. + ProcessHandle result = + OpenProcess(PROCESS_DUP_HANDLE | PROCESS_TERMINATE | + PROCESS_QUERY_INFORMATION | SYNCHRONIZE, + FALSE, pid); + + if (result == NULL) { + return false; + } + + *handle = result; + return true; +} + +bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) { + ProcessHandle result = + OpenProcess(PROCESS_DUP_HANDLE | PROCESS_TERMINATE | + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE, + FALSE, pid); + + if (result == NULL) { + return false; + } + + *handle = result; + return true; +} + +void CloseProcessHandle(ProcessHandle process) { + // closing a handle twice on Windows can be catastrophic - after the first + // close the handle value may be reused, so the second close will kill that + // other new handle. + BOOL ok = CloseHandle(process); + DCHECK(ok); +} + +ProcessId GetProcId(ProcessHandle process) { + if (process == base::kInvalidProcessHandle || process == nullptr) { + return 0; + } + // This returns 0 if we have insufficient rights to query the process handle. + // Invalid handles or non-process handles will cause a diagnostic assert. + ProcessId result = GetProcessId(process); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + CHECK(result != 0 || GetLastError() != ERROR_INVALID_HANDLE) + << "process handle = " << process; +#endif + return result; +} + +// from sandbox_policy_base.cc in a later version of the chromium ipc code... +bool IsInheritableHandle(HANDLE handle) { + if (!handle) return false; + if (handle == INVALID_HANDLE_VALUE) return false; + // File handles (FILE_TYPE_DISK) and pipe handles are known to be + // inheritable. Console handles (FILE_TYPE_CHAR) are not + // inheritable via PROC_THREAD_ATTRIBUTE_HANDLE_LIST. + DWORD handle_type = GetFileType(handle); + return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; +} + +void LoadThreadAttributeFunctions() { + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + InitializeProcThreadAttributeListPtr = + reinterpret_cast( + GetProcAddress(kernel32, "InitializeProcThreadAttributeList")); + DeleteProcThreadAttributeListPtr = + reinterpret_cast( + GetProcAddress(kernel32, "DeleteProcThreadAttributeList")); + UpdateProcThreadAttributePtr = reinterpret_cast( + GetProcAddress(kernel32, "UpdateProcThreadAttribute")); +} + +// Creates and returns a "thread attribute list" to pass to the child process. +// On return, is a pointer to a THREAD_ATTRIBUTE_LIST or NULL if either the +// functions we need aren't available (eg, XP or earlier) or the functions we +// need failed. +// The result of this function must be passed to FreeThreadAttributeList. +// Note that the pointer to the HANDLE array ends up embedded in the result of +// this function and must stay alive until FreeThreadAttributeList is called, +// hence it is passed in so the owner is the caller of this function. +LPPROC_THREAD_ATTRIBUTE_LIST CreateThreadAttributeList(HANDLE* handlesToInherit, + int handleCount) { + if (!InitializeProcThreadAttributeListPtr || + !DeleteProcThreadAttributeListPtr || !UpdateProcThreadAttributePtr) + LoadThreadAttributeFunctions(); + // shouldn't happen as we are only called for Vista+, but better safe than + // sorry... + if (!InitializeProcThreadAttributeListPtr || + !DeleteProcThreadAttributeListPtr || !UpdateProcThreadAttributePtr) + return NULL; + + SIZE_T threadAttrSize; + LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; + + if (!(*InitializeProcThreadAttributeListPtr)(NULL, 1, 0, &threadAttrSize) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + goto fail; + lpAttributeList = + reinterpret_cast(malloc(threadAttrSize)); + if (!lpAttributeList || !(*InitializeProcThreadAttributeListPtr)( + lpAttributeList, 1, 0, &threadAttrSize)) + goto fail; + + if (!(*UpdateProcThreadAttributePtr)( + lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + handlesToInherit, sizeof(handlesToInherit[0]) * handleCount, NULL, + NULL)) { + (*DeleteProcThreadAttributeListPtr)(lpAttributeList); + goto fail; + } + return lpAttributeList; + +fail: + if (lpAttributeList) free(lpAttributeList); + return NULL; +} + +// Frees the data returned by CreateThreadAttributeList. +void FreeThreadAttributeList(LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList) { + // must be impossible to get a NULL DeleteProcThreadAttributeListPtr, as + // we already checked it existed when creating the data we are now freeing. + (*DeleteProcThreadAttributeListPtr)(lpAttributeList); + free(lpAttributeList); +} + +// The next two functions are from chromium/base/environment.cc +// +// Parses a null-terminated input string of an environment block. The key is +// placed into the given string, and the total length of the line, including +// the terminating null, is returned. +static size_t ParseEnvLine(const NativeEnvironmentString::value_type* input, + NativeEnvironmentString* key) { + // Skip to the equals or end of the string, this is the key. + size_t cur = 0; + while (input[cur] && input[cur] != '=') cur++; + *key = NativeEnvironmentString(&input[0], cur); + + // Now just skip to the end of the string. + while (input[cur]) cur++; + return cur + 1; +} + +std::wstring AlterEnvironment(const wchar_t* env, + const EnvironmentMap& changes) { + std::wstring result; + + // First copy all unmodified values to the output. + size_t cur_env = 0; + std::wstring key; + while (env[cur_env]) { + const wchar_t* line = &env[cur_env]; + size_t line_length = ParseEnvLine(line, &key); + + // Keep only values not specified in the change vector. + EnvironmentMap::const_iterator found_change = changes.find(key); + if (found_change == changes.end()) result.append(line, line_length); + + cur_env += line_length; + } + + // Now append all modified and new values. + for (EnvironmentMap::const_iterator i = changes.begin(); i != changes.end(); + ++i) { + if (!i->second.empty()) { + result.append(i->first); + result.push_back('='); + result.append(i->second); + result.push_back(0); + } + } + + // An additional null marks the end of the list. We always need a double-null + // in case nothing was added above. + if (result.empty()) result.push_back(0); + result.push_back(0); + return result; +} + +Result LaunchApp(const std::wstring& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + // We want to inherit the std handles so dump() statements and assertion + // messages in the child process can be seen - but we *do not* want to + // blindly have all handles inherited. Vista and later has a technique + // where only specified handles are inherited - so we use this technique. + // If that fails we just don't inherit anything. + DWORD dwCreationFlags = 0; + BOOL bInheritHandles = FALSE; + + // We use a STARTUPINFOEX, but if we can't do the thread attribute thing, we + // just pass the size of a STARTUPINFO. + STARTUPINFOEX startup_info_ex; + ZeroMemory(&startup_info_ex, sizeof(startup_info_ex)); + STARTUPINFO& startup_info = startup_info_ex.StartupInfo; + startup_info.cb = sizeof(startup_info); + startup_info.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEOFFFEEDBACK; + startup_info.wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW; + + // Per the comment in CreateThreadAttributeList, lpAttributeList will contain + // a pointer to handlesToInherit, so make sure they have the same lifetime. + LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; + std::vector handlesToInherit; + for (HANDLE h : options.handles_to_inherit) { + if (SetHandleInformation(h, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) == + 0) { + MOZ_DIAGNOSTIC_ASSERT(false, "SetHandleInformation failed"); + return Err(LaunchError("SetHandleInformation", GetLastError())); + } + handlesToInherit.push_back(h); + } + + // setup our handle array first - if we end up with no handles that can + // be inherited we can avoid trying to do the ThreadAttributeList dance... + HANDLE stdOut = ::GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE stdErr = ::GetStdHandle(STD_ERROR_HANDLE); + + if (IsInheritableHandle(stdOut)) handlesToInherit.push_back(stdOut); + if (stdErr != stdOut && IsInheritableHandle(stdErr)) + handlesToInherit.push_back(stdErr); + + if (!handlesToInherit.empty()) { + lpAttributeList = CreateThreadAttributeList(handlesToInherit.data(), + handlesToInherit.size()); + if (lpAttributeList) { + // it's safe to inherit handles, so arrange for that... + startup_info.cb = sizeof(startup_info_ex); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + startup_info.hStdOutput = stdOut; + startup_info.hStdError = stdErr; + startup_info.hStdInput = INVALID_HANDLE_VALUE; + startup_info_ex.lpAttributeList = lpAttributeList; + dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; + bInheritHandles = TRUE; + } + } + + dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT; + if (options.start_independent) { + dwCreationFlags |= CREATE_BREAKAWAY_FROM_JOB; + } + + LPTCH original_environment = GetEnvironmentStrings(); + base::NativeEnvironmentString new_environment = + AlterEnvironment(original_environment, options.env_map); + // Ignore return value? What can we do? + FreeEnvironmentStrings(original_environment); + LPVOID new_env_ptr = (void*)new_environment.data(); + + PROCESS_INFORMATION process_info; + + BOOL createdOK = CreateProcess( + NULL, const_cast(cmdline.c_str()), NULL, NULL, bInheritHandles, + dwCreationFlags, new_env_ptr, NULL, &startup_info, &process_info); + if (lpAttributeList) FreeThreadAttributeList(lpAttributeList); + if (!createdOK) { + DLOG(WARNING) << "CreateProcess Failed: " << GetLastError(); + return Err(LaunchError("CreateProcess", GetLastError())); + } + + gProcessLog.print("==> process %d launched child process %d (%S)\n", + GetCurrentProcId(), process_info.dwProcessId, + cmdline.c_str()); + + // Handles must be closed or they will leak + CloseHandle(process_info.hThread); + + if (options.wait) WaitForSingleObject(process_info.hProcess, INFINITE); + + // If the caller wants the process handle, we won't close it. + if (process_handle) { + *process_handle = process_info.hProcess; + } else { + CloseHandle(process_info.hProcess); + } + return Ok(); +} + +Result LaunchApp(const CommandLine& cl, + const LaunchOptions& options, + ProcessHandle* process_handle) { + return LaunchApp(cl.command_line_string(), options, process_handle); +} + +bool KillProcess(ProcessHandle process, int exit_code) { + // INVALID_HANDLE_VALUE is not actually an invalid handle value, but + // instead is the value returned by GetCurrentProcess(). nullptr, + // in contrast, *is* an invalid handle value. Both values are too + // easy to accidentally try to kill, and neither is legitimately + // used by this function's callers, so reject them. + if (!process || process == INVALID_HANDLE_VALUE) { + CHROMIUM_LOG(WARNING) + << "base::KillProcess refusing to terminate process handle " << process; + return false; + } + bool result = (TerminateProcess(process, exit_code) != FALSE); + if (!result) { + DLOG(ERROR) << "Unable to terminate process: " << GetLastError(); + } + return result; +} + +} // namespace base + +namespace mozilla { + +EnvironmentLog::EnvironmentLog(const char* varname, size_t len) { + wchar_t wvarname[len]; + std::copy(varname, varname + len, wvarname); + const wchar_t* e = _wgetenv(wvarname); + if (e && *e) { + fname_ = e; + } +} + +void EnvironmentLog::print(const char* format, ...) { + if (!fname_.size()) return; + + FILE* f; + if (fname_.compare(L"-") == 0) { + f = fdopen(dup(STDOUT_FILENO), "a"); + } else { + f = _wfopen(fname_.c_str(), L"a"); + } + + if (!f) return; + + va_list a; + va_start(a, format); + vfprintf(f, format, a); + va_end(a); + fclose(f); +} + +} // namespace mozilla -- cgit v1.2.3