// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/win/windows_version.h" #include #include #include #include #include "base/check_op.h" #include "base/file_version_info_win.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/no_destructor.h" #include "base/notreached.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "base/win/registry.h" #include "build/build_config.h" #if !defined(__clang__) && _MSC_FULL_VER < 191125507 #error VS 2017 Update 3.2 or higher is required #endif #if !defined(NTDDI_WIN10_NI) #error Windows 10.0.22621.0 SDK or higher required. #endif namespace base { namespace win { namespace { // The values under the CurrentVersion registry hive are mirrored under // the corresponding Wow6432 hive. constexpr wchar_t kRegKeyWindowsNTCurrentVersion[] = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; // Returns the "UBR" (Windows 10 patch number) and "DisplayVersion" (or // "ReleaseId" on earlier versions) (Windows 10 release number) from registry. // "UBR" is an undocumented value and will be 0 if the value was not found. // "ReleaseId" will be an empty string if neither new nor old values are found. std::pair GetVersionData() { DWORD ubr = 0; std::wstring release_id; RegKey key; if (key.Open(HKEY_LOCAL_MACHINE, kRegKeyWindowsNTCurrentVersion, KEY_QUERY_VALUE) == ERROR_SUCCESS) { key.ReadValueDW(L"UBR", &ubr); // "DisplayVersion" has been introduced in Windows 10 2009 // when naming changed to mixed letters and numbers. key.ReadValue(L"DisplayVersion", &release_id); // Use discontinued "ReleaseId" instead, if the former is unavailable. if (release_id.empty()) key.ReadValue(L"ReleaseId", &release_id); } return std::make_pair(static_cast(ubr), WideToUTF8(release_id)); } const _SYSTEM_INFO& GetSystemInfoStorage() { static const _SYSTEM_INFO system_info = [] { _SYSTEM_INFO info = {}; ::GetNativeSystemInfo(&info); return info; }(); return system_info; } } // namespace // static OSInfo** OSInfo::GetInstanceStorage() { // Note: we don't use the Singleton class because it depends on AtExitManager, // and it's convenient for other modules to use this class without it. static OSInfo* info = []() { _OSVERSIONINFOEXW version_info = {sizeof(version_info)}; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // GetVersionEx() is deprecated, and the suggested replacement are // the IsWindows*OrGreater() functions in VersionHelpers.h. We can't // use that because: // - For Windows 10, there's IsWindows10OrGreater(), but nothing more // granular. We need to be able to detect different Windows 10 releases // since they sometimes change behavior in ways that matter. // - There is no IsWindows11OrGreater() function yet. ::GetVersionEx(reinterpret_cast<_OSVERSIONINFOW*>(&version_info)); #pragma clang diagnostic pop DWORD os_type = 0; ::GetProductInfo(version_info.dwMajorVersion, version_info.dwMinorVersion, 0, 0, &os_type); return new OSInfo(version_info, GetSystemInfoStorage(), os_type); }(); return &info; } // static OSInfo* OSInfo::GetInstance() { return *GetInstanceStorage(); } // static OSInfo::WindowsArchitecture OSInfo::GetArchitecture() { switch (GetSystemInfoStorage().wProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_INTEL: return X86_ARCHITECTURE; case PROCESSOR_ARCHITECTURE_AMD64: return X64_ARCHITECTURE; case PROCESSOR_ARCHITECTURE_IA64: return IA64_ARCHITECTURE; case PROCESSOR_ARCHITECTURE_ARM64: return ARM64_ARCHITECTURE; default: return OTHER_ARCHITECTURE; } } // Returns true if this is an x86/x64 process running on ARM64 through // emulation. // static bool OSInfo::IsRunningEmulatedOnArm64() { #if defined(ARCH_CPU_ARM64) // If we're running native ARM64 then we aren't running emulated. return false; #else using IsWow64Process2Function = decltype(&IsWow64Process2); IsWow64Process2Function is_wow64_process2 = reinterpret_cast(::GetProcAddress( ::GetModuleHandleA("kernel32.dll"), "IsWow64Process2")); if (!is_wow64_process2) { return false; } USHORT process_machine; USHORT native_machine; bool retval = is_wow64_process2(::GetCurrentProcess(), &process_machine, &native_machine); if (!retval) { return false; } if (native_machine == IMAGE_FILE_MACHINE_ARM64) { return true; } return false; #endif } OSInfo::OSInfo(const _OSVERSIONINFOEXW& version_info, const _SYSTEM_INFO& system_info, DWORD os_type) : version_(Version::PRE_XP), wow_process_machine_(WowProcessMachine::kUnknown), wow_native_machine_(WowNativeMachine::kUnknown) { version_number_.major = version_info.dwMajorVersion; version_number_.minor = version_info.dwMinorVersion; version_number_.build = version_info.dwBuildNumber; std::tie(version_number_.patch, release_id_) = GetVersionData(); version_ = MajorMinorBuildToVersion( version_number_.major, version_number_.minor, version_number_.build); InitializeWowStatusValuesForProcess(GetCurrentProcess()); service_pack_.major = version_info.wServicePackMajor; service_pack_.minor = version_info.wServicePackMinor; service_pack_str_ = WideToUTF8(version_info.szCSDVersion); processors_ = static_cast(system_info.dwNumberOfProcessors); allocation_granularity_ = system_info.dwAllocationGranularity; if (version_info.dwMajorVersion == 6 || version_info.dwMajorVersion == 10) { // Only present on Vista+. switch (os_type) { case PRODUCT_CLUSTER_SERVER: case PRODUCT_DATACENTER_SERVER: case PRODUCT_DATACENTER_SERVER_CORE: case PRODUCT_ENTERPRISE_SERVER: case PRODUCT_ENTERPRISE_SERVER_CORE: case PRODUCT_ENTERPRISE_SERVER_IA64: case PRODUCT_SMALLBUSINESS_SERVER: case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM: case PRODUCT_STANDARD_SERVER: case PRODUCT_STANDARD_SERVER_CORE: case PRODUCT_WEB_SERVER: version_type_ = SUITE_SERVER; break; case PRODUCT_PROFESSIONAL: case PRODUCT_ULTIMATE: version_type_ = SUITE_PROFESSIONAL; break; case PRODUCT_ENTERPRISE: case PRODUCT_ENTERPRISE_E: case PRODUCT_ENTERPRISE_EVALUATION: case PRODUCT_ENTERPRISE_N: case PRODUCT_ENTERPRISE_N_EVALUATION: case PRODUCT_ENTERPRISE_S: case PRODUCT_ENTERPRISE_S_EVALUATION: case PRODUCT_ENTERPRISE_S_N: case PRODUCT_ENTERPRISE_S_N_EVALUATION: case PRODUCT_BUSINESS: case PRODUCT_BUSINESS_N: version_type_ = SUITE_ENTERPRISE; break; case PRODUCT_PRO_FOR_EDUCATION: case PRODUCT_PRO_FOR_EDUCATION_N: version_type_ = SUITE_EDUCATION_PRO; break; case PRODUCT_EDUCATION: case PRODUCT_EDUCATION_N: version_type_ = SUITE_EDUCATION; break; case PRODUCT_HOME_BASIC: case PRODUCT_HOME_PREMIUM: case PRODUCT_STARTER: default: version_type_ = SUITE_HOME; break; } } else if (version_info.dwMajorVersion == 5 && version_info.dwMinorVersion == 2) { if (version_info.wProductType == VER_NT_WORKSTATION && system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { version_type_ = SUITE_PROFESSIONAL; } else if (version_info.wSuiteMask & VER_SUITE_WH_SERVER) { version_type_ = SUITE_HOME; } else { version_type_ = SUITE_SERVER; } } else if (version_info.dwMajorVersion == 5 && version_info.dwMinorVersion == 1) { if (version_info.wSuiteMask & VER_SUITE_PERSONAL) version_type_ = SUITE_HOME; else version_type_ = SUITE_PROFESSIONAL; } else { // Windows is pre XP so we don't care but pick a safe default. version_type_ = SUITE_HOME; } } OSInfo::~OSInfo() = default; Version OSInfo::Kernel32Version() { static const Version kernel32_version = MajorMinorBuildToVersion(Kernel32BaseVersion().components()[0], Kernel32BaseVersion().components()[1], Kernel32BaseVersion().components()[2]); return kernel32_version; } OSInfo::VersionNumber OSInfo::Kernel32VersionNumber() { DCHECK_EQ(Kernel32BaseVersion().components().size(), 4u); static const VersionNumber version = { .major = Kernel32BaseVersion().components()[0], .minor = Kernel32BaseVersion().components()[1], .build = Kernel32BaseVersion().components()[2], .patch = Kernel32BaseVersion().components()[3]}; return version; } // Retrieve a version from kernel32. This is useful because when running in // compatibility mode for a down-level version of the OS, the file version of // kernel32 will still be the "real" version. base::Version OSInfo::Kernel32BaseVersion() { static const NoDestructor version([] { // Allow the calls to `Kernel32BaseVersion()` to block, as they only happen // once (after which the result is cached in `version`), and reading from // kernel32.dll is fast in practice because it is used by all processes and // therefore likely to be in the OS's file cache. base::ScopedAllowBlocking allow_blocking; std::unique_ptr file_version_info = FileVersionInfoWin::CreateFileVersionInfoWin( FilePath(FILE_PATH_LITERAL("kernel32.dll"))); if (!file_version_info) { // crbug.com/912061: on some systems it seems kernel32.dll might be // corrupted or not in a state to get version info. In this case try // kernelbase.dll as a fallback. file_version_info = FileVersionInfoWin::CreateFileVersionInfoWin( FilePath(FILE_PATH_LITERAL("kernelbase.dll"))); } CHECK(file_version_info); return file_version_info->GetFileVersion(); }()); return *version; } bool OSInfo::IsWowDisabled() const { return (wow_process_machine_ == WowProcessMachine::kDisabled); } bool OSInfo::IsWowX86OnAMD64() const { return (wow_process_machine_ == WowProcessMachine::kX86 && wow_native_machine_ == WowNativeMachine::kAMD64); } bool OSInfo::IsWowX86OnARM64() const { return (wow_process_machine_ == WowProcessMachine::kX86 && wow_native_machine_ == WowNativeMachine::kARM64); } bool OSInfo::IsWowAMD64OnARM64() const { #if defined(ARCH_CPU_X86_64) // An AMD64 process running on an ARM64 device results in the incorrect // identification of the device architecture (AMD64 is reported). However, // IsWow64Process2 will return the correct device type for the native // machine, even though the OS doesn't consider an AMD64 process on an ARM64 // processor a classic Windows-on-Windows setup. return (wow_process_machine_ == WowProcessMachine::kDisabled && wow_native_machine_ == WowNativeMachine::kARM64); #else return false; #endif } bool OSInfo::IsWowX86OnOther() const { return (wow_process_machine_ == WowProcessMachine::kX86 && wow_native_machine_ == WowNativeMachine::kOther); } std::string OSInfo::processor_model_name() { if (processor_model_name_.empty()) { const wchar_t kProcessorNameString[] = L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; RegKey key(HKEY_LOCAL_MACHINE, kProcessorNameString, KEY_READ); std::wstring value; key.ReadValue(L"ProcessorNameString", &value); processor_model_name_ = WideToUTF8(value); } return processor_model_name_; } // With the exception of Server 2003, server variants are treated the same as // the corresponding workstation release. // static Version OSInfo::MajorMinorBuildToVersion(uint32_t major, uint32_t minor, uint32_t build) { if (major == 11) { // We know nothing about this version of Windows or even if it exists. // Known Windows 11 versions have a major number 10 and are thus handled by // the == 10 block below. return Version::WIN11; } if (major == 10) { if (build >= 22621) { return Version::WIN11_22H2; } if (build >= 22000) { return Version::WIN11; } if (build >= 20348) { return Version::SERVER_2022; } if (build >= 19045) { return Version::WIN10_22H2; } if (build >= 19044) { return Version::WIN10_21H2; } if (build >= 19043) { return Version::WIN10_21H1; } if (build >= 19042) { return Version::WIN10_20H2; } if (build >= 19041) { return Version::WIN10_20H1; } if (build >= 18363) { return Version::WIN10_19H2; } if (build >= 18362) { return Version::WIN10_19H1; } if (build >= 17763) { return Version::WIN10_RS5; } if (build >= 17134) { return Version::WIN10_RS4; } if (build >= 16299) { return Version::WIN10_RS3; } if (build >= 15063) { return Version::WIN10_RS2; } if (build >= 14393) { return Version::WIN10_RS1; } if (build >= 10586) { return Version::WIN10_TH2; } return Version::WIN10; } if (major > 6) { // Hitting this likely means that it's time for a >11 block above. NOTREACHED() << major << "." << minor << "." << build; return Version::WIN_LAST; } if (major == 6) { switch (minor) { case 0: return Version::VISTA; case 1: return Version::WIN7; case 2: return Version::WIN8; default: DCHECK_EQ(minor, 3u); return Version::WIN8_1; } } if (major == 5 && minor != 0) { // Treat XP Pro x64, Home Server, and Server 2003 R2 as Server 2003. return minor == 1 ? Version::XP : Version::SERVER_2003; } // Win 2000 or older. return Version::PRE_XP; } Version GetVersion() { return OSInfo::GetInstance()->version(); } OSInfo::WowProcessMachine OSInfo::GetWowProcessMachineArchitecture( const int process_machine) { switch (process_machine) { case IMAGE_FILE_MACHINE_UNKNOWN: return OSInfo::WowProcessMachine::kDisabled; case IMAGE_FILE_MACHINE_I386: return OSInfo::WowProcessMachine::kX86; case IMAGE_FILE_MACHINE_ARM: case IMAGE_FILE_MACHINE_THUMB: case IMAGE_FILE_MACHINE_ARMNT: return OSInfo::WowProcessMachine::kARM32; } return OSInfo::WowProcessMachine::kOther; } OSInfo::WowNativeMachine OSInfo::GetWowNativeMachineArchitecture( const int native_machine) { switch (native_machine) { case IMAGE_FILE_MACHINE_ARM64: return OSInfo::WowNativeMachine::kARM64; case IMAGE_FILE_MACHINE_AMD64: return OSInfo::WowNativeMachine::kAMD64; } return OSInfo::WowNativeMachine::kOther; } void OSInfo::InitializeWowStatusValuesFromLegacyApi(HANDLE process_handle) { BOOL is_wow64 = FALSE; if (!::IsWow64Process(process_handle, &is_wow64)) return; if (is_wow64) { wow_process_machine_ = WowProcessMachine::kX86; wow_native_machine_ = WowNativeMachine::kAMD64; } else { wow_process_machine_ = WowProcessMachine::kDisabled; } } void OSInfo::InitializeWowStatusValuesForProcess(HANDLE process_handle) { static const auto is_wow64_process2 = reinterpret_cast(::GetProcAddress( ::GetModuleHandle(L"kernel32.dll"), "IsWow64Process2")); if (!is_wow64_process2) { InitializeWowStatusValuesFromLegacyApi(process_handle); return; } USHORT process_machine = IMAGE_FILE_MACHINE_UNKNOWN; USHORT native_machine = IMAGE_FILE_MACHINE_UNKNOWN; if (!is_wow64_process2(process_handle, &process_machine, &native_machine)) { return; } wow_process_machine_ = GetWowProcessMachineArchitecture(process_machine); wow_native_machine_ = GetWowNativeMachineArchitecture(native_machine); } } // namespace win } // namespace base