// Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "util/mac/mac_util.h" #include #include #include #include #include #include #include "base/check_op.h" #include "base/logging.h" #include "base/mac/foundation_util.h" #include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_ioobject.h" #include "base/notreached.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" #include "build/build_config.h" #include "util/mac/sysctl.h" extern "C" { // Private CoreFoundation internals. See 10.9.2 CF-855.14/CFPriv.h and // CF-855.14/CFUtilities.c. These are marked for weak import because they’re // private and subject to change. #define WEAK_IMPORT __attribute__((weak_import)) // Don’t call these functions directly, call them through the // TryCFCopy*VersionDictionary() helpers to account for the possibility that // they may not be present at runtime. CFDictionaryRef _CFCopySystemVersionDictionary() WEAK_IMPORT; CFDictionaryRef _CFCopyServerVersionDictionary() WEAK_IMPORT; // Don’t use these constants with CFDictionaryGetValue() directly, use them with // the TryCFDictionaryGetValue() wrapper to account for the possibility that // they may not be present at runtime. extern const CFStringRef _kCFSystemVersionProductNameKey WEAK_IMPORT; extern const CFStringRef _kCFSystemVersionProductVersionKey WEAK_IMPORT; extern const CFStringRef _kCFSystemVersionProductVersionExtraKey WEAK_IMPORT; extern const CFStringRef _kCFSystemVersionBuildVersionKey WEAK_IMPORT; #undef WEAK_IMPORT } // extern "C" namespace { #if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_13_4 // Returns the running system’s Darwin major version. Don’t call this, it’s an // implementation detail and its result is meant to be cached by // MacOSVersionNumber(). // // This is very similar to Chromium’s base/mac/mac_util.mm // DarwinMajorVersionInternal(). int DarwinMajorVersion() { // base::OperatingSystemVersionNumbers calls Gestalt(), which is a // higher-level function than is needed. It might perform unnecessary // operations. On 10.6, it was observed to be able to spawn threads (see // https://crbug.com/53200). It might also read files or perform other // blocking operations. Actually, nobody really knows for sure just what // Gestalt() might do, or what it might be taught to do in the future. // // uname(), on the other hand, is implemented as a simple series of sysctl() // system calls to obtain the relevant data from the kernel. The data is // compiled right into the kernel, so no threads or blocking or other funny // business is necessary. utsname uname_info; int rv = uname(&uname_info); PCHECK(rv == 0) << "uname"; DCHECK_EQ(strcmp(uname_info.sysname, "Darwin"), 0) << "unexpected sysname " << uname_info.sysname; char* dot = strchr(uname_info.release, '.'); CHECK(dot); int darwin_major_version = 0; CHECK(base::StringToInt( base::StringPiece(uname_info.release, dot - uname_info.release), &darwin_major_version)); return darwin_major_version; } #endif // DT < 10.13.4 // Helpers for the weak-imported private CoreFoundation internals. CFDictionaryRef TryCFCopySystemVersionDictionary() { if (_CFCopySystemVersionDictionary) { return _CFCopySystemVersionDictionary(); } return nullptr; } CFDictionaryRef TryCFCopyServerVersionDictionary() { if (_CFCopyServerVersionDictionary) { return _CFCopyServerVersionDictionary(); } return nullptr; } const void* TryCFDictionaryGetValue(CFDictionaryRef dictionary, const void* value) { if (value) { return CFDictionaryGetValue(dictionary, value); } return nullptr; } // Converts |version| to a triplet of version numbers on behalf of // MacOSVersionNumber() and MacOSVersionComponents(). Returns true on success. // If |version| does not have the expected format, returns false. |version| must // be in the form "10.9.2" or just "10.9". In the latter case, |bugfix| will be // set to 0. bool StringToVersionNumbers(const std::string& version, int* major, int* minor, int* bugfix) { size_t first_dot = version.find_first_of('.'); if (first_dot == 0 || first_dot == std::string::npos || first_dot == version.length() - 1) { LOG(ERROR) << "version has unexpected format"; return false; } if (!base::StringToInt(base::StringPiece(&version[0], first_dot), major)) { LOG(ERROR) << "version has unexpected format"; return false; } size_t second_dot = version.find_first_of('.', first_dot + 1); if (second_dot == version.length() - 1) { LOG(ERROR) << "version has unexpected format"; return false; } else if (second_dot == std::string::npos) { second_dot = version.length(); } if (!base::StringToInt(base::StringPiece(&version[first_dot + 1], second_dot - first_dot - 1), minor)) { LOG(ERROR) << "version has unexpected format"; return false; } if (second_dot == version.length()) { *bugfix = 0; } else if (!base::StringToInt( base::StringPiece(&version[second_dot + 1], version.length() - second_dot - 1), bugfix)) { LOG(ERROR) << "version has unexpected format"; return false; } return true; } std::string IORegistryEntryDataPropertyAsString(io_registry_entry_t entry, CFStringRef key) { base::ScopedCFTypeRef property( IORegistryEntryCreateCFProperty(entry, key, kCFAllocatorDefault, 0)); CFDataRef data = base::mac::CFCast(property); if (data && CFDataGetLength(data) > 0) { return reinterpret_cast(CFDataGetBytePtr(data)); } return std::string(); } } // namespace namespace crashpad { int MacOSVersionNumber() { static int macos_version_number = []() { // kern.osproductversion is a lightweight way to get the operating system // version from the kernel without having to open any files or spin up any // threads, but it’s only available in macOS 10.13.4 and later. std::string macos_version_number_string = ReadStringSysctlByName( "kern.osproductversion", __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_13_4); if (!macos_version_number_string.empty()) { int major; int minor; int bugfix; if (StringToVersionNumbers( macos_version_number_string, &major, &minor, &bugfix)) { DCHECK_GE(major, 10); DCHECK_LE(major, 99); DCHECK_GE(minor, 0); DCHECK_LE(minor, 99); DCHECK_GE(bugfix, 0); DCHECK_LE(bugfix, 99); return major * 1'00'00 + minor * 1'00 + bugfix; } } #if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_13_4 // On macOS 10.13.4 and later, the sysctlbyname above should have been // successful. NOTREACHED(); return -1; #else // DT >= 10.13.4 // The Darwin major version is always 4 greater than the macOS minor version // for Darwin versions beginning with 6, corresponding to Mac OS X 10.2, // through Darwin 19, corresponding to macOS 10.15. int darwin_major_version = DarwinMajorVersion(); DCHECK_GE(darwin_major_version, 6); DCHECK_LE(darwin_major_version, 19); int macos_version_number = 10'00'00 + (darwin_major_version - 4) * 1'00; // On macOS 10.13.4 and later, the sysctlbyname above should have been // successful. DCHECK_LT(macos_version_number, 10'13'04); return macos_version_number; #endif // DT >= 10.13.4 }(); return macos_version_number; } bool MacOSVersionComponents(int* major, int* minor, int* bugfix, std::string* build, bool* server, std::string* version_string) { base::ScopedCFTypeRef dictionary( TryCFCopyServerVersionDictionary()); if (dictionary) { *server = true; } else { dictionary.reset(TryCFCopySystemVersionDictionary()); if (!dictionary) { LOG(ERROR) << "_CFCopySystemVersionDictionary failed"; return false; } *server = false; } bool success = true; CFStringRef version_cf = base::mac::CFCast( TryCFDictionaryGetValue(dictionary, _kCFSystemVersionProductVersionKey)); std::string version; if (!version_cf) { LOG(ERROR) << "version_cf not found"; success = false; } else { version = base::SysCFStringRefToUTF8(version_cf); if (!StringToVersionNumbers(version, major, minor, bugfix)) { success = false; } else { DCHECK_GE(*major, 10); DCHECK_LE(*major, 99); DCHECK_GE(*minor, 0); DCHECK_LE(*minor, 99); DCHECK_GE(*bugfix, 0); DCHECK_LE(*bugfix, 99); } } CFStringRef build_cf = base::mac::CFCast( TryCFDictionaryGetValue(dictionary, _kCFSystemVersionBuildVersionKey)); if (!build_cf) { LOG(ERROR) << "build_cf not found"; success = false; } else { build->assign(base::SysCFStringRefToUTF8(build_cf)); } CFStringRef product_cf = base::mac::CFCast( TryCFDictionaryGetValue(dictionary, _kCFSystemVersionProductNameKey)); std::string product; if (!product_cf) { LOG(ERROR) << "product_cf not found"; success = false; } else { product = base::SysCFStringRefToUTF8(product_cf); } // This key is not required, and in fact is normally not present. CFStringRef extra_cf = base::mac::CFCast(TryCFDictionaryGetValue( dictionary, _kCFSystemVersionProductVersionExtraKey)); std::string extra; if (extra_cf) { extra = base::SysCFStringRefToUTF8(extra_cf); } if (!product.empty() || !version.empty() || !build->empty()) { if (!extra.empty()) { version_string->assign(base::StringPrintf("%s %s %s (%s)", product.c_str(), version.c_str(), extra.c_str(), build->c_str())); } else { version_string->assign(base::StringPrintf( "%s %s (%s)", product.c_str(), version.c_str(), build->c_str())); } } return success; } void MacModelAndBoard(std::string* model, std::string* board_id) { base::mac::ScopedIOObject platform_expert( IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))); if (platform_expert) { model->assign( IORegistryEntryDataPropertyAsString(platform_expert, CFSTR("model"))); #if defined(ARCH_CPU_X86_FAMILY) CFStringRef kBoardProperty = CFSTR("board-id"); #elif defined(ARCH_CPU_ARM64) // TODO(https://crashpad.chromium.org/bug/352): When production arm64 // hardware is available, determine whether board-id works and switch to it // if feasible, otherwise, determine whether target-type remains a viable // alternative. CFStringRef kBoardProperty = CFSTR("target-type"); #endif board_id->assign(IORegistryEntryDataPropertyAsString(platform_expert, kBoardProperty)); } else { model->clear(); board_id->clear(); } } } // namespace crashpad