// Copyright (c) 2012 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. #include "chrome_frame/test_utils.h" #include #include #include #include #include #include "base/command_line.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/logging.h" #include "base/path_service.h" #include "base/process_util.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "base/win/scoped_handle.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome_frame/test/chrome_frame_test_utils.h" #include "testing/gtest/include/gtest/gtest.h" const wchar_t kChromeFrameDllName[] = L"npchrome_frame.dll"; const wchar_t kChromeLauncherExeName[] = L"chrome_launcher.exe"; // How long to wait for DLLs to register or unregister themselves before killing // the registrar. const int64 kDllRegistrationTimeoutMs = 30 * 1000; FilePath GetChromeFrameBuildPath() { FilePath build_path; PathService::Get(chrome::DIR_APP, &build_path); FilePath dll_path = build_path.Append(kChromeFrameDllName); if (!file_util::PathExists(dll_path)) { // Well, dang.. try looking in the current directory. dll_path = build_path.Append(kChromeFrameDllName); } if (!file_util::PathExists(dll_path)) { // No luck, return something empty. dll_path = FilePath(); } return dll_path; } const wchar_t ScopedChromeFrameRegistrar::kCallRegistrationEntrypointSwitch[] = L"--call-registration-entrypoint"; bool ScopedChromeFrameRegistrar::register_chrome_path_provider_ = false; // static void ScopedChromeFrameRegistrar::RegisterDefaults() { if (!register_chrome_path_provider_) { chrome::RegisterPathProvider(); register_chrome_path_provider_ = true; } } // Registers or unregisters the DLL at |path| by calling out to the current // executable with --call-registration-entrypoint. Loading the DLL into the // test process is problematic for component=shared_library builds since // singletons in base.dll aren't designed to handle multiple initialization. // Use of rundll32.exe is problematic since it does not return useful error // information. // static void ScopedChromeFrameRegistrar::DoRegistration( const string16& path, RegistrationType registration_type, RegistrationOperation registration_operation) { static const char* const kEntrypoints[] = { "DllRegisterServer", "DllUnregisterServer", "DllRegisterUserServer", "DllUnregisterUserServer", }; DCHECK(!path.empty()); DCHECK(registration_type == PER_USER || registration_type == SYSTEM_LEVEL); DCHECK(registration_operation == REGISTER || registration_operation == UNREGISTER); int entrypoint_index = 0; base::LaunchOptions launch_options; base::ProcessHandle process_handle = INVALID_HANDLE_VALUE; int exit_code = -1; if (registration_type == PER_USER) entrypoint_index += 2; if (registration_operation == UNREGISTER) entrypoint_index += 1; string16 registration_command(ASCIIToUTF16("\"")); registration_command += CommandLine::ForCurrentProcess()->GetProgram().value(); registration_command += ASCIIToUTF16("\" "); registration_command += kCallRegistrationEntrypointSwitch; registration_command += ASCIIToUTF16(" \""); registration_command += path; registration_command += ASCIIToUTF16("\" "); registration_command += ASCIIToUTF16(kEntrypoints[entrypoint_index]); launch_options.wait = true; if (!base::LaunchProcess(registration_command, launch_options, &process_handle)) { PLOG(FATAL) << "Failed to register or unregister DLL with command: " << registration_command; } else { base::win::ScopedHandle rundll32(process_handle); if (!base::WaitForExitCodeWithTimeout( process_handle, &exit_code, base::TimeDelta::FromMilliseconds(kDllRegistrationTimeoutMs))) { LOG(ERROR) << "Timeout waiting to register or unregister DLL with " "command: " << registration_command; base::KillProcess(process_handle, 0, false); NOTREACHED() << "Aborting test due to registration failure."; } } if (exit_code != 0) { if (registration_operation == REGISTER) { LOG(ERROR) << "DLL registration failed (exit code: 0x" << std::hex << exit_code << ", command: " << registration_command << "). Make sure you are running as Admin."; ::ExitProcess(1); } else { LOG(WARNING) << "DLL unregistration failed (exit code: 0x" << std::hex << exit_code << ", command: " << registration_command << ")."; } } } // static void ScopedChromeFrameRegistrar::RegisterAtPath( const std::wstring& path, RegistrationType registration_type) { DoRegistration(path, registration_type, REGISTER); } // static void ScopedChromeFrameRegistrar::UnregisterAtPath( const std::wstring& path, RegistrationType registration_type) { DoRegistration(path, registration_type, UNREGISTER); } FilePath ScopedChromeFrameRegistrar::GetReferenceChromeFrameDllPath() { FilePath reference_build_dir; PathService::Get(chrome::DIR_APP, &reference_build_dir); reference_build_dir = reference_build_dir.DirName(); reference_build_dir = reference_build_dir.DirName(); reference_build_dir = reference_build_dir.AppendASCII("chrome_frame"); reference_build_dir = reference_build_dir.AppendASCII("tools"); reference_build_dir = reference_build_dir.AppendASCII("test"); reference_build_dir = reference_build_dir.AppendASCII("reference_build"); reference_build_dir = reference_build_dir.AppendASCII("chrome"); reference_build_dir = reference_build_dir.AppendASCII("servers"); reference_build_dir = reference_build_dir.Append(kChromeFrameDllName); return reference_build_dir; } // static void ScopedChromeFrameRegistrar::RegisterAndExitProcessIfDirected() { // This method is invoked before any Chromium helpers have been initialized. // Take pains to use only Win32 and CRT functions. int argc = 0; const wchar_t* const* argv = ::CommandLineToArgvW(::GetCommandLine(), &argc); if (argc < 2 || ::lstrcmp(argv[1], kCallRegistrationEntrypointSwitch) != 0) return; if (argc != 4) { printf("Usage: %S %S \n", argv[0], kCallRegistrationEntrypointSwitch); return; } // The only way to leave from here on down is ExitProcess. const wchar_t* dll_path = argv[2]; const wchar_t* wide_entrypoint = argv[3]; char entrypoint[256]; HRESULT exit_code = 0; int entrypoint_len = lstrlen(wide_entrypoint); if (entrypoint_len <= 0 || entrypoint_len >= arraysize(entrypoint)) { exit_code = HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND); } else { // Convert wide to narrow. Since the entrypoint must be a narrow string // anyway, it is safe to truncate each character like this. std::copy(wide_entrypoint, wide_entrypoint + entrypoint_len + 1, &entrypoint[0]); HMODULE dll_module = ::LoadLibrary(dll_path); if (dll_module == NULL) { exit_code = HRESULT_FROM_WIN32(::GetLastError()); } else { typedef HRESULT (STDAPICALLTYPE *RegisterFp)(); RegisterFp register_func = reinterpret_cast(::GetProcAddress(dll_module, entrypoint)); if (register_func == NULL) { exit_code = HRESULT_FROM_WIN32(::GetLastError()); } else { exit_code = register_func(); } ::FreeLibrary(dll_module); } } ::ExitProcess(exit_code); } // Non-statics ScopedChromeFrameRegistrar::ScopedChromeFrameRegistrar( const std::wstring& path, RegistrationType registration_type) : registration_type_(registration_type) { if (!register_chrome_path_provider_) { // Register paths needed by the ScopedChromeFrameRegistrar. chrome::RegisterPathProvider(); register_chrome_path_provider_ = true; } original_dll_path_ = path; RegisterChromeFrameAtPath(original_dll_path_); } ScopedChromeFrameRegistrar::ScopedChromeFrameRegistrar( RegistrationType registration_type) : registration_type_(registration_type) { if (!register_chrome_path_provider_) { // Register paths needed by the ScopedChromeFrameRegistrar. chrome::RegisterPathProvider(); register_chrome_path_provider_ = true; } original_dll_path_ = GetChromeFrameBuildPath().value(); RegisterChromeFrameAtPath(original_dll_path_); } ScopedChromeFrameRegistrar::~ScopedChromeFrameRegistrar() { if (FilePath(original_dll_path_) != FilePath(new_chrome_frame_dll_path_)) { RegisterChromeFrameAtPath(original_dll_path_); } else if (registration_type_ == PER_USER) { UnregisterAtPath(new_chrome_frame_dll_path_, registration_type_); HWND chrome_frame_helper_window = FindWindow(L"ChromeFrameHelperWindowClass", NULL); if (IsWindow(chrome_frame_helper_window)) { PostMessage(chrome_frame_helper_window, WM_CLOSE, 0, 0); } else { base::KillProcesses(L"chrome_frame_helper.exe", 0, NULL); } } } void ScopedChromeFrameRegistrar::RegisterChromeFrameAtPath( const std::wstring& path) { RegisterAtPath(path, registration_type_); new_chrome_frame_dll_path_ = path; } void ScopedChromeFrameRegistrar::RegisterReferenceChromeFrameBuild() { RegisterChromeFrameAtPath(GetReferenceChromeFrameDllPath().value()); } std::wstring ScopedChromeFrameRegistrar::GetChromeFrameDllPath() const { return new_chrome_frame_dll_path_; } // TODO(robertshield): The following could be factored out into its own file. namespace { typedef LONG WINAPI NtQueryInformationProcess( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL ); // Get the function pointer to NtQueryInformationProcess in NTDLL.DLL static bool GetQIP(NtQueryInformationProcess** qip_func_ptr) { static NtQueryInformationProcess* qip_func = reinterpret_cast( GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryInformationProcess")); DCHECK(qip_func) << "Could not get pointer to NtQueryInformationProcess."; *qip_func_ptr = qip_func; return qip_func != NULL; } // Get the command line of a process bool GetCommandLineForProcess(uint32 process_id, std::wstring* cmd_line) { DCHECK(process_id != 0); DCHECK(cmd_line); // Open the process base::win::ScopedHandle process_handle(::OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, process_id)); if (!process_handle) { DLOG(ERROR) << "Failed to open process " << process_id << ", last error = " << GetLastError(); } // Obtain Process Environment Block NtQueryInformationProcess* qip_func = NULL; if (process_handle) { GetQIP(&qip_func); } // Read the address of the process params from the peb. DWORD process_params_address = 0; if (qip_func) { PROCESS_BASIC_INFORMATION info = { 0 }; // NtQueryInformationProcess returns an NTSTATUS for whom negative values // are negative. Just check for that instead of pulling in DDK macros. if ((qip_func(process_handle.Get(), ProcessBasicInformation, &info, sizeof(info), NULL)) < 0) { DLOG(ERROR) << "Failed to invoke NtQueryProcessInformation, last error = " << GetLastError(); } else { BYTE* peb = reinterpret_cast(info.PebBaseAddress); // The process command line parameters are (or were once) located at // the base address of the PEB + 0x10 for 32 bit processes. 64 bit // processes have a different PEB struct as per // http://msdn.microsoft.com/en-us/library/aa813706(VS.85).aspx. // TODO(robertshield): See about doing something about this. SIZE_T bytes_read = 0; if (!::ReadProcessMemory(process_handle.Get(), peb + 0x10, &process_params_address, sizeof(process_params_address), &bytes_read)) { DLOG(ERROR) << "Failed to read process params address, last error = " << GetLastError(); } } } // Copy all the process parameters into a buffer. bool success = false; std::wstring buffer; if (process_params_address) { SIZE_T bytes_read; RTL_USER_PROCESS_PARAMETERS params = { 0 }; if (!::ReadProcessMemory(process_handle.Get(), reinterpret_cast(process_params_address), ¶ms, sizeof(params), &bytes_read)) { DLOG(ERROR) << "Failed to read RTL_USER_PROCESS_PARAMETERS, " << "last error = " << GetLastError(); } else { // Read the command line parameter const int max_cmd_line_len = std::min( static_cast(params.CommandLine.MaximumLength), 4096); buffer.resize(max_cmd_line_len + 1); if (!::ReadProcessMemory(process_handle.Get(), params.CommandLine.Buffer, &buffer[0], max_cmd_line_len, &bytes_read)) { DLOG(ERROR) << "Failed to copy process command line, " << "last error = " << GetLastError(); } else { *cmd_line = buffer; success = true; } } } return success; } // Used to filter processes by process ID. class ArgumentFilter : public base::ProcessFilter { public: explicit ArgumentFilter(const std::wstring& argument) : argument_to_find_(argument) {} // Returns true to indicate set-inclusion and false otherwise. This method // should not have side-effects and should be idempotent. virtual bool Includes(const base::ProcessEntry& entry) const { bool found = false; std::wstring command_line; if (GetCommandLineForProcess(entry.pid(), &command_line)) { std::wstring::const_iterator it = std::search(command_line.begin(), command_line.end(), argument_to_find_.begin(), argument_to_find_.end(), base::CaseInsensitiveCompareASCII()); found = (it != command_line.end()); } return found; } protected: std::wstring argument_to_find_; }; } // namespace bool KillAllNamedProcessesWithArgument(const std::wstring& process_name, const std::wstring& argument) { return base::KillProcesses(process_name, 0, &ArgumentFilter(argument)); } bool IsWorkstationLocked() { bool is_locked = true; HDESK input_desk = ::OpenInputDesktop(0, 0, GENERIC_READ); if (input_desk) { wchar_t name[256] = {0}; DWORD needed = 0; if (::GetUserObjectInformation(input_desk, UOI_NAME, name, sizeof(name), &needed)) { is_locked = lstrcmpi(name, L"default") != 0; } ::CloseDesktop(input_desk); } return is_locked; }