// Copyright (c) 2016 Ryan Prichard // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. #include "WindowsVersion.h" #include #include #include #include #include #include "DebugClient.h" #include "OsModule.h" #include "StringBuilder.h" #include "StringUtil.h" #include "WinptyAssert.h" #include "WinptyException.h" namespace { typedef std::tuple Version; // This function can only return a version up to 6.2 unless the executable is // manifested for a newer version of Windows. See the MSDN documentation for // GetVersionEx. OSVERSIONINFOEX getWindowsVersionInfo() { // Allow use of deprecated functions (i.e. GetVersionEx). We need to use // GetVersionEx for the old MinGW toolchain and with MSVC when it targets XP. // Having two code paths makes code harder to test, and it's not obvious how // to detect the presence of a new enough SDK. (Including ntverp.h and // examining VER_PRODUCTBUILD apparently works, but even then, MinGW-w64 and // MSVC seem to use different version numbers.) #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4996) #endif OSVERSIONINFOEX info = {}; info.dwOSVersionInfoSize = sizeof(info); const auto success = GetVersionEx(reinterpret_cast(&info)); ASSERT(success && "GetVersionEx failed"); return info; #ifdef _MSC_VER #pragma warning(pop) #endif } Version getWindowsVersion() { const auto info = getWindowsVersionInfo(); return Version(info.dwMajorVersion, info.dwMinorVersion); } struct ModuleNotFound : WinptyException { virtual const wchar_t *what() const WINPTY_NOEXCEPT override { return L"ModuleNotFound"; } }; // Throws WinptyException on error. std::wstring getSystemDirectory() { wchar_t systemDirectory[MAX_PATH]; const UINT size = GetSystemDirectoryW(systemDirectory, MAX_PATH); if (size == 0) { throwWindowsError(L"GetSystemDirectory failed"); } else if (size >= MAX_PATH) { throwWinptyException( L"GetSystemDirectory: path is longer than MAX_PATH"); } return systemDirectory; } #define GET_VERSION_DLL_API(name) \ const auto p ## name = \ reinterpret_cast( \ versionDll.proc(#name)); \ if (p ## name == nullptr) { \ throwWinptyException(L ## #name L" is missing"); \ } // Throws WinptyException on error. VS_FIXEDFILEINFO getFixedFileInfo(const std::wstring &path) { // version.dll is not a conventional KnownDll, so if we link to it, there's // a danger of accidentally loading a malicious DLL. In a more typical // application, perhaps we'd guard against this security issue by // controlling which directories this code runs in (e.g. *not* the // "Downloads" directory), but that's harder for the winpty library. OsModule versionDll( (getSystemDirectory() + L"\\version.dll").c_str(), OsModule::LoadErrorBehavior::Throw); GET_VERSION_DLL_API(GetFileVersionInfoSizeW); GET_VERSION_DLL_API(GetFileVersionInfoW); GET_VERSION_DLL_API(VerQueryValueW); DWORD size = pGetFileVersionInfoSizeW(path.c_str(), nullptr); if (!size) { // I see ERROR_FILE_NOT_FOUND on Win7 and // ERROR_RESOURCE_DATA_NOT_FOUND on WinXP. if (GetLastError() == ERROR_FILE_NOT_FOUND || GetLastError() == ERROR_RESOURCE_DATA_NOT_FOUND) { throw ModuleNotFound(); } else { throwWindowsError( (L"GetFileVersionInfoSizeW failed on " + path).c_str()); } } std::unique_ptr versionBuffer(new char[size]); if (!pGetFileVersionInfoW(path.c_str(), 0, size, versionBuffer.get())) { throwWindowsError((L"GetFileVersionInfoW failed on " + path).c_str()); } VS_FIXEDFILEINFO *versionInfo = nullptr; UINT versionInfoSize = 0; if (!pVerQueryValueW( versionBuffer.get(), L"\\", reinterpret_cast(&versionInfo), &versionInfoSize) || versionInfo == nullptr || versionInfoSize != sizeof(VS_FIXEDFILEINFO) || versionInfo->dwSignature != 0xFEEF04BD) { throwWinptyException((L"VerQueryValueW failed on " + path).c_str()); } return *versionInfo; } uint64_t productVersionFromInfo(const VS_FIXEDFILEINFO &info) { return (static_cast(info.dwProductVersionMS) << 32) | (static_cast(info.dwProductVersionLS)); } uint64_t fileVersionFromInfo(const VS_FIXEDFILEINFO &info) { return (static_cast(info.dwFileVersionMS) << 32) | (static_cast(info.dwFileVersionLS)); } std::string versionToString(uint64_t version) { StringBuilder b(32); b << ((uint16_t)(version >> 48)); b << '.'; b << ((uint16_t)(version >> 32)); b << '.'; b << ((uint16_t)(version >> 16)); b << '.'; b << ((uint16_t)(version >> 0)); return b.str_moved(); } } // anonymous namespace // Returns true for Windows Vista (or Windows Server 2008) or newer. bool isAtLeastWindowsVista() { return getWindowsVersion() >= Version(6, 0); } // Returns true for Windows 7 (or Windows Server 2008 R2) or newer. bool isAtLeastWindows7() { return getWindowsVersion() >= Version(6, 1); } // Returns true for Windows 8 (or Windows Server 2012) or newer. bool isAtLeastWindows8() { return getWindowsVersion() >= Version(6, 2); } #define WINPTY_IA32 1 #define WINPTY_X64 2 #if defined(_M_IX86) || defined(__i386__) #define WINPTY_ARCH WINPTY_IA32 #elif defined(_M_X64) || defined(__x86_64__) #define WINPTY_ARCH WINPTY_X64 #endif typedef BOOL WINAPI IsWow64Process_t(HANDLE hProcess, PBOOL Wow64Process); void dumpWindowsVersion() { if (!isTracingEnabled()) { return; } const auto info = getWindowsVersionInfo(); StringBuilder b; b << info.dwMajorVersion << '.' << info.dwMinorVersion << '.' << info.dwBuildNumber << ' ' << "SP" << info.wServicePackMajor << '.' << info.wServicePackMinor << ' '; switch (info.wProductType) { case VER_NT_WORKSTATION: b << "Client"; break; case VER_NT_DOMAIN_CONTROLLER: b << "DomainController"; break; case VER_NT_SERVER: b << "Server"; break; default: b << "product=" << info.wProductType; break; } b << ' '; #if WINPTY_ARCH == WINPTY_IA32 b << "IA32"; OsModule kernel32(L"kernel32.dll"); IsWow64Process_t *pIsWow64Process = reinterpret_cast( kernel32.proc("IsWow64Process")); if (pIsWow64Process != nullptr) { BOOL result = false; const BOOL success = pIsWow64Process(GetCurrentProcess(), &result); if (!success) { b << " WOW64:error"; } else if (success && result) { b << " WOW64"; } } else { b << " WOW64:missingapi"; } #elif WINPTY_ARCH == WINPTY_X64 b << "X64"; #endif const auto dllVersion = [](const wchar_t *dllPath) -> std::string { try { const auto info = getFixedFileInfo(dllPath); StringBuilder fb(64); fb << utf8FromWide(dllPath) << ':'; fb << "F:" << versionToString(fileVersionFromInfo(info)) << '/' << "P:" << versionToString(productVersionFromInfo(info)); return fb.str_moved(); } catch (const ModuleNotFound&) { return utf8FromWide(dllPath) + ":none"; } catch (const WinptyException &e) { trace("Error getting %s version: %s", utf8FromWide(dllPath).c_str(), utf8FromWide(e.what()).c_str()); return utf8FromWide(dllPath) + ":error"; } }; b << ' ' << dllVersion(L"kernel32.dll"); // ConEmu provides a DLL that hooks many Windows APIs, especially console // APIs. Its existence and version number could be useful in debugging. #if WINPTY_ARCH == WINPTY_IA32 b << ' ' << dllVersion(L"ConEmuHk.dll"); #elif WINPTY_ARCH == WINPTY_X64 b << ' ' << dllVersion(L"ConEmuHk64.dll"); #endif trace("Windows version: %s", b.c_str()); }