fix(crashlytics): read SDK versions from LC_BUILD_VERSION (#16830)

This commit is contained in:
Paul Beusterien authored and GitHub committed 2026-10-30 12:04:06 -04:00
1 parent 675f23626f
commit bd7784b838
8 files changed
+140 -1

No files matched your search

+2
View File
@@ -6,6 +6,8 @@
- [fixed] Improved robustness in Firebase Sessions when parsing malformed
settings from the server, including `null` values that could cause a crash
when caching settings. (#16728)
- [fixed] Fixed the app executable's minimum OS and SDK versions being recorded as `0.0.0` in
report metadata for apps targeting iOS 12, macOS 10.14, tvOS 12, watchOS 5, or later. (#16810)
# 13.0.0
- [fixed] Safely validate memory reads when executing `DW_OP_deref_size`
@@ -311,6 +311,7 @@ static void FIRCLSPopulateImageDetailWithLoadCommand(uint32_t type,
case LC_VERSION_MIN_IPHONEOS:
case LC_VERSION_MIN_TVOS:
case LC_VERSION_MIN_WATCHOS:
case LC_BUILD_VERSION:
details->minSDK = FIRCLSMachOGetMinimumOSVersion(cmd);
details->builtSDK = FIRCLSMachOGetLinkedSDKVersion(cmd);
break;
@@ -104,7 +104,8 @@
uuid = FIRCLSMachOGetUUID(cmd);
[executables setObject:FIRCLSUUIDToNSString(uuid) forKey:arch];
} else if (type == LC_VERSION_MIN_MACOSX || type == LC_VERSION_MIN_IPHONEOS) {
} else if (type == LC_VERSION_MIN_MACOSX || type == LC_VERSION_MIN_IPHONEOS ||
type == LC_BUILD_VERSION) {
self->_minimumSDK = FIRCLSMachOGetMinimumOSVersion(cmd);
self->_builtSDK = FIRCLSMachOGetLinkedSDKVersion(cmd);
}
@@ -514,10 +514,18 @@ static FIRCLSMachOVersion FIRCLSMachOVersionFromEncoded(uint32_t encoded) {
}
FIRCLSMachOVersion FIRCLSMachOGetMinimumOSVersion(const struct load_command* cmd) {
if (cmd->cmd == LC_BUILD_VERSION) {
return FIRCLSMachOVersionFromEncoded(((const struct build_version_command*)cmd)->minos);
}
return FIRCLSMachOVersionFromEncoded(((const struct version_min_command*)cmd)->version);
}
FIRCLSMachOVersion FIRCLSMachOGetLinkedSDKVersion(const struct load_command* cmd) {
if (cmd->cmd == LC_BUILD_VERSION) {
return FIRCLSMachOVersionFromEncoded(((const struct build_version_command*)cmd)->sdk);
}
return FIRCLSMachOVersionFromEncoded(((const struct version_min_command*)cmd)->sdk);
}
@@ -66,6 +66,7 @@
case LC_VERSION_MIN_MACOSX:
case LC_VERSION_MIN_WATCHOS:
case LC_VERSION_MIN_TVOS:
case LC_BUILD_VERSION:
self->_minimumOSVersion = FIRCLSMachOGetMinimumOSVersion(cmd);
self->_linkedSDKVersion = FIRCLSMachOGetLinkedSDKVersion(cmd);
break;
@@ -0,0 +1,35 @@
// Copyright 2026 Google LLC
//
// 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.
#import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"
#import <XCTest/XCTest.h>
@interface FIRCLSApplicationIdentifierModelTests : XCTestCase
@end
@implementation FIRCLSApplicationIdentifierModelTests
- (void)testReadsMinimumAndBuiltSDKVersionsOfExecutable {
// The test host is built with a current toolchain and deployment target, so its versions are
// described by LC_BUILD_VERSION rather than LC_VERSION_MIN_*.
FIRCLSApplicationIdentifierModel *model = [[FIRCLSApplicationIdentifierModel alloc] init];
XCTAssertNotNil(model);
XCTAssertNotEqualObjects(model.minimumSDKString, @"0.0.0");
XCTAssertNotEqualObjects(model.builtSDKString, @"0.0.0");
}
@end
@@ -0,0 +1,61 @@
// Copyright 2026 Google LLC
//
// 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 "Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.h"
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
@interface FIRCLSBinaryImageTests : XCTestCase
@end
@implementation FIRCLSBinaryImageTests
- (void)testRecordMainExecutableIncludesMinimumAndBuiltSDKVersions {
// The test host is built with a current toolchain and deployment target, so its versions are
// described by LC_BUILD_VERSION rather than LC_VERSION_MIN_*.
NSString *path =
[NSTemporaryDirectory() stringByAppendingPathComponent:@"executable_test.clsrecord"];
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, [path fileSystemRepresentation], false)) {
XCTFail(@"Failed to open %@", path);
return;
}
XCTAssertTrue(FIRCLSBinaryImageRecordMainExecutable(&file));
FIRCLSFileClose(&file);
NSData *data = [NSData dataWithContentsOfFile:path];
XCTAssertNotNil(data);
if (!data) {
return;
}
NSDictionary *record = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSDictionary *executable = record[@"executable"];
XCTAssertNotNil(executable);
XCTAssertNotNil(executable[@"minimum_sdk_version"]);
XCTAssertNotNil(executable[@"built_sdk_version"]);
XCTAssertNotEqualObjects(executable[@"minimum_sdk_version"], @"0.0.0");
XCTAssertNotEqualObjects(executable[@"built_sdk_version"], @"0.0.0");
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
}
@end
@@ -277,6 +277,36 @@
XCTAssertEqual((uint32_t)0, version.bugfix, @"");
}
- (void)testReadVersionsFromBuildVersionLoadCommand {
// Binaries built for iOS 12, macOS 10.14, tvOS 12, watchOS 5 or later carry LC_BUILD_VERSION
// instead of LC_VERSION_MIN_*.
struct build_version_command command = {0};
command.cmd = LC_BUILD_VERSION;
command.cmdsize = sizeof(command);
command.platform = PLATFORM_IOS;
command.minos = 0x000F0200; // 15.2.0
command.sdk = 0x001A0501; // 26.5.1
FIRCLSMachOVersion version = FIRCLSMachOGetMinimumOSVersion((const struct load_command*)&command);
XCTAssertEqual((uint32_t)15, version.major);
XCTAssertEqual((uint32_t)2, version.minor);
XCTAssertEqual((uint32_t)0, version.bugfix);
version = FIRCLSMachOGetLinkedSDKVersion((const struct load_command*)&command);
XCTAssertEqual((uint32_t)26, version.major);
XCTAssertEqual((uint32_t)5, version.minor);
XCTAssertEqual((uint32_t)1, version.bugfix);
}
- (void)testRunningSliceReadsMinimumOSAndSDKVersions {
// The test host is built with a current toolchain and deployment target, so its versions are
// described by LC_BUILD_VERSION.
FIRCLSMachOSlice* slice = [FIRCLSMachOSlice runningSlice];
XCTAssertGreaterThan(slice.minimumOSVersion.major, (uint32_t)0);
XCTAssertGreaterThan(slice.linkedSDKVersion.major, (uint32_t)0);
}
- (void)testReadx86_64Section {
NSString* path = [[self resourcePath] stringByAppendingPathComponent:@"x86_64-executable"];
struct FIRCLSMachOFile file;