Skip to content

Commit 7db4905

Browse files
committed
entitlement reader using swift
1 parent a98ea60 commit 7db4905

File tree

4 files changed

+189
-4
lines changed

4 files changed

+189
-4
lines changed

Crashlytics/Crashlytics/Components/FIRCLSContext.m

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@
3333
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
3434
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
3535

36+
#if SWIFT_PACKAGE
37+
@import FirebaseCrashlyticsSwift;
38+
#elif __has_include(<FirebaseCrashlytics/FirebaseCrashlytics-Swift.h>)
39+
#import <FirebaseCrashlytics/FirebaseCrashlytics-Swift.h>
40+
#elif __has_include("FirebaseCrashlytics-Swift.h")
41+
// If frameworks are not available, fall back to importing the header as it
42+
// should be findable from a header search path pointing to the build
43+
// directory. See #12611 for more context.
44+
#import "FirebaseCrashlytics-Swift.h"
45+
#endif
46+
3647
// The writable size is our handler stack plus whatever scratch we need. We have to use this space
3748
// extremely carefully, however, because thread stacks always needs to be page-aligned. Only the
3849
// first allocation is guaranteed to be page-aligned.
@@ -182,10 +193,12 @@
182193

183194
#if CLS_MACH_EXCEPTION_SUPPORTED
184195
dispatch_group_async(group, queue, ^{
185-
_firclsContext.readonly->machException.path =
186-
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportMachExceptionFile);
187-
188-
FIRCLSMachExceptionInit(&_firclsContext.readonly->machException);
196+
FIRCLSEntitlementsReader* entitlementReader = [[FIRCLSEntitlementsReader alloc] init];
197+
NSNumber* enableEnhancedSecurity =
198+
[[entitlementReader readEntitlements] platformRestrictions];
199+
if (!enableEnhancedSecurity) {
200+
FIRCLSMachExceptionInit(&_firclsContext.readonly->machException);
201+
}
189202
});
190203
#endif
191204

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
@objc(FIRCLSEntitlements)
18+
public class Entitlements: NSObject {
19+
@objc public var platformRestrictions: NSNumber?
20+
21+
enum Key: String {
22+
case platformRestrictions = "com.apple.security.hardened-process.platform-restrictions"
23+
}
24+
25+
init(rawValue: [String: Any]) {
26+
for key in [Key.platformRestrictions] {
27+
switch key {
28+
case .platformRestrictions:
29+
platformRestrictions = rawValue[key.rawValue] as? NSNumber
30+
}
31+
}
32+
}
33+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
import MachO
17+
18+
@objc(FIRCLSEntitlementsReader)
19+
public class EntitlementsReader: NSObject {
20+
private struct CSMultiBlob {
21+
var magic: UInt32
22+
var lentgh: UInt32
23+
var count: UInt32
24+
}
25+
26+
private struct CSBlob {
27+
var type: UInt32
28+
var offset: UInt32
29+
}
30+
31+
private enum CSMagic {
32+
static let embeddedSignature: UInt32 = 0xFADE_0CC0
33+
static let embededEntitlements: UInt32 = 0xFADE_7171
34+
}
35+
36+
@objc public func readEntitlements() -> Entitlements? {
37+
var executableHeader: UnsafePointer<mach_header>? = nil
38+
39+
for i in 0 ..< _dyld_image_count() {
40+
let imageHeader = _dyld_get_image_header(i)
41+
42+
if let filetype = imageHeader?.pointee.filetype, filetype == MH_EXECUTE {
43+
executableHeader = imageHeader
44+
break
45+
}
46+
}
47+
48+
guard let executableHeader = executableHeader else {
49+
print("Application image not found")
50+
return nil
51+
}
52+
53+
let is64bit = executableHeader.pointee.magic == MH_MAGIC_64 || executableHeader.pointee
54+
.magic == MH_CIGAM_64
55+
56+
var curLoadCommandIterator = Int(bitPattern: executableHeader) +
57+
(is64bit ? MemoryLayout<mach_header_64>.size : MemoryLayout<mach_header>.size)
58+
59+
for _ in 0 ..< executableHeader.pointee.ncmds {
60+
guard let segmentCommand = UnsafePointer<segment_command>(
61+
bitPattern: curLoadCommandIterator
62+
)?.pointee else {
63+
return nil
64+
}
65+
66+
if segmentCommand.cmd == LC_CODE_SIGNATURE {
67+
break
68+
}
69+
curLoadCommandIterator = curLoadCommandIterator + Int(segmentCommand.cmdsize)
70+
}
71+
72+
guard let dataCommand =
73+
UnsafePointer<linkedit_data_command>(bitPattern: curLoadCommandIterator)?.pointee else {
74+
return nil
75+
}
76+
77+
let dataStart = Int(bitPattern: executableHeader) + Int(dataCommand.dataoff)
78+
79+
var signatureCursor: Int = dataStart
80+
81+
// This segement is not always has signature start
82+
// When I test on debug build the this segment also contains symbol table
83+
// need to iterate addresses and find the magic first
84+
while signatureCursor < dataStart + Int(dataCommand.datasize) {
85+
guard let multiBlob = UnsafePointer<CSMultiBlob>(bitPattern: signatureCursor)?.pointee else {
86+
return nil
87+
}
88+
print(String(format: "Finding Magic Number: 0x%08X", CFSwapInt32(multiBlob.magic)))
89+
if CFSwapInt32(multiBlob.magic) == CSMagic.embeddedSignature {
90+
return readEntitlementsFromSignature(startingAt: signatureCursor, multiBlob: multiBlob)
91+
}
92+
signatureCursor = signatureCursor + 4
93+
}
94+
95+
print("Cannot find the magic number")
96+
return nil
97+
}
98+
99+
private func readEntitlementsFromSignature(startingAt offset: Int,
100+
multiBlob: CSMultiBlob) -> Entitlements? {
101+
let multiBlobSize = MemoryLayout<CSMultiBlob>.size
102+
let blobSize = MemoryLayout<CSBlob>.size
103+
let itemCount = Int(CFSwapInt32(multiBlob.count))
104+
105+
for index in 0 ..< itemCount {
106+
let blobOffset = offset + multiBlobSize + index * blobSize
107+
let blob = UnsafePointer<CSBlob>(bitPattern: blobOffset)?.pointee
108+
// the first 4 bytes are the magic
109+
// the next 4 are the length
110+
// after that is the encoded plist
111+
if
112+
let blob = UnsafePointer<CSBlob>(bitPattern: blobOffset)?.pointee,
113+
let magic = UnsafePointer<UInt32>(bitPattern: offset + Int(CFSwapInt32(blob.offset)))?
114+
.pointee, CFSwapInt32(magic) == CSMagic.embededEntitlements {
115+
if let signatureLength =
116+
UnsafePointer<UInt32>(bitPattern: offset + Int(CFSwapInt32(blob.offset)) + 4)?.pointee,
117+
let entitlementDataPointer =
118+
UnsafeRawPointer(bitPattern: offset + Int(CFSwapInt32(blob.offset)) + 8) {
119+
let data = Data(
120+
bytes: entitlementDataPointer,
121+
count: Int(CFSwapInt32(signatureLength)) - 8
122+
)
123+
124+
guard let rawValues = try? PropertyListSerialization.propertyList(
125+
from: data,
126+
options: [],
127+
format: nil
128+
) as? [String: Any] else {
129+
return nil
130+
}
131+
return Entitlements(rawValue: rawValues)
132+
}
133+
}
134+
}
135+
return nil
136+
}
137+
}

Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ let package = Package(
581581
"CrashlyticsInputFiles.xcfilelist",
582582
"third_party/libunwind/LICENSE",
583583
"Crashlytics/Rollouts/",
584+
"Crashlytics/Entitlements/",
584585
],
585586
sources: [
586587
"Crashlytics/",
@@ -616,6 +617,7 @@ let package = Package(
616617
path: "Crashlytics",
617618
sources: [
618619
"Crashlytics/Rollouts/",
620+
"Crashlytics/Entitlements/",
619621
]
620622
),
621623
.testTarget(

0 commit comments

Comments
 (0)