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/win/windows_version.h"
6
7 #include <windows.h>
8
9 #include <memory>
10 #include <tuple>
11 #include <utility>
12
13 #include "base/check_op.h"
14 #include "base/file_version_info_win.h"
15 #include "base/files/file_path.h"
16 #include "base/logging.h"
17 #include "base/no_destructor.h"
18 #include "base/notreached.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/threading/thread_restrictions.h"
22 #include "base/win/registry.h"
23 #include "build/build_config.h"
24
25 #if !defined(__clang__) && _MSC_FULL_VER < 191125507
26 #error VS 2017 Update 3.2 or higher is required
27 #endif
28
29 #if !defined(NTDDI_WIN10_NI)
30 #error Windows 10.0.22621.0 SDK or higher required.
31 #endif
32
33 namespace base {
34 namespace win {
35
36 namespace {
37
38 // The values under the CurrentVersion registry hive are mirrored under
39 // the corresponding Wow6432 hive.
40 constexpr wchar_t kRegKeyWindowsNTCurrentVersion[] =
41 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
42
43 // Returns the "UBR" (Windows 10 patch number) and "DisplayVersion" (or
44 // "ReleaseId" on earlier versions) (Windows 10 release number) from registry.
45 // "UBR" is an undocumented value and will be 0 if the value was not found.
46 // "ReleaseId" will be an empty string if neither new nor old values are found.
GetVersionData()47 std::pair<int, std::string> GetVersionData() {
48 DWORD ubr = 0;
49 std::wstring release_id;
50 RegKey key;
51
52 if (key.Open(HKEY_LOCAL_MACHINE, kRegKeyWindowsNTCurrentVersion,
53 KEY_QUERY_VALUE) == ERROR_SUCCESS) {
54 key.ReadValueDW(L"UBR", &ubr);
55 // "DisplayVersion" has been introduced in Windows 10 2009
56 // when naming changed to mixed letters and numbers.
57 key.ReadValue(L"DisplayVersion", &release_id);
58 // Use discontinued "ReleaseId" instead, if the former is unavailable.
59 if (release_id.empty())
60 key.ReadValue(L"ReleaseId", &release_id);
61 }
62
63 return std::make_pair(static_cast<int>(ubr), WideToUTF8(release_id));
64 }
65
GetSystemInfoStorage()66 const _SYSTEM_INFO& GetSystemInfoStorage() {
67 static const _SYSTEM_INFO system_info = [] {
68 _SYSTEM_INFO info = {};
69 ::GetNativeSystemInfo(&info);
70 return info;
71 }();
72 return system_info;
73 }
74
75 } // namespace
76
77 // static
GetInstanceStorage()78 OSInfo** OSInfo::GetInstanceStorage() {
79 // Note: we don't use the Singleton class because it depends on AtExitManager,
80 // and it's convenient for other modules to use this class without it.
81 static OSInfo* info = []() {
82 _OSVERSIONINFOEXW version_info = {sizeof(version_info)};
83
84 #pragma clang diagnostic push
85 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
86 // GetVersionEx() is deprecated, and the suggested replacement are
87 // the IsWindows*OrGreater() functions in VersionHelpers.h. We can't
88 // use that because:
89 // - For Windows 10, there's IsWindows10OrGreater(), but nothing more
90 // granular. We need to be able to detect different Windows 10 releases
91 // since they sometimes change behavior in ways that matter.
92 // - There is no IsWindows11OrGreater() function yet.
93 ::GetVersionEx(reinterpret_cast<_OSVERSIONINFOW*>(&version_info));
94 #pragma clang diagnostic pop
95
96 DWORD os_type = 0;
97 ::GetProductInfo(version_info.dwMajorVersion, version_info.dwMinorVersion,
98 0, 0, &os_type);
99
100 return new OSInfo(version_info, GetSystemInfoStorage(), os_type);
101 }();
102
103 return &info;
104 }
105
106 // static
GetInstance()107 OSInfo* OSInfo::GetInstance() {
108 return *GetInstanceStorage();
109 }
110
111 // static
GetArchitecture()112 OSInfo::WindowsArchitecture OSInfo::GetArchitecture() {
113 switch (GetSystemInfoStorage().wProcessorArchitecture) {
114 case PROCESSOR_ARCHITECTURE_INTEL:
115 return X86_ARCHITECTURE;
116 case PROCESSOR_ARCHITECTURE_AMD64:
117 return X64_ARCHITECTURE;
118 case PROCESSOR_ARCHITECTURE_IA64:
119 return IA64_ARCHITECTURE;
120 case PROCESSOR_ARCHITECTURE_ARM64:
121 return ARM64_ARCHITECTURE;
122 default:
123 return OTHER_ARCHITECTURE;
124 }
125 }
126
127 // Returns true if this is an x86/x64 process running on ARM64 through
128 // emulation.
129 // static
IsRunningEmulatedOnArm64()130 bool OSInfo::IsRunningEmulatedOnArm64() {
131 #if defined(ARCH_CPU_ARM64)
132 // If we're running native ARM64 then we aren't running emulated.
133 return false;
134 #else
135 using IsWow64Process2Function = decltype(&IsWow64Process2);
136
137 IsWow64Process2Function is_wow64_process2 =
138 reinterpret_cast<IsWow64Process2Function>(::GetProcAddress(
139 ::GetModuleHandleA("kernel32.dll"), "IsWow64Process2"));
140 if (!is_wow64_process2) {
141 return false;
142 }
143 USHORT process_machine;
144 USHORT native_machine;
145 bool retval = is_wow64_process2(::GetCurrentProcess(), &process_machine,
146 &native_machine);
147 if (!retval) {
148 return false;
149 }
150 if (native_machine == IMAGE_FILE_MACHINE_ARM64) {
151 return true;
152 }
153 return false;
154 #endif
155 }
156
OSInfo(const _OSVERSIONINFOEXW & version_info,const _SYSTEM_INFO & system_info,DWORD os_type)157 OSInfo::OSInfo(const _OSVERSIONINFOEXW& version_info,
158 const _SYSTEM_INFO& system_info,
159 DWORD os_type)
160 : version_(Version::PRE_XP),
161 wow_process_machine_(WowProcessMachine::kUnknown),
162 wow_native_machine_(WowNativeMachine::kUnknown) {
163 version_number_.major = version_info.dwMajorVersion;
164 version_number_.minor = version_info.dwMinorVersion;
165 version_number_.build = version_info.dwBuildNumber;
166 std::tie(version_number_.patch, release_id_) = GetVersionData();
167 version_ = MajorMinorBuildToVersion(
168 version_number_.major, version_number_.minor, version_number_.build);
169 InitializeWowStatusValuesForProcess(GetCurrentProcess());
170 service_pack_.major = version_info.wServicePackMajor;
171 service_pack_.minor = version_info.wServicePackMinor;
172 service_pack_str_ = WideToUTF8(version_info.szCSDVersion);
173
174 processors_ = static_cast<int>(system_info.dwNumberOfProcessors);
175 allocation_granularity_ = system_info.dwAllocationGranularity;
176
177 if (version_info.dwMajorVersion == 6 || version_info.dwMajorVersion == 10) {
178 // Only present on Vista+.
179 switch (os_type) {
180 case PRODUCT_CLUSTER_SERVER:
181 case PRODUCT_DATACENTER_SERVER:
182 case PRODUCT_DATACENTER_SERVER_CORE:
183 case PRODUCT_ENTERPRISE_SERVER:
184 case PRODUCT_ENTERPRISE_SERVER_CORE:
185 case PRODUCT_ENTERPRISE_SERVER_IA64:
186 case PRODUCT_SMALLBUSINESS_SERVER:
187 case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM:
188 case PRODUCT_STANDARD_SERVER:
189 case PRODUCT_STANDARD_SERVER_CORE:
190 case PRODUCT_WEB_SERVER:
191 version_type_ = SUITE_SERVER;
192 break;
193 case PRODUCT_PROFESSIONAL:
194 case PRODUCT_ULTIMATE:
195 version_type_ = SUITE_PROFESSIONAL;
196 break;
197 case PRODUCT_ENTERPRISE:
198 case PRODUCT_ENTERPRISE_E:
199 case PRODUCT_ENTERPRISE_EVALUATION:
200 case PRODUCT_ENTERPRISE_N:
201 case PRODUCT_ENTERPRISE_N_EVALUATION:
202 case PRODUCT_ENTERPRISE_S:
203 case PRODUCT_ENTERPRISE_S_EVALUATION:
204 case PRODUCT_ENTERPRISE_S_N:
205 case PRODUCT_ENTERPRISE_S_N_EVALUATION:
206 case PRODUCT_BUSINESS:
207 case PRODUCT_BUSINESS_N:
208 version_type_ = SUITE_ENTERPRISE;
209 break;
210 case PRODUCT_PRO_FOR_EDUCATION:
211 case PRODUCT_PRO_FOR_EDUCATION_N:
212 version_type_ = SUITE_EDUCATION_PRO;
213 break;
214 case PRODUCT_EDUCATION:
215 case PRODUCT_EDUCATION_N:
216 version_type_ = SUITE_EDUCATION;
217 break;
218 case PRODUCT_HOME_BASIC:
219 case PRODUCT_HOME_PREMIUM:
220 case PRODUCT_STARTER:
221 default:
222 version_type_ = SUITE_HOME;
223 break;
224 }
225 } else if (version_info.dwMajorVersion == 5 &&
226 version_info.dwMinorVersion == 2) {
227 if (version_info.wProductType == VER_NT_WORKSTATION &&
228 system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
229 version_type_ = SUITE_PROFESSIONAL;
230 } else if (version_info.wSuiteMask & VER_SUITE_WH_SERVER) {
231 version_type_ = SUITE_HOME;
232 } else {
233 version_type_ = SUITE_SERVER;
234 }
235 } else if (version_info.dwMajorVersion == 5 &&
236 version_info.dwMinorVersion == 1) {
237 if (version_info.wSuiteMask & VER_SUITE_PERSONAL)
238 version_type_ = SUITE_HOME;
239 else
240 version_type_ = SUITE_PROFESSIONAL;
241 } else {
242 // Windows is pre XP so we don't care but pick a safe default.
243 version_type_ = SUITE_HOME;
244 }
245 }
246
247 OSInfo::~OSInfo() = default;
248
Kernel32Version()249 Version OSInfo::Kernel32Version() {
250 static const Version kernel32_version =
251 MajorMinorBuildToVersion(Kernel32BaseVersion().components()[0],
252 Kernel32BaseVersion().components()[1],
253 Kernel32BaseVersion().components()[2]);
254 return kernel32_version;
255 }
256
Kernel32VersionNumber()257 OSInfo::VersionNumber OSInfo::Kernel32VersionNumber() {
258 DCHECK_EQ(Kernel32BaseVersion().components().size(), 4u);
259 static const VersionNumber version = {
260 .major = Kernel32BaseVersion().components()[0],
261 .minor = Kernel32BaseVersion().components()[1],
262 .build = Kernel32BaseVersion().components()[2],
263 .patch = Kernel32BaseVersion().components()[3]};
264 return version;
265 }
266
267 // Retrieve a version from kernel32. This is useful because when running in
268 // compatibility mode for a down-level version of the OS, the file version of
269 // kernel32 will still be the "real" version.
Kernel32BaseVersion()270 base::Version OSInfo::Kernel32BaseVersion() {
271 static const NoDestructor<base::Version> version([] {
272 // Allow the calls to `Kernel32BaseVersion()` to block, as they only happen
273 // once (after which the result is cached in `version`), and reading from
274 // kernel32.dll is fast in practice because it is used by all processes and
275 // therefore likely to be in the OS's file cache.
276 base::ScopedAllowBlocking allow_blocking;
277 std::unique_ptr<FileVersionInfoWin> file_version_info =
278 FileVersionInfoWin::CreateFileVersionInfoWin(
279 FilePath(FILE_PATH_LITERAL("kernel32.dll")));
280 if (!file_version_info) {
281 // crbug.com/912061: on some systems it seems kernel32.dll might be
282 // corrupted or not in a state to get version info. In this case try
283 // kernelbase.dll as a fallback.
284 file_version_info = FileVersionInfoWin::CreateFileVersionInfoWin(
285 FilePath(FILE_PATH_LITERAL("kernelbase.dll")));
286 }
287 CHECK(file_version_info);
288 return file_version_info->GetFileVersion();
289 }());
290 return *version;
291 }
292
IsWowDisabled() const293 bool OSInfo::IsWowDisabled() const {
294 return (wow_process_machine_ == WowProcessMachine::kDisabled);
295 }
296
IsWowX86OnAMD64() const297 bool OSInfo::IsWowX86OnAMD64() const {
298 return (wow_process_machine_ == WowProcessMachine::kX86 &&
299 wow_native_machine_ == WowNativeMachine::kAMD64);
300 }
301
IsWowX86OnARM64() const302 bool OSInfo::IsWowX86OnARM64() const {
303 return (wow_process_machine_ == WowProcessMachine::kX86 &&
304 wow_native_machine_ == WowNativeMachine::kARM64);
305 }
306
IsWowAMD64OnARM64() const307 bool OSInfo::IsWowAMD64OnARM64() const {
308 #if defined(ARCH_CPU_X86_64)
309 // An AMD64 process running on an ARM64 device results in the incorrect
310 // identification of the device architecture (AMD64 is reported). However,
311 // IsWow64Process2 will return the correct device type for the native
312 // machine, even though the OS doesn't consider an AMD64 process on an ARM64
313 // processor a classic Windows-on-Windows setup.
314 return (wow_process_machine_ == WowProcessMachine::kDisabled &&
315 wow_native_machine_ == WowNativeMachine::kARM64);
316 #else
317 return false;
318 #endif
319 }
320
IsWowX86OnOther() const321 bool OSInfo::IsWowX86OnOther() const {
322 return (wow_process_machine_ == WowProcessMachine::kX86 &&
323 wow_native_machine_ == WowNativeMachine::kOther);
324 }
325
processor_model_name()326 std::string OSInfo::processor_model_name() {
327 if (processor_model_name_.empty()) {
328 const wchar_t kProcessorNameString[] =
329 L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
330 RegKey key(HKEY_LOCAL_MACHINE, kProcessorNameString, KEY_READ);
331 std::wstring value;
332 key.ReadValue(L"ProcessorNameString", &value);
333 processor_model_name_ = WideToUTF8(value);
334 }
335 return processor_model_name_;
336 }
337
338 // With the exception of Server 2003, server variants are treated the same as
339 // the corresponding workstation release.
340 // static
MajorMinorBuildToVersion(uint32_t major,uint32_t minor,uint32_t build)341 Version OSInfo::MajorMinorBuildToVersion(uint32_t major,
342 uint32_t minor,
343 uint32_t build) {
344 if (major == 11) {
345 // We know nothing about this version of Windows or even if it exists.
346 // Known Windows 11 versions have a major number 10 and are thus handled by
347 // the == 10 block below.
348 return Version::WIN11;
349 }
350
351 if (major == 10) {
352 if (build >= 22621) {
353 return Version::WIN11_22H2;
354 }
355 if (build >= 22000) {
356 return Version::WIN11;
357 }
358 if (build >= 20348) {
359 return Version::SERVER_2022;
360 }
361 if (build >= 19045) {
362 return Version::WIN10_22H2;
363 }
364 if (build >= 19044) {
365 return Version::WIN10_21H2;
366 }
367 if (build >= 19043) {
368 return Version::WIN10_21H1;
369 }
370 if (build >= 19042) {
371 return Version::WIN10_20H2;
372 }
373 if (build >= 19041) {
374 return Version::WIN10_20H1;
375 }
376 if (build >= 18363) {
377 return Version::WIN10_19H2;
378 }
379 if (build >= 18362) {
380 return Version::WIN10_19H1;
381 }
382 if (build >= 17763) {
383 return Version::WIN10_RS5;
384 }
385 if (build >= 17134) {
386 return Version::WIN10_RS4;
387 }
388 if (build >= 16299) {
389 return Version::WIN10_RS3;
390 }
391 if (build >= 15063) {
392 return Version::WIN10_RS2;
393 }
394 if (build >= 14393) {
395 return Version::WIN10_RS1;
396 }
397 if (build >= 10586) {
398 return Version::WIN10_TH2;
399 }
400 return Version::WIN10;
401 }
402
403 if (major > 6) {
404 // Hitting this likely means that it's time for a >11 block above.
405 NOTREACHED() << major << "." << minor << "." << build;
406 return Version::WIN_LAST;
407 }
408
409 if (major == 6) {
410 switch (minor) {
411 case 0:
412 return Version::VISTA;
413 case 1:
414 return Version::WIN7;
415 case 2:
416 return Version::WIN8;
417 default:
418 DCHECK_EQ(minor, 3u);
419 return Version::WIN8_1;
420 }
421 }
422
423 if (major == 5 && minor != 0) {
424 // Treat XP Pro x64, Home Server, and Server 2003 R2 as Server 2003.
425 return minor == 1 ? Version::XP : Version::SERVER_2003;
426 }
427
428 // Win 2000 or older.
429 return Version::PRE_XP;
430 }
431
GetVersion()432 Version GetVersion() {
433 return OSInfo::GetInstance()->version();
434 }
435
GetWowProcessMachineArchitecture(const int process_machine)436 OSInfo::WowProcessMachine OSInfo::GetWowProcessMachineArchitecture(
437 const int process_machine) {
438 switch (process_machine) {
439 case IMAGE_FILE_MACHINE_UNKNOWN:
440 return OSInfo::WowProcessMachine::kDisabled;
441 case IMAGE_FILE_MACHINE_I386:
442 return OSInfo::WowProcessMachine::kX86;
443 case IMAGE_FILE_MACHINE_ARM:
444 case IMAGE_FILE_MACHINE_THUMB:
445 case IMAGE_FILE_MACHINE_ARMNT:
446 return OSInfo::WowProcessMachine::kARM32;
447 }
448 return OSInfo::WowProcessMachine::kOther;
449 }
450
GetWowNativeMachineArchitecture(const int native_machine)451 OSInfo::WowNativeMachine OSInfo::GetWowNativeMachineArchitecture(
452 const int native_machine) {
453 switch (native_machine) {
454 case IMAGE_FILE_MACHINE_ARM64:
455 return OSInfo::WowNativeMachine::kARM64;
456 case IMAGE_FILE_MACHINE_AMD64:
457 return OSInfo::WowNativeMachine::kAMD64;
458 }
459 return OSInfo::WowNativeMachine::kOther;
460 }
461
InitializeWowStatusValuesFromLegacyApi(HANDLE process_handle)462 void OSInfo::InitializeWowStatusValuesFromLegacyApi(HANDLE process_handle) {
463 BOOL is_wow64 = FALSE;
464 if (!::IsWow64Process(process_handle, &is_wow64))
465 return;
466 if (is_wow64) {
467 wow_process_machine_ = WowProcessMachine::kX86;
468 wow_native_machine_ = WowNativeMachine::kAMD64;
469 } else {
470 wow_process_machine_ = WowProcessMachine::kDisabled;
471 }
472 }
473
InitializeWowStatusValuesForProcess(HANDLE process_handle)474 void OSInfo::InitializeWowStatusValuesForProcess(HANDLE process_handle) {
475 static const auto is_wow64_process2 =
476 reinterpret_cast<decltype(&IsWow64Process2)>(::GetProcAddress(
477 ::GetModuleHandle(L"kernel32.dll"), "IsWow64Process2"));
478 if (!is_wow64_process2) {
479 InitializeWowStatusValuesFromLegacyApi(process_handle);
480 return;
481 }
482
483 USHORT process_machine = IMAGE_FILE_MACHINE_UNKNOWN;
484 USHORT native_machine = IMAGE_FILE_MACHINE_UNKNOWN;
485 if (!is_wow64_process2(process_handle, &process_machine, &native_machine)) {
486 return;
487 }
488 wow_process_machine_ = GetWowProcessMachineArchitecture(process_machine);
489 wow_native_machine_ = GetWowNativeMachineArchitecture(native_machine);
490 }
491
492 } // namespace win
493 } // namespace base
494