1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 * Copyright (c) 2014 Apple Inc. All rights reserved.
5 * @APPLE_LICENSE_HEADER_START@
7 * This file contains Original Code and/or Modifications of Original Code
8 * as defined in and that are subject to the Apple Public Source License
9 * Version 2.0 (the 'License'). You may not use this file except in
10 * compliance with the License. Please obtain a copy of the License at
11 * http://www.opensource.apple.com/apsl/ and read it before using this
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19 * Please see the License for the specific language governing rights and
20 * limitations under the License.
22 * @APPLE_LICENSE_HEADER_END@
25 #include "AppCacheBuilder.h"
27 #include <mach/mach_time.h>
29 #include <CoreFoundation/CFArray.h>
30 #include <CoreFoundation/CFError.h>
31 #include <CoreFoundation/CFNumber.h>
32 #include <CoreFoundation/CFPropertyList.h>
33 #include <CoreFoundation/CFString.h>
35 #include <CommonCrypto/CommonHMAC.h>
36 #include <CommonCrypto/CommonDigest.h>
37 #include <CommonCrypto/CommonDigestSPI.h>
39 AppCacheBuilder::AppCacheBuilder(const DyldSharedCache::CreateOptions
& options
,
40 const Options
& appCacheOptions
,
41 const dyld3::closure::FileSystem
& fileSystem
)
42 : CacheBuilder(options
, fileSystem
), appCacheOptions(appCacheOptions
)
44 // FIXME: 32-bit support
48 AppCacheBuilder::~AppCacheBuilder() {
49 if (prelinkInfoDict
) {
50 CFRelease(prelinkInfoDict
);
52 if (_fullAllocatedBuffer
) {
53 vm_deallocate(mach_task_self(), _fullAllocatedBuffer
, _allocatedBufferSize
);
58 void AppCacheBuilder::makeSortedDylibs(const std::vector
<InputDylib
>& dylibs
)
60 for (const InputDylib
& file
: dylibs
) {
61 if ( file
.dylib
.loadedFileInfo
.fileContent
== nullptr ) {
62 codelessKexts
.push_back(file
);
64 AppCacheDylibInfo
& dylibInfo
= sortedDylibs
.emplace_back();
65 dylibInfo
.input
= &file
.dylib
;
66 dylibInfo
.dylibID
= file
.dylibID
;
67 dylibInfo
.dependencies
= file
.dylibDeps
;
68 dylibInfo
.infoPlist
= file
.infoPlist
;
69 dylibInfo
.errors
= file
.errors
;
70 dylibInfo
.bundlePath
= file
.bundlePath
;
71 dylibInfo
.stripMode
= file
.stripMode
;
75 std::sort(sortedDylibs
.begin(), sortedDylibs
.end(), [&](const DylibInfo
& a
, const DylibInfo
& b
) {
76 // Sort the kernel first, then kext's
77 bool isStaticExecutableA
= a
.input
->mappedFile
.mh
->isStaticExecutable();
78 bool isStaticExecutableB
= b
.input
->mappedFile
.mh
->isStaticExecutable();
79 if (isStaticExecutableA
!= isStaticExecutableB
)
80 return isStaticExecutableA
;
82 // Sort split seg next
83 bool splitSegA
= a
.input
->mappedFile
.mh
->hasSplitSeg();
84 bool splitSegB
= b
.input
->mappedFile
.mh
->hasSplitSeg();
85 if (splitSegA
!= splitSegB
)
88 // Finally sort by path
89 return a
.input
->mappedFile
.runtimePath
< b
.input
->mappedFile
.runtimePath
;
92 // Sort codeless kext's by ID
93 std::sort(codelessKexts
.begin(), codelessKexts
.end(), [&](const InputDylib
& a
, const InputDylib
& b
) {
94 return a
.dylibID
< b
.dylibID
;
99 void AppCacheBuilder::forEachCacheDylib(void (^callback
)(const dyld3::MachOAnalyzer
* ma
,
100 const std::string
& dylibID
,
101 DylibStripMode stripMode
,
102 const std::vector
<std::string
>& dependencies
,
103 Diagnostics
& dylibDiag
,
106 for (const AppCacheDylibInfo
& dylib
: sortedDylibs
) {
107 for (const SegmentMappingInfo
& loc
: dylib
.cacheLocation
) {
108 if (!strcmp(loc
.segName
, "__TEXT")) {
109 // Assume __TEXT contains the mach header
110 callback((const dyld3::MachOAnalyzer
*)loc
.dstSegment
, dylib
.dylibID
, dylib
.stripMode
,
111 dylib
.dependencies
, *dylib
.errors
, stop
);
120 void AppCacheBuilder::forEachDylibInfo(void (^callback
)(const DylibInfo
& dylib
, Diagnostics
& dylibDiag
)) {
121 for (const AppCacheDylibInfo
& dylibInfo
: sortedDylibs
)
122 callback(dylibInfo
, *dylibInfo
.errors
);
125 const CacheBuilder::DylibInfo
* AppCacheBuilder::getKernelStaticExecutableInputFile() const {
126 for (const auto& dylib
: sortedDylibs
) {
127 const dyld3::MachOAnalyzer
* ma
= dylib
.input
->mappedFile
.mh
;
128 if ( ma
->isStaticExecutable() )
134 const dyld3::MachOAnalyzer
* AppCacheBuilder::getKernelStaticExecutableFromCache() const {
135 // FIXME: Support reading this from a prebuilt KC
136 assert(appCacheOptions
.cacheKind
== Options::AppCacheKind::kernel
);
138 __block
const dyld3::MachOAnalyzer
* kernelMA
= nullptr;
139 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
140 DylibStripMode stripMode
, const std::vector
<std::string
>& dependencies
,
141 Diagnostics
& dylibDiag
,
143 if ( ma
->isStaticExecutable() ) {
149 assert(kernelMA
!= nullptr);
153 void AppCacheBuilder::forEachRegion(void (^callback
)(const Region
& region
)) const {
155 callback(cacheHeaderRegion
);
157 // readOnlyTextRegion
158 callback(readOnlyTextRegion
);
161 if ( readExecuteRegion
.sizeInUse
!= 0 )
162 callback(readExecuteRegion
);
165 if ( branchStubsRegion
.bufferSize
!= 0 )
166 callback(branchStubsRegion
);
169 if ( dataConstRegion
.sizeInUse
!= 0 )
170 callback(dataConstRegion
);
173 if ( branchGOTsRegion
.bufferSize
!= 0 )
174 callback(branchGOTsRegion
);
177 if ( readWriteRegion
.sizeInUse
!= 0 )
178 callback(readWriteRegion
);
181 if ( hibernateRegion
.sizeInUse
!= 0 )
182 callback(hibernateRegion
);
185 for (const Region
& region
: customDataRegions
)
189 if ( prelinkInfoDict
!= nullptr )
190 callback(prelinkInfoRegion
);
192 // nonSplitSegRegions
193 for (const Region
& region
: nonSplitSegRegions
)
197 callback(_readOnlyRegion
);
200 // We don't count this as its not a real region
203 uint64_t AppCacheBuilder::numRegions() const {
204 __block
uint64_t count
= 0;
206 forEachRegion(^(const Region
®ion
) {
213 uint64_t AppCacheBuilder::fixupsPageSize() const {
215 use4K
|= (_options
.archs
== &dyld3::GradedArchs::x86_64
);
216 use4K
|= (_options
.archs
== &dyld3::GradedArchs::x86_64h
);
217 return use4K
? 4096 : 16384;
220 uint64_t AppCacheBuilder::numWritablePagesToFixup(uint64_t numBytesToFixup
) const {
221 uint64_t pageSize
= fixupsPageSize();
222 assert((numBytesToFixup
% pageSize
) == 0);
223 uint64_t numPagesToFixup
= numBytesToFixup
/ pageSize
;
224 return numPagesToFixup
;
227 // Returns true if each kext inside the KC needs to be reloadable, ie, have its
228 // pages reset and its start method rerun. This means we can't pack pages and need
229 // fixups on each kext individually
230 bool AppCacheBuilder::fixupsArePerKext() const {
231 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::pageableKC
)
233 bool isX86
= (_options
.archs
== &dyld3::GradedArchs::x86_64
) || (_options
.archs
== &dyld3::GradedArchs::x86_64h
);
234 return isX86
&& (appCacheOptions
.cacheKind
== Options::AppCacheKind::auxKC
);
237 // x86_64 kext's don't contain stubs for branches so we need to generate some
238 // if branches cross from one KC to another, eg, from the auxKC to the base KC
239 uint64_t AppCacheBuilder::numBranchRelocationTargets() {
240 bool mayHaveBranchRelocations
= false;
241 mayHaveBranchRelocations
|= (_options
.archs
== &dyld3::GradedArchs::x86_64
);
242 mayHaveBranchRelocations
|= (_options
.archs
== &dyld3::GradedArchs::x86_64h
);
243 if ( !mayHaveBranchRelocations
)
246 switch (appCacheOptions
.cacheKind
) {
247 case Options::AppCacheKind::none
:
248 case Options::AppCacheKind::kernel
:
249 // Nothing to do here as we can't bind from a lower level up to a higher one
251 case Options::AppCacheKind::pageableKC
:
252 case Options::AppCacheKind::kernelCollectionLevel2
:
253 case Options::AppCacheKind::auxKC
:
254 // Any calls in these might be to a lower level so add space for each call target
258 uint64_t totalTargets
= 0;
259 for (const DylibInfo
& dylib
: sortedDylibs
) {
260 // We need the symbol name and libOrdinal just in case we bind to the same symbol name in 2 different KCs
261 typedef std::pair
<std::string_view
, int> Symbol
;
264 size_t operator() (const Symbol
& symbol
) const
266 return std::hash
<std::string_view
>{}(symbol
.first
) ^ std::hash
<int>{}(symbol
.second
);
269 __block
std::unordered_set
<Symbol
, SymbolHash
> seenSymbols
;
270 dylib
.input
->mappedFile
.mh
->forEachBind(_diagnostics
,
271 ^(uint64_t runtimeOffset
, int libOrdinal
, uint8_t type
,
272 const char *symbolName
, bool weakImport
,
273 bool lazyBind
, uint64_t addend
, bool &stop
) {
274 if ( type
!= BIND_TYPE_TEXT_PCREL32
)
276 seenSymbols
.insert({ symbolName
, libOrdinal
});
277 }, ^(const char *symbolName
) {
279 totalTargets
+= seenSymbols
.size();
284 void AppCacheBuilder::assignSegmentRegionsAndOffsets()
286 // Segments can be re-ordered in memory relative to the order of the LC_SEGMENT load comamnds
287 // so first make space for all the cache location objects so that we get the order the same
288 // as the LC_SEGMENTs
289 for (DylibInfo
& dylib
: sortedDylibs
) {
290 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
291 dylib
.cacheLocation
.push_back({});
295 // If we are building the kernel collection, then inherit the base address of the statically linked kernel
296 const dyld3::MachOAnalyzer
* kernelMA
= nullptr;
297 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::kernel
) {
298 for (DylibInfo
& dylib
: sortedDylibs
) {
299 if ( dylib
.input
->mappedFile
.mh
->isStaticExecutable() ) {
300 kernelMA
= dylib
.input
->mappedFile
.mh
;
304 if ( kernelMA
== nullptr ) {
305 _diagnostics
.error("Could not find kernel image");
308 cacheBaseAddress
= kernelMA
->preferredLoadAddress();
311 // x86_64 doesn't have stubs for kext branches. So work out how many potential targets
312 // we need to emit stubs for.
313 uint64_t branchTargetsFromKexts
= numBranchRelocationTargets();
315 uint32_t minimumSegmentAlignmentP2
= 14;
316 if ( (_options
.archs
== &dyld3::GradedArchs::x86_64
) || (_options
.archs
== &dyld3::GradedArchs::x86_64h
) ) {
317 minimumSegmentAlignmentP2
= 12;
320 auto getMinAlignment
= ^(const dyld3::MachOAnalyzer
* ma
) {
321 // The kernel wants to be able to unmap its own segments so always align it.
322 // And align the pageable KC as each kext can be mapped individually
323 if ( ma
== kernelMA
)
324 return minimumSegmentAlignmentP2
;
325 if ( fixupsArePerKext() )
326 return minimumSegmentAlignmentP2
;
327 if ( _options
.archs
== &dyld3::GradedArchs::arm64e
)
328 return minimumSegmentAlignmentP2
;
333 // __TEXT segments with r/o permissions
334 __block
uint64_t offsetInRegion
= 0;
335 for (DylibInfo
& dylib
: sortedDylibs
) {
336 bool canBePacked
= dylib
.input
->mappedFile
.mh
->hasSplitSeg();
340 __block
uint64_t textSegVmAddr
= 0;
341 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
342 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
343 textSegVmAddr
= segInfo
.vmAddr
;
344 if ( segInfo
.protections
!= (VM_PROT_READ
) )
346 if ( (strcmp(segInfo
.segName
, "__DATA_CONST") == 0)
347 || (strcmp(segInfo
.segName
, "__PPLDATA_CONST") == 0)
348 || (strcmp(segInfo
.segName
, "__LASTDATA_CONST") == 0) )
350 if ( strcmp(segInfo
.segName
, "__LINKEDIT") == 0 )
352 if ( strcmp(segInfo
.segName
, "__LINKINFO") == 0 )
355 uint32_t minAlignmentP2
= getMinAlignment(dylib
.input
->mappedFile
.mh
);
356 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
357 uint64_t dstCacheSegmentSize
= align(segInfo
.sizeOfSections
, minAlignmentP2
);
359 // __CTF is not mapped in to the kernel, so remove it from the final binary.
360 if ( strcmp(segInfo
.segName
, "__CTF") == 0 ) {
362 dstCacheSegmentSize
= 0;
365 // kxld packs __TEXT so we will do
366 // Note we align to at least 16-bytes as LDR's can scale up to 16 from their address
367 // and aligning them less than 16 would break that
368 offsetInRegion
= align(offsetInRegion
, std::max(segInfo
.p2align
, 4U));
369 offsetInRegion
= align(offsetInRegion
, minAlignmentP2
);
370 SegmentMappingInfo loc
;
371 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
372 loc
.segName
= segInfo
.segName
;
373 loc
.dstSegment
= nullptr;
374 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
375 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
376 loc
.dstCacheSegmentSize
= (uint32_t)dstCacheSegmentSize
;
377 loc
.dstCacheFileSize
= (uint32_t)copySize
;
378 loc
.copySegmentSize
= (uint32_t)copySize
;
379 loc
.srcSegmentIndex
= segInfo
.segIndex
;
380 loc
.parentRegion
= &readOnlyTextRegion
;
381 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
382 offsetInRegion
+= dstCacheSegmentSize
;
386 // kclist needs this segment, even if its empty, so leave it in there
387 readOnlyTextRegion
.bufferSize
= align(offsetInRegion
, 14);
388 readOnlyTextRegion
.sizeInUse
= readOnlyTextRegion
.bufferSize
;
389 readOnlyTextRegion
.permissions
= VM_PROT_READ
;
390 readOnlyTextRegion
.name
= "__PRELINK_TEXT";
393 // __TEXT segments with r/x permissions
395 // __TEXT segments with r/x permissions
396 __block
uint64_t offsetInRegion
= 0;
397 for (DylibInfo
& dylib
: sortedDylibs
) {
398 bool canBePacked
= dylib
.input
->mappedFile
.mh
->hasSplitSeg();
402 __block
uint64_t textSegVmAddr
= 0;
403 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
404 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
405 textSegVmAddr
= segInfo
.vmAddr
;
406 if ( strcmp(segInfo
.segName
, "__HIB") == 0 )
408 if ( segInfo
.protections
!= (VM_PROT_READ
| VM_PROT_EXECUTE
) )
410 // kxld packs __TEXT_EXEC so we will do
411 // Note we align to at least 16-bytes as LDR's can scale up to 16 from their address
412 // and aligning them less than 16 would break that
413 uint32_t minAlignmentP2
= getMinAlignment(dylib
.input
->mappedFile
.mh
);
414 offsetInRegion
= align(offsetInRegion
, std::max(segInfo
.p2align
, 4U));
415 offsetInRegion
= align(offsetInRegion
, minAlignmentP2
);
416 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
417 uint64_t dstCacheSegmentSize
= align(segInfo
.sizeOfSections
, minAlignmentP2
);
418 SegmentMappingInfo loc
;
419 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
420 loc
.segName
= segInfo
.segName
;
421 loc
.dstSegment
= nullptr;
422 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
423 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
424 loc
.dstCacheSegmentSize
= (uint32_t)dstCacheSegmentSize
;
425 loc
.dstCacheFileSize
= (uint32_t)copySize
;
426 loc
.copySegmentSize
= (uint32_t)copySize
;
427 loc
.srcSegmentIndex
= segInfo
.segIndex
;
428 loc
.parentRegion
= &readExecuteRegion
;
429 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
430 offsetInRegion
+= loc
.dstCacheSegmentSize
;
434 // align r/x region end
435 readExecuteRegion
.bufferSize
= align(offsetInRegion
, 14);
436 readExecuteRegion
.sizeInUse
= readExecuteRegion
.bufferSize
;
437 readExecuteRegion
.permissions
= VM_PROT_READ
| VM_PROT_EXECUTE
;
438 readExecuteRegion
.name
= "__TEXT_EXEC";
441 if ( branchTargetsFromKexts
!= 0 ) {
443 branchStubsRegion
.bufferSize
= align(branchTargetsFromKexts
* 6, 14);
444 branchStubsRegion
.sizeInUse
= branchStubsRegion
.bufferSize
;
445 branchStubsRegion
.permissions
= VM_PROT_READ
| VM_PROT_EXECUTE
;
446 branchStubsRegion
.name
= "__BRANCH_STUBS";
449 // __DATA_CONST segments
451 __block
uint64_t offsetInRegion
= 0;
452 for (DylibInfo
& dylib
: sortedDylibs
) {
453 if (!dylib
.input
->mappedFile
.mh
->hasSplitSeg())
456 __block
uint64_t textSegVmAddr
= 0;
457 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
458 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
459 textSegVmAddr
= segInfo
.vmAddr
;
460 if ( (segInfo
.protections
& VM_PROT_EXECUTE
) != 0 )
462 if ( (strcmp(segInfo
.segName
, "__DATA_CONST") != 0)
463 && (strcmp(segInfo
.segName
, "__PPLDATA_CONST") != 0)
464 && (strcmp(segInfo
.segName
, "__LASTDATA_CONST") != 0) )
466 // kxld packs __DATA_CONST so we will do
467 uint32_t minAlignmentP2
= getMinAlignment(dylib
.input
->mappedFile
.mh
);
468 offsetInRegion
= align(offsetInRegion
, segInfo
.p2align
);
469 offsetInRegion
= align(offsetInRegion
, minAlignmentP2
);
470 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
471 uint64_t dstCacheSegmentSize
= align(segInfo
.sizeOfSections
, minAlignmentP2
);
472 SegmentMappingInfo loc
;
473 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
474 loc
.segName
= segInfo
.segName
;
475 loc
.dstSegment
= nullptr;
476 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
477 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
478 loc
.dstCacheSegmentSize
= (uint32_t)dstCacheSegmentSize
;
479 loc
.dstCacheFileSize
= (uint32_t)copySize
;
480 loc
.copySegmentSize
= (uint32_t)copySize
;
481 loc
.srcSegmentIndex
= segInfo
.segIndex
;
482 loc
.parentRegion
= &dataConstRegion
;
483 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
484 offsetInRegion
+= loc
.dstCacheSegmentSize
;
488 // align r/o region end
489 dataConstRegion
.bufferSize
= align(offsetInRegion
, 14);
490 dataConstRegion
.sizeInUse
= dataConstRegion
.bufferSize
;
491 dataConstRegion
.permissions
= VM_PROT_READ
;
492 dataConstRegion
.name
= "__DATA_CONST";
496 if ( branchTargetsFromKexts
!= 0 ) {
498 branchGOTsRegion
.bufferSize
= align(branchTargetsFromKexts
* 8, 14);
499 branchGOTsRegion
.sizeInUse
= branchGOTsRegion
.bufferSize
;
500 branchGOTsRegion
.permissions
= VM_PROT_READ
| VM_PROT_WRITE
;
501 branchGOTsRegion
.name
= "__BRANCH_GOTS";
506 __block
uint64_t offsetInRegion
= 0;
507 for (DylibInfo
& dylib
: sortedDylibs
) {
508 if (!dylib
.input
->mappedFile
.mh
->hasSplitSeg())
511 __block
uint64_t textSegVmAddr
= 0;
512 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
513 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
514 textSegVmAddr
= segInfo
.vmAddr
;
515 if ( strcmp(segInfo
.segName
, "__HIB") == 0 )
517 if ( (strcmp(segInfo
.segName
, "__DATA_CONST") == 0)
518 || (strcmp(segInfo
.segName
, "__PPLDATA_CONST") == 0)
519 || (strcmp(segInfo
.segName
, "__LASTDATA_CONST") == 0) )
521 if ( segInfo
.protections
!= (VM_PROT_READ
| VM_PROT_WRITE
) )
523 // kxld packs __DATA so we will do
524 uint32_t minAlignmentP2
= getMinAlignment(dylib
.input
->mappedFile
.mh
);
525 offsetInRegion
= align(offsetInRegion
, segInfo
.p2align
);
526 offsetInRegion
= align(offsetInRegion
, minAlignmentP2
);
527 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
528 uint64_t dstCacheSegmentSize
= align(segInfo
.sizeOfSections
, minAlignmentP2
);
529 SegmentMappingInfo loc
;
530 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
531 loc
.segName
= segInfo
.segName
;
532 loc
.dstSegment
= nullptr;
533 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
534 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
535 loc
.dstCacheSegmentSize
= (uint32_t)dstCacheSegmentSize
;
536 loc
.dstCacheFileSize
= (uint32_t)copySize
;
537 loc
.copySegmentSize
= (uint32_t)copySize
;
538 loc
.srcSegmentIndex
= segInfo
.segIndex
;
539 loc
.parentRegion
= &readWriteRegion
;
540 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
541 offsetInRegion
+= loc
.dstCacheSegmentSize
;
545 // align r/w region end
546 readWriteRegion
.bufferSize
= align(offsetInRegion
, 14);
547 readWriteRegion
.sizeInUse
= readWriteRegion
.bufferSize
;
548 readWriteRegion
.permissions
= VM_PROT_READ
| VM_PROT_WRITE
;
549 readWriteRegion
.name
= "__DATA";
554 __block
uint64_t offsetInRegion
= 0;
555 for (DylibInfo
& dylib
: sortedDylibs
) {
556 if ( !dylib
.input
->mappedFile
.mh
->isStaticExecutable() )
559 __block
uint64_t textSegVmAddr
= 0;
560 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
561 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
562 textSegVmAddr
= segInfo
.vmAddr
;
563 if ( strcmp(segInfo
.segName
, "__HIB") != 0 )
565 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
566 SegmentMappingInfo loc
;
567 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
568 loc
.segName
= segInfo
.segName
;
569 loc
.dstSegment
= nullptr;
570 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
571 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
572 loc
.dstCacheSegmentSize
= (uint32_t)segInfo
.vmSize
;
573 loc
.dstCacheFileSize
= (uint32_t)copySize
;
574 loc
.copySegmentSize
= (uint32_t)copySize
;
575 loc
.srcSegmentIndex
= segInfo
.segIndex
;
576 loc
.parentRegion
= &hibernateRegion
;
577 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
578 offsetInRegion
+= loc
.dstCacheSegmentSize
;
580 hibernateAddress
= segInfo
.vmAddr
;
583 // Only xnu has __HIB, so no need to continue once we've found it.
587 hibernateRegion
.bufferSize
= align(offsetInRegion
, 14);
588 hibernateRegion
.sizeInUse
= hibernateRegion
.bufferSize
;
589 hibernateRegion
.permissions
= VM_PROT_READ
| VM_PROT_WRITE
| VM_PROT_EXECUTE
;
590 hibernateRegion
.name
= "__HIB";
593 // __TEXT and __DATA from non-split seg dylibs, if we have any
595 for (DylibInfo
& dylib
: sortedDylibs
) {
596 bool canBePacked
= dylib
.input
->mappedFile
.mh
->hasSplitSeg();
600 __block
uint64_t textSegVmAddr
= 0;
601 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
602 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
603 textSegVmAddr
= segInfo
.vmAddr
;
604 if ( strcmp(segInfo
.segName
, "__LINKEDIT") == 0 )
607 nonSplitSegRegions
.emplace_back();
608 nonSplitSegRegions
.back().permissions
= segInfo
.protections
;
609 nonSplitSegRegions
.back().name
= "__REGION" + std::to_string(nonSplitSegRegions
.size() - 1);
611 // Note we don't align the region offset as we have no split seg
612 uint64_t offsetInRegion
= 0;
613 SegmentMappingInfo loc
;
614 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
615 loc
.segName
= segInfo
.segName
;
616 loc
.dstSegment
= nullptr;
617 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
618 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
619 loc
.dstCacheSegmentSize
= (uint32_t)segInfo
.vmSize
;
620 loc
.dstCacheFileSize
= (uint32_t)segInfo
.fileSize
;
621 loc
.copySegmentSize
= (uint32_t)segInfo
.fileSize
;
622 loc
.srcSegmentIndex
= segInfo
.segIndex
;
623 loc
.parentRegion
= &nonSplitSegRegions
.back();
624 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
625 offsetInRegion
+= loc
.dstCacheSegmentSize
;
627 // record non-split seg region end
628 nonSplitSegRegions
.back().bufferSize
= offsetInRegion
;
629 nonSplitSegRegions
.back().sizeInUse
= nonSplitSegRegions
.back().bufferSize
;
635 if ( !customSegments
.empty() ) {
636 for (CustomSegment
& segment
: customSegments
) {
637 uint64_t offsetInRegion
= 0;
638 for (CustomSegment::CustomSection
& section
: segment
.sections
) {
639 section
.offsetInRegion
= offsetInRegion
;
640 offsetInRegion
+= section
.data
.size();
643 Region
& customRegion
= customDataRegions
.emplace_back();
644 segment
.parentRegion
= &customRegion
;
647 customRegion
.bufferSize
= align(offsetInRegion
, 14);
648 customRegion
.sizeInUse
= customRegion
.bufferSize
;
649 customRegion
.permissions
= VM_PROT_READ
;
650 customRegion
.name
= segment
.segmentName
;
656 // This is populated with regular kexts and codeless kexts
658 CFDictionaryRef infoPlist
= nullptr;
659 const dyld3::MachOAnalyzer
* ma
= nullptr;
660 std::string_view bundlePath
;
661 std::string_view executablePath
;
663 std::vector
<PrelinkInfo
> infos
;
664 for (AppCacheDylibInfo
& dylib
: sortedDylibs
) {
665 if (dylib
.infoPlist
== nullptr)
667 infos
.push_back({ dylib
.infoPlist
, dylib
.input
->mappedFile
.mh
, dylib
.bundlePath
, dylib
.input
->loadedFileInfo
.path
});
669 for (InputDylib
& dylib
: codelessKexts
) {
670 infos
.push_back({ dylib
.infoPlist
, nullptr, dylib
.bundlePath
, "" });
673 CFMutableArrayRef bundlesArrayRef
= CFArrayCreateMutable(kCFAllocatorDefault
, 0,
674 &kCFTypeArrayCallBacks
);
675 for (PrelinkInfo
& info
: infos
) {
676 CFDictionaryRef infoPlist
= info
.infoPlist
;
677 // Create a copy of the dictionary so that we can add more fields
678 CFMutableDictionaryRef dictCopyRef
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, infoPlist
);
680 // _PrelinkBundlePath
681 CFStringRef bundlePath
= CFStringCreateWithCStringNoCopy(kCFAllocatorDefault
, info
.bundlePath
.data(),
682 kCFStringEncodingASCII
, kCFAllocatorNull
);
683 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkBundlePath"), bundlePath
);
684 CFRelease(bundlePath
);
686 // Note we want this address to be a large enough integer in the xml format that we have enough space
687 // to replace it with its real address later
688 const uint64_t largeAddress
= 0x7FFFFFFFFFFFFFFF;
690 // _PrelinkExecutableLoadAddr
691 // Leave a placeholder for this for now just so that we have enough space for it later
692 CFNumberRef loadAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &largeAddress
);
693 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkExecutableLoadAddr"), loadAddrRef
);
694 CFRelease(loadAddrRef
);
696 // _PrelinkExecutableRelativePath
697 if ( info
.executablePath
!= "" ) {
698 const char* relativePath
= info
.executablePath
.data();
699 if ( strncmp(relativePath
, info
.bundlePath
.data(), info
.bundlePath
.size()) == 0 ) {
700 relativePath
= relativePath
+ info
.bundlePath
.size();
701 if ( relativePath
[0] == '/' )
703 } else if ( const char* lastSlash
= strrchr(relativePath
, '/') )
704 relativePath
= lastSlash
+1;
705 CFStringRef executablePath
= CFStringCreateWithCStringNoCopy(kCFAllocatorDefault
, relativePath
,
706 kCFStringEncodingASCII
, kCFAllocatorNull
);
707 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkExecutableRelativePath"), executablePath
);
708 CFRelease(executablePath
);
711 // _PrelinkExecutableSize
712 // This seems to be the file size of __TEXT
713 __block
uint64_t textSegFileSize
= 0;
714 if ( info
.ma
!= nullptr ) {
715 info
.ma
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
716 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
717 textSegFileSize
= segInfo
.fileSize
;
720 if (textSegFileSize
!= 0) {
721 CFNumberRef fileSizeRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &textSegFileSize
);
722 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkExecutableSize"), fileSizeRef
);
723 CFRelease(fileSizeRef
);
726 // _PrelinkExecutableSourceAddr
727 // Leave a placeholder for this for now just so that we have enough space for it later
728 CFNumberRef sourceAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &largeAddress
);
729 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkExecutableSourceAddr"), sourceAddrRef
);
730 CFRelease(sourceAddrRef
);
733 // Leave a placeholder for this for now just so that we have enough space for it later
734 dyld3::MachOAnalyzer::FoundSymbol foundInfo
;
735 if ( (info
.ma
!= nullptr) ) {
736 // Check for a global first
737 __block
bool found
= false;
738 found
= info
.ma
->findExportedSymbol(_diagnostics
, "_kmod_info", true, foundInfo
, nullptr);
740 // And fall back to a local if we need to
741 info
.ma
->forEachLocalSymbol(_diagnostics
, ^(const char* aSymbolName
, uint64_t n_value
, uint8_t n_type
,
742 uint8_t n_sect
, uint16_t n_desc
, bool& stop
) {
743 if ( strcmp(aSymbolName
, "_kmod_info") == 0 ) {
751 CFNumberRef kmodInfoAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &largeAddress
);
752 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkKmodInfo"), kmodInfoAddrRef
);
753 CFRelease(kmodInfoAddrRef
);
757 CFArrayAppendValue(bundlesArrayRef
, dictCopyRef
);
758 // Release the temporary dictionary now that its in the array
759 CFRelease(dictCopyRef
);
762 prelinkInfoDict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
763 &kCFTypeDictionaryKeyCallBacks
,
764 &kCFTypeDictionaryValueCallBacks
);
766 // First add any data from addPrelinkInfo()
767 if ( extraPrelinkInfo
!= nullptr ) {
768 CFDictionaryApplierFunction applier
= [](const void *key
, const void *value
, void *context
) {
769 CFMutableDictionaryRef parentDict
= (CFMutableDictionaryRef
)context
;
770 CFDictionaryAddValue(parentDict
, key
, value
);
772 CFDictionaryApplyFunction(extraPrelinkInfo
, applier
, (void*)prelinkInfoDict
);
775 if ( bundlesArrayRef
!= nullptr ) {
776 CFDictionaryAddValue(prelinkInfoDict
, CFSTR("_PrelinkInfoDictionary"), bundlesArrayRef
);
777 CFRelease(bundlesArrayRef
);
780 // Add a placeholder for the collection UUID
783 CFDataRef dataRef
= CFDataCreate(kCFAllocatorDefault
, (const uint8_t*)&uuid
, sizeof(uuid
));
784 CFDictionaryAddValue(prelinkInfoDict
, CFSTR("_PrelinkKCID"), dataRef
);
788 // The pageable/aux KCs should embed the UUID of the base kernel collection
789 if ( existingKernelCollection
!= nullptr ) {
791 bool foundUUID
= existingKernelCollection
->getUuid(uuid
);
793 _diagnostics
.error("Could not find UUID in base kernel collection");
796 CFDataRef dataRef
= CFDataCreate(kCFAllocatorDefault
, (const uint8_t*)&uuid
, sizeof(uuid
));
797 CFDictionaryAddValue(prelinkInfoDict
, CFSTR("_BootKCID"), dataRef
);
801 // The aux KC should embed the UUID of the pageable kernel collection if we have one
802 if ( pageableKernelCollection
!= nullptr ) {
804 bool foundUUID
= pageableKernelCollection
->getUuid(uuid
);
806 _diagnostics
.error("Could not find UUID in pageable kernel collection");
809 CFDataRef dataRef
= CFDataCreate(kCFAllocatorDefault
, (const uint8_t*)&uuid
, sizeof(uuid
));
810 CFDictionaryAddValue(prelinkInfoDict
, CFSTR("_PageableKCID"), dataRef
);
814 CFErrorRef errorRef
= nullptr;
815 CFDataRef xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, prelinkInfoDict
,
816 kCFPropertyListXMLFormat_v1_0
, 0, &errorRef
);
817 if (errorRef
!= nullptr) {
818 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
819 _diagnostics
.error("Could not serialise plist because :%s",
820 CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
825 CFIndex xmlDataLength
= CFDataGetLength(xmlData
);
829 prelinkInfoRegion
.bufferSize
= align(xmlDataLength
, 14);
830 prelinkInfoRegion
.sizeInUse
= prelinkInfoRegion
.bufferSize
;
831 prelinkInfoRegion
.permissions
= VM_PROT_READ
| VM_PROT_WRITE
;
832 prelinkInfoRegion
.name
= "__PRELINK_INFO";
836 // Do all __LINKINFO regardless of split seg
837 _nonLinkEditReadOnlySize
= 0;
838 __block
uint64_t offsetInRegion
= 0;
839 for (DylibInfo
& dylib
: sortedDylibs
) {
840 __block
uint64_t textSegVmAddr
= 0;
841 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
842 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
843 textSegVmAddr
= segInfo
.vmAddr
;
844 if ( segInfo
.protections
!= VM_PROT_READ
)
846 if ( strcmp(segInfo
.segName
, "__LINKINFO") != 0 )
848 // Keep segments 4K or more aligned
849 offsetInRegion
= align(offsetInRegion
, std::max((int)segInfo
.p2align
, (int)12));
850 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
851 SegmentMappingInfo loc
;
852 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
853 loc
.segName
= segInfo
.segName
;
854 loc
.dstSegment
= nullptr;
855 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
856 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
857 loc
.dstCacheSegmentSize
= (uint32_t)align(segInfo
.sizeOfSections
, 12);
858 loc
.dstCacheFileSize
= (uint32_t)copySize
;
859 loc
.copySegmentSize
= (uint32_t)copySize
;
860 loc
.srcSegmentIndex
= segInfo
.segIndex
;
861 loc
.parentRegion
= &_readOnlyRegion
;
862 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
863 offsetInRegion
+= loc
.dstCacheSegmentSize
;
867 // Align the end of the __LINKINFO
868 offsetInRegion
= align(offsetInRegion
, 14);
869 _nonLinkEditReadOnlySize
= offsetInRegion
;
871 // Do all __LINKEDIT, regardless of split seg
872 for (DylibInfo
& dylib
: sortedDylibs
) {
873 __block
uint64_t textSegVmAddr
= 0;
874 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
875 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
876 textSegVmAddr
= segInfo
.vmAddr
;
877 if ( segInfo
.protections
!= VM_PROT_READ
)
879 if ( strcmp(segInfo
.segName
, "__LINKEDIT") != 0 )
881 // Keep segments 4K or more aligned
882 offsetInRegion
= align(offsetInRegion
, std::max((int)segInfo
.p2align
, (int)12));
883 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
884 SegmentMappingInfo loc
;
885 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
886 loc
.segName
= segInfo
.segName
;
887 loc
.dstSegment
= nullptr;
888 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
889 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
890 loc
.dstCacheSegmentSize
= (uint32_t)align(segInfo
.sizeOfSections
, 12);
891 loc
.dstCacheFileSize
= (uint32_t)copySize
;
892 loc
.copySegmentSize
= (uint32_t)copySize
;
893 loc
.srcSegmentIndex
= segInfo
.segIndex
;
894 loc
.parentRegion
= &_readOnlyRegion
;
895 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
896 offsetInRegion
+= loc
.dstCacheSegmentSize
;
900 // align r/o region end
901 _readOnlyRegion
.bufferSize
= align(offsetInRegion
, 14);
902 _readOnlyRegion
.sizeInUse
= _readOnlyRegion
.bufferSize
;
903 _readOnlyRegion
.permissions
= VM_PROT_READ
;
904 _readOnlyRegion
.name
= "__LINKEDIT";
906 // Add space in __LINKEDIT for chained fixups and classic relocs
909 // The pageableKC (and sometimes auxKC) has 1 LC_DYLD_CHAINED_FIXUPS per kext
910 // while other KCs have 1 for the whole KC.
911 // It also tracks each segment in each kext for chained fixups, not the segments on the KC itself
912 __block
uint64_t numSegmentsForChainedFixups
= 0;
913 uint64_t numChainedFixupHeaders
= 0;
914 if ( fixupsArePerKext() ) {
915 for (DylibInfo
& dylib
: sortedDylibs
) {
916 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
917 ++numSegmentsForChainedFixups
;
920 numChainedFixupHeaders
= sortedDylibs
.size();
922 // Branch stubs need fixups on the GOTs region. So add in a top-level chained fixup entry
923 // and for now all the regions as we don't know what segment index the branch GOTs will be
924 numSegmentsForChainedFixups
+= numRegions();
925 numChainedFixupHeaders
++;
927 numSegmentsForChainedFixups
= numRegions();
928 numChainedFixupHeaders
= 1;
931 uint64_t numBytesForPageStarts
= 0;
932 if ( dataConstRegion
.sizeInUse
!= 0 )
933 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(dataConstRegion
.bufferSize
));
934 if ( branchGOTsRegion
.bufferSize
!= 0 )
935 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(branchGOTsRegion
.bufferSize
));
936 if ( readWriteRegion
.sizeInUse
!= 0 )
937 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(readWriteRegion
.bufferSize
));
938 if ( hibernateRegion
.sizeInUse
!= 0 )
939 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(hibernateRegion
.bufferSize
));
940 for (const Region
& region
: nonSplitSegRegions
) {
941 // Assume writable regions have fixups to emit
942 // Note, third party kext's have __TEXT fixups, so assume all of these have fixups
943 // LINKEDIT is already elsewhere
944 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(region
.bufferSize
));
947 uint64_t numBytesForChainedFixups
= 0;
948 if ( numBytesForPageStarts
!= 0 ) {
949 numBytesForChainedFixups
= numBytesForPageStarts
;
950 numBytesForChainedFixups
+= sizeof(dyld_chained_fixups_header
) * numChainedFixupHeaders
;
951 numBytesForChainedFixups
+= sizeof(dyld_chained_starts_in_image
) * numChainedFixupHeaders
;
952 numBytesForChainedFixups
+= sizeof(uint32_t) * numSegmentsForChainedFixups
;
955 __block
uint64_t numBytesForClassicRelocs
= 0;
956 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::kernel
) {
957 if ( const DylibInfo
* dylib
= getKernelStaticExecutableInputFile() ) {
958 if ( dylib
->input
->mappedFile
.mh
->usesClassicRelocationsInKernelCollection() ) {
959 dylib
->input
->mappedFile
.mh
->forEachRebase(_diagnostics
, false, ^(uint64_t runtimeOffset
, bool &stop
) {
960 numBytesForClassicRelocs
+= sizeof(relocation_info
);
966 // align fixups region end
967 if ( (numBytesForChainedFixups
!= 0) || (numBytesForClassicRelocs
!= 0) ) {
968 uint64_t numBytes
= align(numBytesForChainedFixups
, 3) + align(numBytesForClassicRelocs
, 3);
969 fixupsSubRegion
.bufferSize
= align(numBytes
, 14);
970 fixupsSubRegion
.sizeInUse
= fixupsSubRegion
.bufferSize
;
971 fixupsSubRegion
.permissions
= VM_PROT_READ
;
972 fixupsSubRegion
.name
= "__FIXUPS";
977 void AppCacheBuilder::assignSegmentAddresses() {
978 // Segments already have offsets in to their regions. Now assign the regions their addresses
979 // in the full allocated buffer, and then assign all segments in those regions
980 for (DylibInfo
& dylib
: sortedDylibs
) {
981 for (SegmentMappingInfo
& loc
: dylib
.cacheLocation
) {
982 loc
.dstSegment
= loc
.parentRegion
->buffer
+ loc
.dstCacheFileOffset
;
983 loc
.dstCacheUnslidAddress
= loc
.parentRegion
->unslidLoadAddress
+ loc
.dstCacheFileOffset
;
984 loc
.dstCacheFileOffset
= (uint32_t)loc
.parentRegion
->cacheFileOffset
+ loc
.dstCacheFileOffset
;
989 void AppCacheBuilder::copyRawSegments() {
990 const bool log
= false;
992 // Call the base class to copy segment data
993 CacheBuilder::copyRawSegments();
995 // The copy any custom sections
996 for (const CustomSegment
& segment
: customSegments
) {
997 for (const CustomSegment::CustomSection
& section
: segment
.sections
) {
998 uint8_t* dstBuffer
= segment
.parentRegion
->buffer
+ section
.offsetInRegion
;
999 uint64_t dstVMAddr
= segment
.parentRegion
->unslidLoadAddress
+ section
.offsetInRegion
;
1000 if (log
) fprintf(stderr
, "copy %s segment %s %s (0x%08lX bytes) from %p to %p (logical addr 0x%llX)\n",
1001 _options
.archs
->name(), segment
.segmentName
.c_str(), section
.sectionName
.c_str(),
1002 section
.data
.size(), section
.data
.data(), dstBuffer
, dstVMAddr
);
1003 ::memcpy(dstBuffer
, section
.data
.data(), section
.data
.size());
1008 static uint8_t getFixupLevel(AppCacheBuilder::Options::AppCacheKind kind
) {
1009 uint8_t currentLevel
= (uint8_t)~0U;
1011 case AppCacheBuilder::Options::AppCacheKind::none
:
1012 assert(0 && "Cache kind should have been set");
1014 case AppCacheBuilder::Options::AppCacheKind::kernel
:
1017 case AppCacheBuilder::Options::AppCacheKind::pageableKC
:
1018 // The pageableKC sits right above the baseKC which is level 0
1021 case AppCacheBuilder::Options::AppCacheKind::kernelCollectionLevel2
:
1022 assert(0 && "Unimplemented");
1024 case AppCacheBuilder::Options::AppCacheKind::auxKC
:
1028 return currentLevel
;
1031 uint32_t AppCacheBuilder::getCurrentFixupLevel() const {
1032 return getFixupLevel(appCacheOptions
.cacheKind
);
1035 struct VTableBindSymbol
{
1036 std::string_view binaryID
;
1037 std::string symbolName
;
1040 // For every dylib, lets make a map from its exports to its defs
1041 struct DylibSymbols
{
1042 // Define a bunch of constructors so that we know we are getting move constructors not copies
1043 DylibSymbols() = default;
1044 DylibSymbols(const DylibSymbols
&) = delete;
1045 DylibSymbols(DylibSymbols
&&) = default;
1046 DylibSymbols(std::map
<std::string_view
, uint64_t>&& globals
,
1047 std::map
<std::string_view
, uint64_t>&& locals
,
1048 std::unique_ptr
<std::unordered_set
<std::string
>> kpiSymbols
,
1049 uint32_t dylibLevel
, const std::string
& dylibName
)
1050 : globals(std::move(globals
)), locals(std::move(locals
)), kpiSymbols(std::move(kpiSymbols
)),
1051 dylibLevel(dylibLevel
), dylibName(dylibName
) { }
1053 DylibSymbols
& operator=(const DylibSymbols
& other
) = delete;
1054 DylibSymbols
& operator=(DylibSymbols
&& other
) = default;
1056 std::map
<std::string_view
, uint64_t> globals
;
1058 // We also need to track locals as vtable patching supports patching with these too
1059 std::map
<std::string_view
, uint64_t> locals
;
1061 // KPI (ie, a symbol set embedded in this binary)
1062 std::unique_ptr
<std::unordered_set
<std::string
>> kpiSymbols
;
1064 // Kernel collections can reference each other in levels. This is the level
1065 // of the exported dylib. Eg, the base KC is 0, and the aux KC is 3
1066 uint32_t dylibLevel
= 0;
1068 // Store the name of the dylib for fast lookups
1069 std::string dylibName
;
1071 // Keep track of the binds in this dylib as these tell us if a vtable slot is to a local
1072 // or external definition of a function
1073 std::unordered_map
<const uint8_t*, VTableBindSymbol
> resolvedBindLocations
;
1076 class VTablePatcher
{
1079 VTablePatcher(uint32_t numFixupLevels
);
1081 bool hasError() const;
1083 void addKernelCollection(const dyld3::MachOAppCache
* cacheMA
, AppCacheBuilder::Options::AppCacheKind kind
,
1084 const uint8_t* basePointer
, uint64_t baseAddress
);
1085 void addDylib(Diagnostics
& diags
, const dyld3::MachOAnalyzer
* ma
, const std::string
& dylibID
,
1086 const std::vector
<std::string
>& dependencies
, uint8_t cacheLevel
);
1088 void findMetaclassDefinitions(std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
1089 const std::string
& kernelID
, const dyld3::MachOAnalyzer
* kernelMA
,
1090 AppCacheBuilder::Options::AppCacheKind cacheKind
);
1091 void findExistingFixups(Diagnostics
& diags
,
1092 const dyld3::MachOAppCache
* existingKernelCollection
,
1093 const dyld3::MachOAppCache
* pageableKernelCollection
);
1094 void findBaseKernelVTables(Diagnostics
& diags
, const dyld3::MachOAppCache
* existingKernelCollection
,
1095 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
);
1096 void findPageableKernelVTables(Diagnostics
& diags
, const dyld3::MachOAppCache
* existingKernelCollection
,
1097 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
);
1098 void findVTables(uint8_t currentLevel
, const dyld3::MachOAnalyzer
* kernelMA
,
1099 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
1100 const AppCacheBuilder::ASLR_Tracker
& aslrTracker
,
1101 const std::map
<const uint8_t*, const VTableBindSymbol
>& missingBindLocations
);
1102 void calculateSymbols();
1103 void patchVTables(Diagnostics
& diags
,
1104 std::map
<const uint8_t*, const VTableBindSymbol
>& missingBindLocations
,
1105 AppCacheBuilder::ASLR_Tracker
& aslrTracker
,
1106 uint8_t currentLevel
);
1110 void logFunc(const char* format
, ...) {
1111 if ( logPatching
) {
1113 va_start(list
, format
);
1114 vfprintf(stderr
, format
, list
);
1119 void logFuncVerbose(const char* format
, ...) {
1120 if ( logPatchingVerbose
) {
1122 va_start(list
, format
);
1123 vfprintf(stderr
, format
, list
);
1128 // Extract a substring by dropping optional prefix/suffix
1129 std::string_view
extractString(std::string_view str
, std::string_view prefix
, std::string_view suffix
) {
1130 if ( !prefix
.empty() ) {
1131 // Make sure we have the prefix we are looking for
1132 if ( str
.find(prefix
) != 0 ) {
1133 return std::string_view();
1135 str
.remove_prefix(prefix
.size());
1137 if ( !suffix
.empty() ) {
1138 // Make sure we have the prefix we are looking for
1139 size_t pos
= str
.rfind(suffix
);
1140 if ( pos
!= (str
.size() - suffix
.size()) ) {
1141 return std::string_view();
1143 str
.remove_suffix(suffix
.size());
1150 const uint8_t* location
= nullptr;
1151 uint64_t targetVMAddr
= ~0ULL;
1152 uint32_t targetCacheLevel
= ~0;
1154 uint16_t diversity
= 0;
1155 bool hasAddrDiv
= false;
1157 bool hasPointerAuth
= false;
1160 const dyld3::MachOAnalyzer
* ma
= nullptr;
1161 const uint8_t* superVTable
= nullptr;
1162 const DylibSymbols
* dylib
= nullptr;
1163 bool fromParentCollection
= false;
1164 bool patched
= false;
1165 std::string name
= "";
1166 std::vector
<Entry
> entries
;
1169 struct SymbolLocation
{
1170 uint64_t vmAddr
= 0;
1171 bool foundSymbol
= 0;
1173 bool found() const {
1179 uint64_t targetVMAddr
= 0;
1180 uint8_t cacheLevel
= 0;
1182 uint16_t diversity
= 0;
1183 bool hasAddrDiv
= false;
1185 bool hasPointerAuth
= false;
1188 struct VTableDylib
{
1189 Diagnostics
* diags
= nullptr;
1190 const dyld3::MachOAnalyzer
* ma
= nullptr;
1191 std::string dylibID
= "";
1192 std::vector
<std::string
> dependencies
;
1193 uint32_t cacheLevel
= ~0U;
1196 struct KernelCollection
{
1197 const dyld3::MachOAppCache
* ma
= nullptr;
1199 // We need the base pointers to the buffers for every level
1200 // These are the base of the allocated memory, which corresponds to pointing to the lowest
1201 // vmAddr for the buffer. These do *not* necessarily point to a mach_header
1202 const uint8_t* basePointer
= nullptr;
1204 // We also need the base vm addresses to the buffers for every level
1205 uint64_t baseAddress
= ~0ULL;
1207 std::unordered_map
<uint64_t, const char*> symbolNames
;
1208 std::map
<uint64_t, std::string_view
> metaclassDefinitions
;
1211 SymbolLocation
findVTablePatchingSymbol(std::string_view symbolName
, const DylibSymbols
& dylibSymbols
);
1213 std::vector
<VTableDylib
> dylibs
;
1214 std::map
<const uint8_t*, VTable
> vtables
;
1215 std::vector
<KernelCollection
> collections
;
1216 const uint8_t* baseMetaClassVTableLoc
= nullptr;
1218 // Record all the fixup locations in the base/pageable KCs as we need to use them instead of the ASLR tracker
1219 std::map
<const uint8_t*, Fixup
> existingCollectionFixupLocations
;
1221 const uint32_t pointerSize
= 8;
1222 const bool logPatching
= false;
1223 const bool logPatchingVerbose
= false;
1225 // Magic constants for vtable patching
1226 //const char* cxxPrefix = "__Z";
1227 const char* vtablePrefix
= "__ZTV";
1228 const char* osObjPrefix
= "__ZN";
1229 // const char* vtableReservedToken = "_RESERVED";
1230 const char* metaclassToken
= "10gMetaClassE";
1231 const char* superMetaclassPointerToken
= "10superClassE";
1232 const char* metaclassVTablePrefix
= "__ZTVN";
1233 const char* metaclassVTableSuffix
= "9MetaClassE";
1236 VTablePatcher::VTablePatcher(uint32_t numFixupLevels
) {
1237 collections
.resize(numFixupLevels
);
1240 bool VTablePatcher::hasError() const {
1241 for (const VTableDylib
& dylib
: dylibs
) {
1242 if ( dylib
.diags
->hasError() )
1248 void VTablePatcher::addKernelCollection(const dyld3::MachOAppCache
* cacheMA
, AppCacheBuilder::Options::AppCacheKind kind
,
1249 const uint8_t* basePointer
, uint64_t baseAddress
) {
1250 uint8_t cacheLevel
= getFixupLevel(kind
);
1252 assert(cacheLevel
< collections
.size());
1253 assert(collections
[cacheLevel
].ma
== nullptr);
1255 collections
[cacheLevel
].ma
= cacheMA
;
1256 collections
[cacheLevel
].basePointer
= basePointer
;
1257 collections
[cacheLevel
].baseAddress
= baseAddress
;
1260 void VTablePatcher::addDylib(Diagnostics
&diags
, const dyld3::MachOAnalyzer
*ma
,
1261 const std::string
& dylibID
, const std::vector
<std::string
>& dependencies
,
1262 uint8_t cacheLevel
) {
1263 dylibs
.push_back((VTableDylib
){ &diags
, ma
, dylibID
, dependencies
, cacheLevel
});
1266 VTablePatcher::SymbolLocation
VTablePatcher::findVTablePatchingSymbol(std::string_view symbolName
,
1267 const DylibSymbols
& dylibSymbols
) {
1268 // First look in the globals
1269 auto globalsIt
= dylibSymbols
.globals
.find(symbolName
);
1270 if ( globalsIt
!= dylibSymbols
.globals
.end() ) {
1271 return { globalsIt
->second
, true };
1274 // Then again in the locals
1275 auto localsIt
= dylibSymbols
.locals
.find(symbolName
);
1276 if ( localsIt
!= dylibSymbols
.locals
.end() ) {
1277 return { localsIt
->second
, true };
1280 return { ~0ULL, false };
1283 void VTablePatcher::findMetaclassDefinitions(std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
1284 const std::string
& kernelID
, const dyld3::MachOAnalyzer
* kernelMA
,
1285 AppCacheBuilder::Options::AppCacheKind cacheKind
) {
1286 for (VTableDylib
& dylib
: dylibs
) {
1287 auto& metaclassDefinitions
= collections
[dylib
.cacheLevel
].metaclassDefinitions
;
1288 dylib
.ma
->forEachGlobalSymbol(*dylib
.diags
, ^(const char *symbolName
, uint64_t n_value
,
1289 uint8_t n_type
, uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1290 if ( strstr(symbolName
, metaclassToken
) != nullptr )
1291 metaclassDefinitions
[n_value
] = symbolName
;
1293 dylib
.ma
->forEachLocalSymbol(*dylib
.diags
, ^(const char *symbolName
, uint64_t n_value
,
1294 uint8_t n_type
, uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1295 if ( strstr(symbolName
, metaclassToken
) != nullptr )
1296 metaclassDefinitions
[n_value
] = symbolName
;
1300 // Keep track of the root OSMetaClass from which all other metaclasses inherit
1301 DylibSymbols
& kernelDylibSymbols
= dylibsToSymbols
[kernelID
];
1302 SymbolLocation symbolLocation
= findVTablePatchingSymbol("__ZTV11OSMetaClass", kernelDylibSymbols
);
1303 if ( symbolLocation
.found() ) {
1304 baseMetaClassVTableLoc
= (uint8_t*)kernelMA
+ (symbolLocation
.vmAddr
- kernelMA
->preferredLoadAddress());
1306 VTable
& vtable
= vtables
[baseMetaClassVTableLoc
];
1307 vtable
.ma
= kernelMA
;
1308 vtable
.dylib
= &kernelDylibSymbols
;
1309 vtable
.fromParentCollection
= (cacheKind
!= AppCacheBuilder::Options::AppCacheKind::kernel
);
1310 vtable
.patched
= true;
1311 vtable
.name
= "__ZTV11OSMetaClass";
1315 void VTablePatcher::findExistingFixups(Diagnostics
& diags
,
1316 const dyld3::MachOAppCache
* existingKernelCollection
,
1317 const dyld3::MachOAppCache
* pageableKernelCollection
) {
1319 const bool is64
= pointerSize
== 8;
1321 if ( existingKernelCollection
!= nullptr ) {
1322 uint8_t kernelLevel
= getFixupLevel(AppCacheBuilder::Options::AppCacheKind::kernel
);
1323 uint64_t kernelBaseAddress
= collections
[kernelLevel
].baseAddress
;
1324 const uint8_t* kernelBasePointer
= collections
[kernelLevel
].basePointer
;
1326 // We may have both chained and classic fixups. First add chained
1327 if ( existingKernelCollection
->hasChainedFixupsLoadCommand() ) {
1328 existingKernelCollection
->withChainStarts(diags
, 0, ^(const dyld_chained_starts_in_image
* starts
) {
1329 existingKernelCollection
->forEachFixupInAllChains(diags
, starts
, false,
1330 ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk
* fixupLoc
, const dyld_chained_starts_in_segment
* segInfo
, bool& stop
) {
1331 uint64_t vmOffset
= 0;
1332 bool isRebase
= fixupLoc
->isRebase(segInfo
->pointer_format
, kernelBaseAddress
, vmOffset
);
1334 uint64_t targetVMAddr
= kernelBaseAddress
+ vmOffset
;
1335 uint16_t diversity
= fixupLoc
->kernel64
.diversity
;
1336 bool hasAddrDiv
= fixupLoc
->kernel64
.addrDiv
;
1337 uint8_t key
= fixupLoc
->kernel64
.key
;
1338 bool hasPointerAuth
= fixupLoc
->kernel64
.isAuth
;
1339 existingCollectionFixupLocations
[(const uint8_t*)fixupLoc
] = { targetVMAddr
, kernelLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
};
1344 // And add classic if we have them
1345 existingKernelCollection
->forEachRebase(diags
, ^(const char *opcodeName
, const dyld3::MachOAnalyzer::LinkEditInfo
&leInfo
,
1346 const dyld3::MachOAnalyzer::SegmentInfo
*segments
,
1347 bool segIndexSet
, uint32_t pointerSize
, uint8_t segmentIndex
,
1348 uint64_t segmentOffset
, dyld3::MachOAnalyzer::Rebase kind
, bool &stop
) {
1349 uint64_t rebaseVmAddr
= segments
[segmentIndex
].vmAddr
+ segmentOffset
;
1350 uint64_t runtimeOffset
= rebaseVmAddr
- kernelBaseAddress
;
1351 const uint8_t* fixupLoc
= kernelBasePointer
+ runtimeOffset
;
1352 uint64_t targetVMAddr
= 0;
1354 targetVMAddr
= *(uint64_t*)fixupLoc
;
1356 targetVMAddr
= *(uint32_t*)fixupLoc
;
1358 // Classic relocs have no pointer auth
1359 uint16_t diversity
= 0;
1360 bool hasAddrDiv
= false;
1362 bool hasPointerAuth
= false;
1363 existingCollectionFixupLocations
[(const uint8_t*)fixupLoc
] = { targetVMAddr
, kernelLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
};
1367 // Add pageable fixup locations if we have it
1368 if ( pageableKernelCollection
!= nullptr ) {
1369 // We only have chained fixups here to add, but they are on each kext, not on the KC itself
1370 pageableKernelCollection
->forEachDylib(diags
, ^(const dyld3::MachOAnalyzer
*ma
, const char *name
, bool &stop
) {
1371 // Skip kexts without fixups
1372 if ( !ma
->hasChainedFixupsLoadCommand() )
1374 ma
->withChainStarts(diags
, 0, ^(const dyld_chained_starts_in_image
* starts
) {
1375 ma
->forEachFixupInAllChains(diags
, starts
, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk
* fixupLoc
, const dyld_chained_starts_in_segment
* segInfo
, bool& stop
) {
1376 uint64_t vmOffset
= 0;
1377 bool isRebase
= fixupLoc
->isRebase(DYLD_CHAINED_PTR_64_KERNEL_CACHE
, 0, vmOffset
);
1379 uint8_t targetFixupLevel
= fixupLoc
->kernel64
.cacheLevel
;
1380 uint64_t targetVMAddr
= collections
[targetFixupLevel
].baseAddress
+ vmOffset
;
1381 uint16_t diversity
= fixupLoc
->kernel64
.diversity
;
1382 bool hasAddrDiv
= fixupLoc
->kernel64
.addrDiv
;
1383 uint8_t key
= fixupLoc
->kernel64
.key
;
1384 bool hasPointerAuth
= fixupLoc
->kernel64
.isAuth
;
1385 existingCollectionFixupLocations
[(const uint8_t*)fixupLoc
] = { targetVMAddr
, targetFixupLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
};
1392 void VTablePatcher::findBaseKernelVTables(Diagnostics
& diags
, const dyld3::MachOAppCache
* existingKernelCollection
,
1393 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
)
1395 const bool is64
= pointerSize
== 8;
1397 uint8_t kernelLevel
= getFixupLevel(AppCacheBuilder::Options::AppCacheKind::kernel
);
1398 uint64_t kernelBaseAddress
= collections
[kernelLevel
].baseAddress
;
1399 const uint8_t* kernelBasePointer
= collections
[kernelLevel
].basePointer
;
1400 uint16_t chainedPointerFormat
= 0;
1402 if ( existingKernelCollection
->hasChainedFixupsLoadCommand() )
1403 chainedPointerFormat
= existingKernelCollection
->chainedPointerFormat();
1405 // Map from dylibID to list of dependencies
1406 std::map
<std::string
, const std::vector
<std::string
>*> kextDependencies
;
1407 for (VTableDylib
& dylib
: dylibs
) {
1408 if ( dylib
.cacheLevel
!= kernelLevel
)
1410 kextDependencies
[dylib
.dylibID
] = &dylib
.dependencies
;
1413 bool kernelUsesClassicRelocs
= existingKernelCollection
->usesClassicRelocationsInKernelCollection();
1414 existingKernelCollection
->forEachDylib(diags
, ^(const dyld3::MachOAnalyzer
*ma
, const char *dylibID
, bool &stop
) {
1415 uint64_t loadAddress
= ma
->preferredLoadAddress();
1417 auto visitBaseKernelCollectionSymbols
= ^(const char *symbolName
, uint64_t n_value
) {
1418 if ( strstr(symbolName
, superMetaclassPointerToken
) == nullptr )
1420 uint8_t* fixupLoc
= (uint8_t*)ma
+ (n_value
- loadAddress
);
1421 logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName
, dylibID
, fixupLoc
);
1423 // 2 - Derive the name of the class from the super MetaClass pointer.
1424 std::string_view className
= extractString(symbolName
, osObjPrefix
, superMetaclassPointerToken
);
1425 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1426 if ( className
.empty() ) {
1427 logFunc("Unsupported vtable superclass name\n");
1430 logFunc("Class name: '%s'\n", std::string(className
).c_str());
1432 // 3 - Derive the name of the class's vtable from the name of the class
1433 // We support namespaces too which means adding an N before the class name and E after
1434 std::string classVTableName
= std::string(vtablePrefix
) + std::string(className
);
1435 logFunc("Class vtable name: '%s'\n", classVTableName
.c_str());
1437 uint64_t classVTableVMAddr
= 0;
1438 const DylibSymbols
& dylibSymbols
= dylibsToSymbols
[dylibID
];
1440 std::string namespacedVTableName
;
1441 SymbolLocation symbolLocation
= findVTablePatchingSymbol(classVTableName
, dylibSymbols
);
1442 if ( !symbolLocation
.found() ) {
1443 // If we didn't find a name then try again with namespaces
1444 namespacedVTableName
= std::string(vtablePrefix
) + "N" + std::string(className
) + "E";
1445 logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName
.c_str());
1446 symbolLocation
= findVTablePatchingSymbol(namespacedVTableName
, dylibSymbols
);
1448 if ( symbolLocation
.found() ) {
1449 classVTableVMAddr
= symbolLocation
.vmAddr
;
1451 diags
.error("Class vtables '%s' or '%s' is not exported from '%s'",
1452 classVTableName
.c_str(), namespacedVTableName
.c_str(), dylibID
);
1458 logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr
);
1459 const uint8_t* classVTableLoc
= kernelBasePointer
+ (classVTableVMAddr
- kernelBaseAddress
);
1461 // 4 - Follow the super MetaClass pointer to get the address of the super MetaClass's symbol
1462 uint64_t superMetaclassSymbolAddress
= 0;
1463 auto existingKernelCollectionFixupLocIt
= existingCollectionFixupLocations
.find(fixupLoc
);
1464 if ( existingKernelCollectionFixupLocIt
!= existingCollectionFixupLocations
.end() ) {
1465 if ( ma
->isKextBundle() || !kernelUsesClassicRelocs
) {
1466 auto* chainedFixupLoc
= (dyld3::MachOLoaded::ChainedFixupPointerOnDisk
*)fixupLoc
;
1467 uint64_t vmOffset
= 0;
1468 bool isRebase
= chainedFixupLoc
->isRebase(chainedPointerFormat
, kernelBaseAddress
, vmOffset
);
1470 superMetaclassSymbolAddress
= kernelBaseAddress
+ vmOffset
;
1472 // The classic reloc is already the vmAddr so nothing special to do here.
1474 superMetaclassSymbolAddress
= *(uint64_t*)fixupLoc
;
1478 logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress
);
1480 if ( superMetaclassSymbolAddress
== 0 ) {
1481 if ( classVTableName
== "__ZTV8OSObject" ) {
1482 // This is the base class of all objects, so it doesn't have a super class
1483 // We add it as a placeholder and set it to 'true' to show its already been processed
1484 VTable
& vtable
= vtables
[classVTableLoc
];
1486 vtable
.dylib
= &dylibSymbols
;
1487 vtable
.fromParentCollection
= true;
1488 vtable
.patched
= true;
1489 vtable
.name
= classVTableName
;
1494 // 5 - Look up the super MetaClass symbol by address
1495 // FIXME: VTable patching the auxKC with the superclass in the baseKC
1496 uint8_t superclassFixupLevel
= kernelLevel
;
1498 auto& metaclassDefinitions
= collections
[superclassFixupLevel
].metaclassDefinitions
;
1499 auto metaclassIt
= metaclassDefinitions
.find(superMetaclassSymbolAddress
);
1500 if ( metaclassIt
== metaclassDefinitions
.end() ) {
1501 diags
.error("Cannot find symbol for metaclass pointed to by '%s' in '%s'",
1502 symbolName
, dylibID
);
1507 // 6 - Derive the super class's name from the super MetaClass name
1508 std::string_view superClassName
= extractString(metaclassIt
->second
, osObjPrefix
, metaclassToken
);
1509 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1510 if ( superClassName
.empty() ) {
1511 logFunc("Unsupported vtable superclass name\n");
1514 logFunc("Superclass name: '%s'\n", std::string(superClassName
).c_str());
1516 // 7 - Derive the super class's vtable from the super class's name
1517 std::string superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1519 // We support namespaces, so first try the superclass without the namespace, then again with it
1520 const uint8_t* superclassVTableLoc
= nullptr;
1521 for (unsigned i
= 0; i
!= 2; ++i
) {
1523 superclassVTableName
= std::string(vtablePrefix
) + + "N" + std::string(superClassName
) + "E";
1525 logFunc("Superclass vtable name: '%s'\n", superclassVTableName
.c_str());
1527 if ( ma
->isKextBundle() ) {
1528 // First check if the superclass vtable comes from a dependent kext
1529 auto it
= kextDependencies
.find(dylibID
);
1530 assert(it
!= kextDependencies
.end());
1531 const std::vector
<std::string
>& dependencies
= *it
->second
;
1532 for (const std::string
& dependencyID
: dependencies
) {
1533 auto depIt
= dylibsToSymbols
.find(dependencyID
);
1534 if (depIt
== dylibsToSymbols
.end()) {
1535 diags
.error("Failed to bind '%s' in '%s' as could not find a kext with '%s' bundle-id",
1536 symbolName
, dylibID
, dependencyID
.c_str());
1541 const DylibSymbols
& dylibSymbols
= depIt
->second
;
1542 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
1543 if ( !symbolLocation
.found() )
1546 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
1547 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
1548 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
1552 if ( superclassVTableLoc
== nullptr ) {
1553 auto depIt
= dylibsToSymbols
.find(dylibID
);
1554 if (depIt
== dylibsToSymbols
.end()) {
1555 diags
.error("Failed to bind '%s' in '%s' as could not find a binary with '%s' bundle-id",
1556 symbolName
, dylibID
, dylibID
);
1561 const DylibSymbols
& dylibSymbols
= depIt
->second
;
1562 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
1563 if ( symbolLocation
.found() ) {
1564 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
1565 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
1566 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
1570 if ( superclassVTableLoc
!= nullptr )
1574 if ( superclassVTableLoc
== nullptr ) {
1575 superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1576 diags
.error("Superclass vtable '%s' is not exported from '%s' or its dependencies",
1577 superclassVTableName
.c_str(), dylibID
);
1582 // Add an entry for this vtable
1583 VTable
& vtable
= vtables
[classVTableLoc
];
1584 vtable
.superVTable
= superclassVTableLoc
;
1586 vtable
.dylib
= &dylibSymbols
;
1587 vtable
.fromParentCollection
= true;
1588 vtable
.patched
= true;
1589 vtable
.name
= classVTableName
;
1591 // And an entry for the superclass vtable
1592 VTable
& supervtable
= vtables
[superclassVTableLoc
];
1593 supervtable
.fromParentCollection
= true;
1594 supervtable
.patched
= true;
1595 supervtable
.name
= superclassVTableName
;
1598 ma
->forEachGlobalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
1599 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1600 visitBaseKernelCollectionSymbols(symbolName
, n_value
);
1603 if ( diags
.hasError() ) {
1608 ma
->forEachLocalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
1609 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1610 visitBaseKernelCollectionSymbols(symbolName
, n_value
);
1613 if ( diags
.hasError() ) {
1620 void VTablePatcher::findPageableKernelVTables(Diagnostics
& diags
, const dyld3::MachOAppCache
* pageableKernelCollection
,
1621 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
)
1623 uint8_t collectionLevel
= getFixupLevel(AppCacheBuilder::Options::AppCacheKind::pageableKC
);
1624 uint64_t collectionBaseAddress
= collections
[collectionLevel
].baseAddress
;
1625 const uint8_t* collectionBasePointer
= collections
[collectionLevel
].basePointer
;
1627 // Map from dylibID to list of dependencies
1628 std::map
<std::string
, const std::vector
<std::string
>*> kextDependencies
;
1629 for (VTableDylib
& dylib
: dylibs
) {
1630 if ( dylib
.cacheLevel
!= collectionLevel
)
1632 kextDependencies
[dylib
.dylibID
] = &dylib
.dependencies
;
1635 pageableKernelCollection
->forEachDylib(diags
, ^(const dyld3::MachOAnalyzer
*ma
, const char *dylibID
, bool &stop
) {
1636 uint64_t loadAddress
= ma
->preferredLoadAddress();
1637 auto visitPageableKernelCollectionSymbols
= ^(const char *symbolName
, uint64_t n_value
) {
1638 if ( strstr(symbolName
, superMetaclassPointerToken
) == nullptr )
1640 uint8_t* fixupLoc
= (uint8_t*)ma
+ (n_value
- loadAddress
);
1641 logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName
, dylibID
, fixupLoc
);
1643 // 2 - Derive the name of the class from the super MetaClass pointer.
1644 std::string_view className
= extractString(symbolName
, osObjPrefix
, superMetaclassPointerToken
);
1645 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1646 if ( className
.empty() ) {
1647 logFunc("Unsupported vtable superclass name\n");
1650 logFunc("Class name: '%s'\n", std::string(className
).c_str());
1652 // 3 - Derive the name of the class's vtable from the name of the class
1653 // We support namespaces too which means adding an N before the class name and E after
1654 std::string classVTableName
= std::string(vtablePrefix
) + std::string(className
);
1655 logFunc("Class vtable name: '%s'\n", classVTableName
.c_str());
1657 uint64_t classVTableVMAddr
= 0;
1658 const DylibSymbols
& dylibSymbols
= dylibsToSymbols
[dylibID
];
1660 std::string namespacedVTableName
;
1661 SymbolLocation symbolLocation
= findVTablePatchingSymbol(classVTableName
, dylibSymbols
);
1662 if ( !symbolLocation
.found() ) {
1663 // If we didn't find a name then try again with namespaces
1664 namespacedVTableName
= std::string(vtablePrefix
) + "N" + std::string(className
) + "E";
1665 logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName
.c_str());
1666 symbolLocation
= findVTablePatchingSymbol(namespacedVTableName
, dylibSymbols
);
1668 if ( symbolLocation
.found() ) {
1669 classVTableVMAddr
= symbolLocation
.vmAddr
;
1671 diags
.error("Class vtables '%s' or '%s' is not exported from '%s'",
1672 classVTableName
.c_str(), namespacedVTableName
.c_str(), dylibID
);
1678 logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr
);
1679 const uint8_t* classVTableLoc
= collectionBasePointer
+ (classVTableVMAddr
- collectionBaseAddress
);
1681 // 4 - Follow the super MetaClass pointer to get the address of the super MetaClass's symbol
1682 uint8_t superclassFixupLevel
= (uint8_t)~0U;
1683 uint64_t superMetaclassSymbolAddress
= 0;
1684 auto existingKernelCollectionFixupLocIt
= existingCollectionFixupLocations
.find(fixupLoc
);
1685 if ( existingKernelCollectionFixupLocIt
!= existingCollectionFixupLocations
.end() ) {
1686 auto* chainedFixupLoc
= (dyld3::MachOLoaded::ChainedFixupPointerOnDisk
*)fixupLoc
;
1687 uint64_t vmOffset
= 0;
1688 bool isRebase
= chainedFixupLoc
->isRebase(DYLD_CHAINED_PTR_64_KERNEL_CACHE
, 0, vmOffset
);
1690 // The superclass could be in the baseKC, while we are analysing the pageableKC, so we need to get the correct level
1692 superclassFixupLevel
= chainedFixupLoc
->kernel64
.cacheLevel
;
1693 superMetaclassSymbolAddress
= collections
[superclassFixupLevel
].baseAddress
+ vmOffset
;
1696 logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress
);
1698 if ( superMetaclassSymbolAddress
== 0 ) {
1699 if ( classVTableName
== "__ZTV8OSObject" ) {
1700 // This is the base class of all objects, so it doesn't have a super class
1701 // We add it as a placeholder and set it to 'true' to show its already been processed
1702 VTable
& vtable
= vtables
[classVTableLoc
];
1704 vtable
.dylib
= &dylibSymbols
;
1705 vtable
.fromParentCollection
= true;
1706 vtable
.patched
= true;
1707 vtable
.name
= classVTableName
;
1712 // 5 - Look up the super MetaClass symbol by address
1713 auto& metaclassDefinitions
= collections
[superclassFixupLevel
].metaclassDefinitions
;
1714 auto metaclassIt
= metaclassDefinitions
.find(superMetaclassSymbolAddress
);
1715 if ( metaclassIt
== metaclassDefinitions
.end() ) {
1716 diags
.error("Cannot find symbol for metaclass pointed to by '%s' in '%s'",
1717 symbolName
, dylibID
);
1722 // 6 - Derive the super class's name from the super MetaClass name
1723 std::string_view superClassName
= extractString(metaclassIt
->second
, osObjPrefix
, metaclassToken
);
1724 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1725 if ( superClassName
.empty() ) {
1726 logFunc("Unsupported vtable superclass name\n");
1729 logFunc("Superclass name: '%s'\n", std::string(superClassName
).c_str());
1731 // 7 - Derive the super class's vtable from the super class's name
1732 std::string superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1734 // We support namespaces, so first try the superclass without the namespace, then again with it
1735 const uint8_t* superclassVTableLoc
= nullptr;
1736 for (unsigned i
= 0; i
!= 2; ++i
) {
1738 superclassVTableName
= std::string(vtablePrefix
) + + "N" + std::string(superClassName
) + "E";
1740 logFunc("Superclass vtable name: '%s'\n", superclassVTableName
.c_str());
1742 if ( ma
->isKextBundle() ) {
1743 // First check if the superclass vtable comes from a dependent kext
1744 auto it
= kextDependencies
.find(dylibID
);
1745 assert(it
!= kextDependencies
.end());
1746 const std::vector
<std::string
>& dependencies
= *it
->second
;
1747 for (const std::string
& dependencyID
: dependencies
) {
1748 auto depIt
= dylibsToSymbols
.find(dependencyID
);
1749 if (depIt
== dylibsToSymbols
.end()) {
1750 diags
.error("Failed to bind '%s' in '%s' as could not find a kext with '%s' bundle-id",
1751 symbolName
, dylibID
, dependencyID
.c_str());
1756 const DylibSymbols
& dylibSymbols
= depIt
->second
;
1757 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
1758 if ( !symbolLocation
.found() )
1761 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
1762 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
1763 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
1767 if ( superclassVTableLoc
== nullptr ) {
1768 auto depIt
= dylibsToSymbols
.find(dylibID
);
1769 if (depIt
== dylibsToSymbols
.end()) {
1770 diags
.error("Failed to bind '%s' in '%s' as could not find a binary with '%s' bundle-id",
1771 symbolName
, dylibID
, dylibID
);
1776 const DylibSymbols
& dylibSymbols
= depIt
->second
;
1777 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
1778 if ( symbolLocation
.found() ) {
1779 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
1780 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
1781 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
1785 if ( superclassVTableLoc
!= nullptr )
1789 if ( superclassVTableLoc
== nullptr ) {
1790 superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1791 diags
.error("Superclass vtable '%s' is not exported from '%s' or its dependencies",
1792 superclassVTableName
.c_str(), dylibID
);
1797 // Add an entry for this vtable
1798 VTable
& vtable
= vtables
[classVTableLoc
];
1799 vtable
.superVTable
= superclassVTableLoc
;
1801 vtable
.dylib
= &dylibSymbols
;
1802 vtable
.fromParentCollection
= true;
1803 vtable
.patched
= true;
1804 vtable
.name
= classVTableName
;
1806 // And an entry for the superclass vtable
1807 VTable
& supervtable
= vtables
[superclassVTableLoc
];
1808 supervtable
.fromParentCollection
= true;
1809 supervtable
.patched
= true;
1810 supervtable
.name
= superclassVTableName
;
1813 ma
->forEachGlobalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
1814 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1815 visitPageableKernelCollectionSymbols(symbolName
, n_value
);
1818 if ( diags
.hasError() ) {
1823 ma
->forEachLocalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
1824 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1825 visitPageableKernelCollectionSymbols(symbolName
, n_value
);
1828 if ( diags
.hasError() ) {
1835 void VTablePatcher::findVTables(uint8_t currentLevel
, const dyld3::MachOAnalyzer
* kernelMA
,
1836 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
1837 const AppCacheBuilder::ASLR_Tracker
& aslrTracker
,
1838 const std::map
<const uint8_t*, const VTableBindSymbol
>& missingBindLocations
)
1840 const bool is64
= pointerSize
== 8;
1842 uint64_t collectionBaseAddress
= collections
[currentLevel
].baseAddress
;
1843 const uint8_t* collectionBasePointer
= collections
[currentLevel
].basePointer
;
1845 // VTable patching algorithm (for each symbol...):
1846 // - To find the address of a class vtable:
1847 // - Take symbols with '10superClassE' in their name, eg, __ZN10IOMachPort10superClassE
1848 // - Work out the name of the class from that symbol name, eg, 10IOMachPort
1849 // - Work out the name of the VTable from that class name, eg, __ZTV10IOMachPort
1850 // - Find the address for the export with that vtable name
1851 // - To find the superclass for a given class
1852 // - Take the symbol with '10superClassE' in their name, eg, __ZN10IOMachPort10superClassE
1853 // - Take its address and dereference it as "__ZN10IOMachPort10superClassE = &__ZN8OSObject10gMetaClassE"
1854 // - Find the name of the symbol at this address, eg, work out we have a symbol called __ZN8OSObject10gMetaClassE
1855 // - Get the superclassic from that symbol name, eg, 8OSObject
1856 // - Get the VTable name from that symbol, eg, __ZTV8OSObject
1857 // - Find the superclass vtable address from that name by searching the image and dependents for __ZTV8OSObject
1858 for (VTableDylib
& dylib
: dylibs
) {
1859 // Only process dylibs in the level we are building
1860 // Existing collections were handled elsewhere
1861 if ( dylib
.cacheLevel
!= currentLevel
)
1864 const dyld3::MachOAnalyzer
* ma
= dylib
.ma
;
1865 const std::string
& dylibID
= dylib
.dylibID
;
1866 Diagnostics
& dylibDiags
= *dylib
.diags
;
1867 const std::vector
<std::string
>& dependencies
= dylib
.dependencies
;
1869 uint64_t loadAddress
= ma
->preferredLoadAddress();
1870 bool alreadyPatched
= (ma
== kernelMA
);
1871 auto visitSymbols
= ^(const char *symbolName
, uint64_t n_value
) {
1872 if ( strstr(symbolName
, superMetaclassPointerToken
) == nullptr )
1875 uint8_t* fixupLoc
= (uint8_t*)ma
+ (n_value
- loadAddress
);
1876 logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName
, dylibID
.c_str(), fixupLoc
);
1878 // 2 - Derive the name of the class from the super MetaClass pointer.
1879 std::string_view className
= extractString(symbolName
, osObjPrefix
, superMetaclassPointerToken
);
1880 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1881 if ( className
.empty() ) {
1882 logFunc("Unsupported vtable superclass name\n");
1885 logFunc("Class name: '%s'\n", std::string(className
).c_str());
1887 // 3 - Derive the name of the class's vtable from the name of the class
1888 // We support namespaces too which means adding an N before the class name and E after
1889 std::string classVTableName
= std::string(vtablePrefix
) + std::string(className
);
1890 logFunc("Class vtable name: '%s'\n", classVTableName
.c_str());
1892 uint64_t classVTableVMAddr
= 0;
1893 const DylibSymbols
& dylibSymbols
= dylibsToSymbols
[dylibID
];
1895 std::string namespacedVTableName
;
1896 SymbolLocation symbolLocation
= findVTablePatchingSymbol(classVTableName
, dylibSymbols
);
1897 if ( !symbolLocation
.found() ) {
1898 // If we didn't find a name then try again with namespaces
1899 namespacedVTableName
= std::string(vtablePrefix
) + "N" + std::string(className
) + "E";
1900 logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName
.c_str());
1901 symbolLocation
= findVTablePatchingSymbol(namespacedVTableName
, dylibSymbols
);
1903 if ( symbolLocation
.found() ) {
1904 classVTableVMAddr
= symbolLocation
.vmAddr
;
1906 dylibDiags
.error("Class vtables '%s' or '%s' is not an exported symbol",
1907 classVTableName
.c_str(), namespacedVTableName
.c_str());
1912 logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr
);
1913 const uint8_t* classVTableLoc
= (uint8_t*)ma
+ (classVTableVMAddr
- loadAddress
);
1915 // 4 - Follow the super MetaClass pointer to get the address of the super MetaClass's symbol
1916 uint64_t superMetaclassSymbolAddress
= 0;
1918 uint32_t vmAddr32
= 0;
1919 uint64_t vmAddr64
= 0;
1920 if ( aslrTracker
.hasRebaseTarget32(fixupLoc
, &vmAddr32
) ) {
1921 superMetaclassSymbolAddress
= vmAddr32
;
1922 } else if ( aslrTracker
.hasRebaseTarget64(fixupLoc
, &vmAddr64
) ) {
1923 superMetaclassSymbolAddress
= vmAddr64
;
1926 superMetaclassSymbolAddress
= *(uint64_t*)fixupLoc
;
1928 uint8_t highByte
= 0;
1929 if ( aslrTracker
.hasHigh8(fixupLoc
, &highByte
) ) {
1930 uint64_t tbi
= (uint64_t)highByte
<< 56;
1931 superMetaclassSymbolAddress
|= tbi
;
1934 logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress
);
1936 if ( superMetaclassSymbolAddress
== 0 ) {
1937 if ( classVTableName
== "__ZTV8OSObject" ) {
1938 // This is the base class of all objects, so it doesn't have a super class
1939 // We add it as a placeholder and set it to 'true' to show its already been processed
1940 VTable
& vtable
= vtables
[classVTableLoc
];
1942 vtable
.dylib
= &dylibSymbols
;
1943 vtable
.fromParentCollection
= false;
1944 vtable
.patched
= true;
1945 vtable
.name
= classVTableName
;
1950 // 5 - Look up the super MetaClass symbol by address
1951 // FIXME: VTable patching the auxKC with the superclass in the baseKC
1952 uint8_t superclassFixupLevel
= currentLevel
;
1953 aslrTracker
.has(fixupLoc
, &superclassFixupLevel
);
1955 auto& metaclassDefinitions
= collections
[superclassFixupLevel
].metaclassDefinitions
;
1956 auto metaclassIt
= metaclassDefinitions
.find(superMetaclassSymbolAddress
);
1957 if ( metaclassIt
== metaclassDefinitions
.end() ) {
1958 auto bindIt
= missingBindLocations
.find(fixupLoc
);
1959 if ( bindIt
!= missingBindLocations
.end() ) {
1960 dylibDiags
.error("Cannot find symbol for metaclass pointed to by '%s'. "
1961 "Expected symbol '%s' to be defined in another kext",
1962 symbolName
, bindIt
->second
.symbolName
.c_str());
1964 dylibDiags
.error("Cannot find symbol for metaclass pointed to by '%s'",
1970 // 6 - Derive the super class's name from the super MetaClass name
1971 std::string_view superClassName
= extractString(metaclassIt
->second
, osObjPrefix
, metaclassToken
);
1972 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1973 if ( superClassName
.empty() ) {
1974 logFunc("Unsupported vtable superclass name\n");
1977 logFunc("Superclass name: '%s'\n", std::string(superClassName
).c_str());
1979 // 7 - Derive the super class's vtable from the super class's name
1980 std::string superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1982 // We support namespaces, so first try the superclass without the namespace, then again with it
1983 const uint8_t* superclassVTableLoc
= nullptr;
1984 bool superVTableIsInParentCollection
= false;
1985 for (unsigned i
= 0; i
!= 2; ++i
) {
1987 superclassVTableName
= std::string(vtablePrefix
) + + "N" + std::string(superClassName
) + "E";
1989 logFunc("Superclass vtable name: '%s'\n", superclassVTableName
.c_str());
1992 // First check if the superclass vtable comes from a dependent kext
1993 for (const std::string
& dependencyID
: dependencies
) {
1994 auto depIt
= dylibsToSymbols
.find(dependencyID
);
1995 if (depIt
== dylibsToSymbols
.end()) {
1996 dylibDiags
.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
1997 symbolName
, dependencyID
.c_str());
2001 const DylibSymbols
& dylibSymbols
= depIt
->second
;
2002 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
2003 if ( !symbolLocation
.found() )
2006 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
2007 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
2008 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
2009 superVTableIsInParentCollection
= dylibSymbols
.dylibLevel
!= currentLevel
;
2013 if ( superclassVTableLoc
== nullptr ) {
2014 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
2015 if ( symbolLocation
.found() ) {
2016 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
2017 superclassVTableLoc
= (uint8_t*)collectionBasePointer
+ (superclassVTableVMAddr
- collectionBaseAddress
);
2018 superVTableIsInParentCollection
= false;
2023 if ( superclassVTableLoc
!= nullptr )
2027 if ( superclassVTableLoc
== nullptr ) {
2028 superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
2029 dylibDiags
.error("Superclass vtable '%s' is not exported from kext or its dependencies",
2030 superclassVTableName
.c_str());
2034 // Add an entry for this vtable
2035 VTable
& vtable
= vtables
[classVTableLoc
];
2036 vtable
.superVTable
= superclassVTableLoc
;
2038 vtable
.dylib
= &dylibSymbols
;
2039 vtable
.fromParentCollection
= false;
2040 vtable
.patched
|= alreadyPatched
;
2041 vtable
.name
= classVTableName
;
2043 // And an entry for the superclass vtable
2044 VTable
& supervtable
= vtables
[superclassVTableLoc
];
2045 supervtable
.fromParentCollection
= superVTableIsInParentCollection
;
2046 supervtable
.patched
|= alreadyPatched
;
2047 supervtable
.name
= superclassVTableName
;
2049 // Also calculate the metaclass vtable name so that we can patch it
2050 std::string metaclassVTableName
= std::string(metaclassVTablePrefix
) + std::string(className
) + metaclassVTableSuffix
;
2051 logFunc("Metaclass vtable name: '%s'\n", metaclassVTableName
.c_str());
2054 // Note its safe to just ignore missing metaclass symbols if we can't find them
2055 // If the binary links then kxld would have let it run
2056 SymbolLocation symbolLocation
= findVTablePatchingSymbol(metaclassVTableName
, dylibSymbols
);
2057 if ( symbolLocation
.found() ) {
2058 uint64_t metaclassVTableVMAddr
= symbolLocation
.vmAddr
;
2060 logFunc("Metaclass vtable vmAddr: '0x%llx'\n", metaclassVTableVMAddr
);
2061 uint8_t* metaclassVTableLoc
= (uint8_t*)ma
+ (metaclassVTableVMAddr
- loadAddress
);
2063 // Add an entry for this vtable
2064 VTable
& vtable
= vtables
[metaclassVTableLoc
];
2065 vtable
.superVTable
= baseMetaClassVTableLoc
;
2067 vtable
.dylib
= &dylibSymbols
;
2068 vtable
.fromParentCollection
= false;
2069 vtable
.patched
|= alreadyPatched
;
2070 vtable
.name
= metaclassVTableName
;
2075 ma
->forEachGlobalSymbol(dylibDiags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
2076 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
2077 visitSymbols(symbolName
, n_value
);
2080 ma
->forEachLocalSymbol(dylibDiags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
2081 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
2082 visitSymbols(symbolName
, n_value
);
2087 void VTablePatcher::calculateSymbols() {
2088 for (VTableDylib
& dylib
: dylibs
) {
2089 auto& symbolNames
= collections
[dylib
.cacheLevel
].symbolNames
;
2090 dylib
.ma
->forEachGlobalSymbol(*dylib
.diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
2091 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
2092 symbolNames
[n_value
] = symbolName
;
2094 dylib
.ma
->forEachLocalSymbol(*dylib
.diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
2095 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
2096 symbolNames
[n_value
] = symbolName
;
2101 void VTablePatcher::patchVTables(Diagnostics
& diags
,
2102 std::map
<const uint8_t*, const VTableBindSymbol
>& missingBindLocations
,
2103 AppCacheBuilder::ASLR_Tracker
& aslrTracker
,
2104 uint8_t currentLevel
)
2106 const bool is64
= pointerSize
== 8;
2108 // If we have vtables to patch, then make sure we found the OSMetaClass symbol to patch against
2109 if ( (baseMetaClassVTableLoc
== nullptr) && !vtables
.empty() ) {
2110 diags
.error("Could not find OSMetaClass vtable in kernel binary");
2116 auto calculateVTableEntries
= ^(const uint8_t* vtableLoc
, VTable
& vtable
) {
2117 assert(vtable
.patched
);
2118 logFunc("Calculating vtable: '%s'\n", vtable
.name
.c_str());
2120 // The first entry we want to patch is 2 pointers from the start of the vtable
2121 const uint8_t* relocLoc
= vtableLoc
+ (2 * pointerSize
);
2123 if ( vtable
.fromParentCollection
) {
2124 auto it
= existingCollectionFixupLocations
.find(relocLoc
);
2125 while ( it
!= existingCollectionFixupLocations
.end() ) {
2126 const Fixup
& fixup
= it
->second
;
2127 uint64_t targetVMAddr
= fixup
.targetVMAddr
;
2128 uint16_t diversity
= fixup
.diversity
;
2129 bool hasAddrDiv
= fixup
.hasAddrDiv
;
2130 uint8_t key
= fixup
.key
;
2131 bool hasPointerAuth
= fixup
.hasPointerAuth
;
2132 uint32_t cacheLevel
= fixup
.cacheLevel
;
2133 vtable
.entries
.push_back({ relocLoc
, targetVMAddr
, cacheLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
});
2134 relocLoc
+= pointerSize
;
2135 it
= existingCollectionFixupLocations
.find(relocLoc
);
2138 while ( aslrTracker
.has((void*)relocLoc
) ||
2139 (missingBindLocations
.find(relocLoc
) != missingBindLocations
.end()) ) {
2141 uint16_t diversity
= 0;
2142 bool hasAddrDiv
= false;
2144 bool hasPointerAuth
= false;
2145 uint8_t cacheLevel
= currentLevel
;
2147 if ( aslrTracker
.has((void*)relocLoc
, &cacheLevel
) ) {
2148 hasPointerAuth
= aslrTracker
.hasAuthData((void*)relocLoc
, &diversity
, &hasAddrDiv
, &key
);
2151 uint64_t targetVMAddr
= 0;
2153 uint32_t vmAddr32
= 0;
2154 uint64_t vmAddr64
= 0;
2155 if ( aslrTracker
.hasRebaseTarget32((void*)relocLoc
, &vmAddr32
) ) {
2156 targetVMAddr
= vmAddr32
;
2157 } else if ( aslrTracker
.hasRebaseTarget64((void*)relocLoc
, &vmAddr64
) ) {
2158 targetVMAddr
= vmAddr64
;
2161 targetVMAddr
= *(uint64_t*)relocLoc
;
2163 uint8_t highByte
= 0;
2164 if ( aslrTracker
.hasHigh8((void*)relocLoc
, &highByte
) ) {
2165 uint64_t tbi
= (uint64_t)highByte
<< 56;
2166 targetVMAddr
|= tbi
;
2170 vtable
.entries
.push_back({ relocLoc
, targetVMAddr
, cacheLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
});
2171 relocLoc
+= pointerSize
;
2175 logFunc("Found %d vtable items: '%s'\n", vtable
.entries
.size(), vtable
.name
.c_str());
2178 // Map from MachO to diagnostics to emit for that file
2179 std::unordered_map
<const dyld3::MachOAnalyzer
*, Diagnostics
*> diagsMap
;
2180 for (VTableDylib
& dylib
: dylibs
)
2181 diagsMap
[dylib
.ma
] = dylib
.diags
;
2183 uint32_t numPatchedVTables
= 0;
2184 for (auto& vtableEntry
: vtables
) {
2185 if ( vtableEntry
.second
.patched
) {
2186 calculateVTableEntries(vtableEntry
.first
, vtableEntry
.second
);
2187 ++numPatchedVTables
;
2190 while ( numPatchedVTables
!= vtables
.size() ) {
2191 typedef std::pair
<const uint8_t*, VTable
*> VTableEntry
;
2192 std::vector
<VTableEntry
> toBePatched
;
2193 for (auto& vtableEntry
: vtables
) {
2194 if ( vtableEntry
.second
.patched
)
2196 auto superIt
= vtables
.find(vtableEntry
.second
.superVTable
);
2197 assert(superIt
!= vtables
.end());
2198 if ( !superIt
->second
.patched
)
2200 logFunc("Found unpatched vtable: '%s' with patched superclass '%s'\n",
2201 vtableEntry
.second
.name
.c_str(), superIt
->second
.name
.c_str());
2202 toBePatched
.push_back({ vtableEntry
.first
, &vtableEntry
.second
});
2205 if ( toBePatched
.empty() ) {
2206 // If we can't find anything to patch, then print out what we have left
2207 for (const auto& vtableEntry
: vtables
) {
2208 if ( vtableEntry
.second
.patched
)
2210 auto superIt
= vtables
.find(vtableEntry
.second
.superVTable
);
2211 assert(superIt
!= vtables
.end());
2212 diags
.error("Found unpatched vtable: '%s' with unpatched superclass '%s'\n",
2213 vtableEntry
.second
.name
.c_str(), superIt
->second
.name
.c_str());
2218 for (VTableEntry
& vtableEntry
: toBePatched
) {
2219 VTable
& vtable
= *vtableEntry
.second
;
2221 // We can immediately mark this as patched as then calculateVTableEntries can make
2222 // sure we never ask for vtables which aren't ready yet
2223 vtable
.patched
= true;
2224 ++numPatchedVTables
;
2226 auto superIt
= vtables
.find(vtable
.superVTable
);
2227 logFunc("Processing unpatched vtable: '%s' with patched superclass '%s'\n",
2228 vtable
.name
.c_str(), superIt
->second
.name
.c_str());
2230 calculateVTableEntries(vtableEntry
.first
, vtable
);
2232 const VTable
& supervtable
= superIt
->second
;
2233 if ( vtable
.entries
.size() < supervtable
.entries
.size() ) {
2234 // Try emit the error to a per dylib diagnostic object if we can find one
2235 auto diagIt
= diagsMap
.find(vtable
.ma
);
2236 Diagnostics
* diag
= (diagIt
!= diagsMap
.end()) ? diagIt
->second
: &diags
;
2237 diag
->error("Malformed vtable. Super class '%s' has %lu entries vs subclass '%s' with %lu entries",
2238 supervtable
.name
.c_str(), supervtable
.entries
.size(),
2239 vtable
.name
.c_str(), vtable
.entries
.size());
2243 const std::unordered_map
<const uint8_t*, VTableBindSymbol
>& resolvedBindLocations
= vtable
.dylib
->resolvedBindLocations
;
2244 for (uint64_t entryIndex
= 0; entryIndex
!= supervtable
.entries
.size(); ++entryIndex
) {
2245 logFuncVerbose("Processing entry %lld: super[0x%llx] vs subclass[0x%llx]\n", entryIndex
,
2246 *(uint64_t*)supervtable
.entries
[entryIndex
].location
,
2247 *(uint64_t*)vtable
.entries
[entryIndex
].location
);
2249 VTable::Entry
& vtableEntry
= vtable
.entries
[entryIndex
];
2250 const VTable::Entry
& superVTableEntry
= supervtable
.entries
[entryIndex
];
2252 const uint8_t* patchLoc
= vtableEntry
.location
;
2253 uint64_t targetVMAddr
= superVTableEntry
.targetVMAddr
;
2255 // 1) If the symbol is defined locally, do not patch
2256 // This corresponds to a rebase not a bind, so if we have a match in our bind set
2257 // we were bound to another image, and should see if that bind should be overridden by a
2258 // better vtable patch.
2259 auto resolvedBindIt
= resolvedBindLocations
.find(patchLoc
);
2260 auto unresolvedBindIt
= missingBindLocations
.find(patchLoc
);
2261 if ( (resolvedBindIt
== resolvedBindLocations
.end()) && (unresolvedBindIt
== missingBindLocations
.end()) )
2264 // Find the child and parent symbols, if any
2265 const char* childSymbolName
= nullptr;
2266 const char* parentSymbolName
= nullptr;
2268 if ( resolvedBindIt
!= resolvedBindLocations
.end() ) {
2269 childSymbolName
= resolvedBindIt
->second
.symbolName
.c_str();
2271 assert(unresolvedBindIt
!= missingBindLocations
.end());
2272 childSymbolName
= unresolvedBindIt
->second
.symbolName
.c_str();
2275 auto& symbolNames
= collections
[superVTableEntry
.targetCacheLevel
].symbolNames
;
2276 auto parentNameIt
= symbolNames
.find(superVTableEntry
.targetVMAddr
);
2277 if ( parentNameIt
!= symbolNames
.end() )
2278 parentSymbolName
= parentNameIt
->second
;
2280 // The child entry can be NULL when a locally-defined, non-external
2281 // symbol is stripped. We wouldn't patch this entry anyway, so we just skip it.
2282 if ( childSymbolName
== nullptr ) {
2286 // It's possible for the patched parent entry not to have a symbol
2287 // (e.g. when the definition is inlined). We can't patch this entry no
2288 // matter what, so we'll just skip it and die later if it's a problem
2289 // (which is not likely).
2290 if ( parentSymbolName
== nullptr ) {
2294 logFuncVerbose("Processing entry %lld: super[%s] vs subclass[%s]\n", entryIndex
,
2295 parentSymbolName
, childSymbolName
);
2297 // 2) If the child is a pure virtual function, do not patch.
2298 // In general, we want to proceed with patching when the symbol is
2299 // externally defined because pad slots fall into this category.
2300 // The pure virtual function symbol is special case, as the pure
2301 // virtual property itself overrides the parent's implementation.
2302 if ( !strcmp(childSymbolName
, "___cxa_pure_virtual") ) {
2306 // 3) If the symbols are the same, do not patch
2307 // Note that if the symbol was a missing bind, then we'll still patch
2308 // This is the case where the vtable entry itself was a local symbol
2309 // so we had originally failed to bind to it as it wasn't exported, but it
2310 // has the same name as the parent name
2311 if ( !strcmp(childSymbolName
, parentSymbolName
) && (unresolvedBindIt
== missingBindLocations
.end()) ) {
2316 // FIXME: Implement this
2318 // 4) If the parent vtable entry is a pad slot, and the child does not
2319 // match it, then the child was built against a newer version of the
2320 // libraries, so it is binary-incompatible.
2321 require_action(!kxld_sym_name_is_padslot(parent_entry
->patched
.name
),
2322 finish
, rval
= KERN_FAILURE
;
2323 kxld_log(kKxldLogPatching
, kKxldLogErr
,
2324 kKxldLogParentOutOfDate
,
2325 kxld_demangle(super_vtable
->name
, &demangled_name1
,
2326 &demangled_length1
),
2327 kxld_demangle(vtable
->name
, &demangled_name2
,
2328 &demangled_length2
)));
2331 logFunc("Patching entry '%s' in '%s' to point to '%s' in superclass '%s'\n",
2332 childSymbolName
, vtable
.name
.c_str(), parentSymbolName
, supervtable
.name
.c_str());
2335 *((uint64_t*)patchLoc
) = targetVMAddr
;
2337 *((uint32_t*)patchLoc
) = (uint32_t)targetVMAddr
;
2340 // FIXME: When we support a baseKC, pageableKC, and auxKC, the supervtable cache level
2341 // may no longer be correct here as we may be:
2342 // - patching a vtable in auxKC
2343 // - where the supervtable is in pageableKC
2344 // - but the entry slot points to baseKC
2345 aslrTracker
.add((void*)patchLoc
, superVTableEntry
.targetCacheLevel
);
2347 // Add pointer auth if the super vtable had it
2348 if ( superVTableEntry
.hasPointerAuth
)
2349 aslrTracker
.setAuthData((void*)patchLoc
, superVTableEntry
.diversity
,
2350 superVTableEntry
.hasAddrDiv
, superVTableEntry
.key
);
2352 // Update this vtable entry in case there are any subclasses which then need to use it
2353 // to be patched themselves
2354 vtableEntry
.targetVMAddr
= superVTableEntry
.targetVMAddr
;
2355 vtableEntry
.targetCacheLevel
= superVTableEntry
.targetCacheLevel
;
2356 vtableEntry
.diversity
= superVTableEntry
.diversity
;
2357 vtableEntry
.hasAddrDiv
= superVTableEntry
.hasAddrDiv
;
2358 vtableEntry
.key
= superVTableEntry
.key
;
2359 vtableEntry
.hasPointerAuth
= superVTableEntry
.hasPointerAuth
;
2361 missingBindLocations
.erase(patchLoc
);
2367 typedef std::pair
<uint8_t, uint64_t> CacheOffset
;
2369 struct DylibSymbolLocation
{
2370 const DylibSymbols
* dylibSymbols
;
2371 uint64_t symbolVMAddr
;
2375 struct DylibFixups
{
2376 void processFixups(const std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
2377 const std::unordered_map
<std::string_view
, std::vector
<DylibSymbolLocation
>>& symbolMap
,
2378 const std::string
& kernelID
, const CacheBuilder::ASLR_Tracker
& aslrTracker
);
2381 const dyld3::MachOAnalyzer
* ma
= nullptr;
2382 DylibSymbols
& dylibSymbols
;
2383 Diagnostics
& dylibDiag
;
2384 const std::vector
<std::string
>& dependencies
;
2392 struct BranchStubData
{
2393 CacheOffset targetCacheOffset
;
2394 const void* fixupLoc
;
2395 uint64_t fixupVMOffset
;
2397 std::unordered_map
<const uint8_t*, VTableBindSymbol
> missingBindLocations
;
2398 std::unordered_map
<void*, uint8_t> fixupLocs
;
2399 std::unordered_map
<void*, uint8_t> fixupHigh8s
;
2400 std::unordered_map
<void*, AuthData
> fixupAuths
;
2401 std::vector
<BranchStubData
> branchStubs
;
2404 void DylibFixups::processFixups(const std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
2405 const std::unordered_map
<std::string_view
, std::vector
<DylibSymbolLocation
>>& symbolMap
,
2406 const std::string
& kernelID
, const CacheBuilder::ASLR_Tracker
& aslrTracker
) {
2407 auto& resolvedBindLocations
= dylibSymbols
.resolvedBindLocations
;
2408 const std::string
& dylibID
= dylibSymbols
.dylibName
;
2410 const bool _is64
= true;
2411 const bool isThirdPartyKext
= (dylibID
.find("com.apple") != 0);
2413 // The magic symbol for missing weak imports
2414 const char* missingWeakImportSymbolName
= "_gOSKextUnresolved";
2416 struct SymbolDefinition
{
2417 uint64_t symbolVMAddr
;
2418 uint32_t kernelCollectionLevel
;
2420 auto findDependencyWithSymbol
= [&symbolMap
, &isThirdPartyKext
](const char* symbolName
,
2421 const std::vector
<std::string
>& dependencies
) {
2422 auto symbolMapIt
= symbolMap
.find(symbolName
);
2423 if ( symbolMapIt
== symbolMap
.end() )
2424 return (SymbolDefinition
){ ~0ULL, 0 };
2425 // Find the first dependency in the list
2426 const std::vector
<DylibSymbolLocation
>& dylibSymbols
= symbolMapIt
->second
;
2427 // The massively common case is 1 or 2 definitions of a given symbol, so a basic searhc should be
2429 for (const std::string
& dependency
: dependencies
) {
2430 for (const DylibSymbolLocation
& dylibSymbol
: dylibSymbols
) {
2431 if ( dependency
== dylibSymbol
.dylibSymbols
->dylibName
) {
2432 // If the Apple kext we are linking has a symbol set, and the user is a third-party kext,
2433 // then only allow the third party kext to see symbols in the kext export list, if it has one
2434 const bool isAppleKext
= (dependency
.find("com.apple") == 0);
2435 if ( isThirdPartyKext
&& isAppleKext
&& !dylibSymbol
.isKPI
)
2437 return (SymbolDefinition
){ dylibSymbol
.symbolVMAddr
, dylibSymbol
.dylibSymbols
->dylibLevel
};
2441 return (SymbolDefinition
){ ~0ULL, 0 };
2444 if (ma
->hasChainedFixups()) {
2445 // build array of targets
2447 const VTableBindSymbol bindSymbol
;
2449 uint32_t dylibLevel
;
2450 bool isMissingWeakImport
;
2451 bool isMissingSymbol
;
2453 __block
std::vector
<BindTarget
> bindTargets
;
2454 __block
bool foundMissingWeakImport
= false;
2455 ma
->forEachChainedFixupTarget(dylibDiag
, ^(int libOrdinal
, const char* symbolName
, uint64_t addend
,
2456 bool weakImport
, bool& stop
) {
2457 if ( (libOrdinal
!= BIND_SPECIAL_DYLIB_FLAT_LOOKUP
) && (libOrdinal
!= BIND_SPECIAL_DYLIB_WEAK_LOOKUP
) ) {
2458 dylibDiag
.error("All chained binds should be flat namespace or weak lookups");
2463 if ( addend
!= 0 ) {
2464 dylibDiag
.error("Chained bind addends are not supported right now");
2469 VTableBindSymbol bindSymbol
= { dylibID
, symbolName
};
2470 bool isMissingSymbol
= false;
2472 for (const std::string
& dependencyID
: dependencies
) {
2473 auto depIt
= dylibsToSymbols
.find(dependencyID
);
2474 if (depIt
== dylibsToSymbols
.end()) {
2475 dylibDiag
.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
2476 symbolName
, dependencyID
.c_str());
2481 const DylibSymbols
& dylibSymbols
= depIt
->second
;
2482 auto exportIt
= dylibSymbols
.globals
.find(symbolName
);
2483 if ( exportIt
== dylibSymbols
.globals
.end() )
2486 isMissingSymbol
= false;
2487 bindTargets
.push_back({ bindSymbol
, exportIt
->second
, dylibSymbols
.dylibLevel
, false, isMissingSymbol
});
2491 // If the symbol is weak, and we didn't find it in our listed
2492 // dependencies, then use our own definition
2493 if ( libOrdinal
== BIND_SPECIAL_DYLIB_WEAK_LOOKUP
) {
2494 auto dylibIt
= dylibsToSymbols
.find(dylibID
);
2495 if (dylibIt
== dylibsToSymbols
.end()) {
2496 dylibDiag
.error("Failed to bind weak '%s' as could not find a define in self",
2501 const DylibSymbols
& dylibSymbols
= dylibIt
->second
;
2502 auto exportIt
= dylibSymbols
.globals
.find(symbolName
);
2503 if ( exportIt
!= dylibSymbols
.globals
.end() ) {
2504 isMissingSymbol
= false;
2505 bindTargets
.push_back({ bindSymbol
, exportIt
->second
, dylibSymbols
.dylibLevel
, false, isMissingSymbol
});
2511 // Find _gOSKextUnresolved in the kernel
2512 // Weak imports are not compared against null, but instead against the address of that symbol
2513 auto kernelSymbolsIt
= dylibsToSymbols
.find(kernelID
);
2514 assert(kernelSymbolsIt
!= dylibsToSymbols
.end());
2515 const DylibSymbols
& kernelSymbols
= kernelSymbolsIt
->second
;
2516 auto exportIt
= kernelSymbols
.globals
.find(missingWeakImportSymbolName
);
2517 if (exportIt
!= kernelSymbols
.globals
.end()) {
2518 foundMissingWeakImport
= true;
2519 isMissingSymbol
= false;
2520 bindTargets
.push_back({ bindSymbol
, exportIt
->second
, kernelSymbols
.dylibLevel
, true, isMissingSymbol
});
2523 dylibDiag
.error("Weak bind symbol '%s' not found in kernel", missingWeakImportSymbolName
);
2527 // Store missing binds for later. They may be fixed by vtable patching
2528 isMissingSymbol
= true;
2529 bindTargets
.push_back({ bindSymbol
, 0, 0, false, isMissingSymbol
});
2531 if ( dylibDiag
.hasError() )
2534 if( foundMissingWeakImport
) {
2535 // If we found a missing weak import, then we need to check that the user did
2536 // something like "if ( &foo == &gOSKextUnresolved )"
2537 // If they didn't use gOSKextUnresolved at all, then there's no way they could be doing that check
2538 auto kernelSymbolsIt
= dylibsToSymbols
.find(kernelID
);
2539 assert(kernelSymbolsIt
!= dylibsToSymbols
.end());
2540 const DylibSymbols
& kernelSymbols
= kernelSymbolsIt
->second
;
2541 auto exportIt
= kernelSymbols
.globals
.find(missingWeakImportSymbolName
);
2542 assert(exportIt
!= kernelSymbols
.globals
.end());
2543 bool foundUseOfMagicSymbol
= false;
2544 for (const BindTarget
& bindTarget
: bindTargets
) {
2545 // Skip the missing weak imports
2546 if ( bindTarget
.isMissingWeakImport
|| bindTarget
.isMissingSymbol
)
2548 // Skip anything which isn't the symbol we are looking for
2549 if ( (bindTarget
.dylibLevel
!= 0) && (bindTarget
.vmAddr
!= exportIt
->second
) )
2551 foundUseOfMagicSymbol
= true;
2555 if ( !foundUseOfMagicSymbol
) {
2556 dylibDiag
.error("Has weak references but does not test for them. "
2557 "Test for weak references with OSKextSymbolIsResolved().");
2562 // uint64_t baseAddress = ma->preferredLoadAddress();
2564 ma
->withChainStarts(dylibDiag
, 0, ^(const dyld_chained_starts_in_image
* starts
) {
2565 ma
->forEachFixupInAllChains(dylibDiag
, starts
, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk
* fixupLoc
, const dyld_chained_starts_in_segment
* segInfo
, bool& stop
) {
2566 switch (segInfo
->pointer_format
) {
2567 case DYLD_CHAINED_PTR_64_OFFSET
:
2568 if ( fixupLoc
->generic64
.bind
.bind
) {
2569 uint64_t bindOrdinal
= fixupLoc
->generic64
.bind
.ordinal
;
2570 if ( bindOrdinal
>= bindTargets
.size() ) {
2571 dylibDiag
.error("Bind ordinal %lld out of range %lu", bindOrdinal
, bindTargets
.size());
2576 const BindTarget
& bindTarget
= bindTargets
[bindOrdinal
];
2577 if ( bindTarget
.isMissingSymbol
) {
2578 // Track this missing bind for later
2579 // For now we bind it to null and don't slide it.
2580 fixupLoc
->raw64
= 0;
2581 missingBindLocations
[(const uint8_t*)fixupLoc
] = bindTarget
.bindSymbol
;
2583 fixupLoc
->raw64
= bindTarget
.vmAddr
;
2584 fixupLocs
[fixupLoc
] = bindTarget
.dylibLevel
;
2585 resolvedBindLocations
[(const uint8_t*)fixupLoc
] = bindTarget
.bindSymbol
;
2589 // convert rebase chain entry to raw pointer to target vmaddr
2590 uint64_t targetVMAddr
= fixupLoc
->generic64
.rebase
.target
;
2591 uint64_t sideTableAddr
= 0;
2592 if ( aslrTracker
.hasRebaseTarget64(fixupLoc
, &sideTableAddr
) )
2593 targetVMAddr
= sideTableAddr
;
2594 // store high8 in side table
2595 if ( fixupLoc
->generic64
.rebase
.high8
)
2596 fixupHigh8s
[fixupLoc
] = fixupLoc
->generic64
.rebase
.high8
;
2597 fixupLoc
->raw64
= targetVMAddr
;
2600 case DYLD_CHAINED_PTR_ARM64E_KERNEL
:
2601 if ( fixupLoc
->arm64e
.bind
.bind
) {
2602 uint64_t bindOrdinal
= fixupLoc
->arm64e
.bind
.ordinal
;
2603 if ( bindOrdinal
>= bindTargets
.size() ) {
2604 dylibDiag
.error("Bind ordinal %lld out of range %lu", bindOrdinal
, bindTargets
.size());
2609 const BindTarget
& bindTarget
= bindTargets
[bindOrdinal
];
2610 uint64_t targetVMAddr
= bindTarget
.vmAddr
;
2612 if ( fixupLoc
->arm64e
.authBind
.auth
) {
2613 // store auth data in side table
2614 fixupAuths
[fixupLoc
] = {
2615 (uint16_t)fixupLoc
->arm64e
.authBind
.diversity
,
2616 (bool)fixupLoc
->arm64e
.authBind
.addrDiv
,
2617 (uint8_t)fixupLoc
->arm64e
.authBind
.key
2621 // plain binds can have addend in chain
2622 targetVMAddr
+= fixupLoc
->arm64e
.bind
.addend
;
2624 // change location from a chain ptr into a raw pointer to the target vmaddr
2625 if ( bindTarget
.isMissingSymbol
) {
2626 // Track this missing bind for later
2627 // For now we bind it to null and don't slide it.
2628 fixupLoc
->raw64
= 0;
2629 missingBindLocations
[(const uint8_t*)fixupLoc
] = bindTarget
.bindSymbol
;
2631 fixupLoc
->raw64
= targetVMAddr
;
2632 fixupLocs
[fixupLoc
] = bindTarget
.dylibLevel
;
2633 resolvedBindLocations
[(const uint8_t*)fixupLoc
] = bindTarget
.bindSymbol
;
2637 // convert rebase chain entry to raw pointer to target vmaddr
2638 if ( fixupLoc
->arm64e
.rebase
.auth
) {
2639 // store auth data in side table
2640 fixupAuths
[fixupLoc
] = {
2641 (uint16_t)fixupLoc
->arm64e
.authRebase
.diversity
,
2642 (bool)fixupLoc
->arm64e
.authRebase
.addrDiv
,
2643 (uint8_t)fixupLoc
->arm64e
.authRebase
.key
2645 uint64_t targetVMAddr
= fixupLoc
->arm64e
.authRebase
.target
;
2646 fixupLoc
->raw64
= targetVMAddr
;
2649 uint64_t targetVMAddr
= fixupLoc
->arm64e
.rebase
.target
;
2650 uint64_t sideTableAddr
;
2651 if ( aslrTracker
.hasRebaseTarget64(fixupLoc
, &sideTableAddr
) )
2652 targetVMAddr
= sideTableAddr
;
2653 // store high8 in side table
2654 if ( fixupLoc
->arm64e
.rebase
.high8
)
2655 fixupHigh8s
[fixupLoc
] = fixupLoc
->arm64e
.rebase
.high8
;
2656 fixupLoc
->raw64
= targetVMAddr
;
2661 fprintf(stderr
, "unknown pointer type %d\n", segInfo
->pointer_format
);
2669 // If we have any missing imports, then they should check for the kernel symbol
2670 // Grab a hold of that now if it exists so we can check it later
2671 __block
bool foundUseOfMagicSymbol
= false;
2672 __block
bool foundMissingWeakImport
= false;
2674 const uint64_t loadAddress
= ma
->preferredLoadAddress();
2675 ma
->forEachBind(dylibDiag
, ^(uint64_t runtimeOffset
, int libOrdinal
, uint8_t bindType
,
2676 const char *symbolName
, bool weakImport
, bool lazyBind
, uint64_t addend
, bool &stop
) {
2677 // printf("Bind at 0x%llx to '%s'\n", runtimeOffset, symbolName);
2678 // Kext binds are a flat namespace so walk until we find the symbol we need
2679 bool foundSymbol
= false;
2680 VTableBindSymbol bindSymbol
= { dylibID
, symbolName
};
2681 if (SymbolDefinition symbolDef
= findDependencyWithSymbol(symbolName
, dependencies
); symbolDef
.symbolVMAddr
!= ~0ULL) {
2682 // Set the bind to the target address since we found it
2683 uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2684 if ( bindType
== BIND_TYPE_POINTER
) {
2686 *((uint64_t*)fixupLoc
) = symbolDef
.symbolVMAddr
;
2688 *((uint32_t*)fixupLoc
) = (uint32_t)symbolDef
.symbolVMAddr
;
2690 // Only track regular fixups for ASLR, not branch fixups
2691 fixupLocs
[fixupLoc
] = symbolDef
.kernelCollectionLevel
;
2692 resolvedBindLocations
[(const uint8_t*)fixupLoc
] = bindSymbol
;
2693 } else if ( bindType
== BIND_TYPE_TEXT_PCREL32
) {
2694 // The value to store is the difference between the bind target
2695 // and the value of the PC after this instruction
2696 uint64_t targetAddress
= 0;
2697 if ( dylibSymbols
.dylibLevel
!= symbolDef
.kernelCollectionLevel
) {
2698 // Record this for later as we want to create stubs serially
2699 CacheOffset targetCacheOffset
= { symbolDef
.kernelCollectionLevel
, symbolDef
.symbolVMAddr
};
2700 branchStubs
.emplace_back((BranchStubData
){
2701 .targetCacheOffset
= targetCacheOffset
,
2702 .fixupLoc
= fixupLoc
,
2703 .fixupVMOffset
= runtimeOffset
2706 targetAddress
= symbolDef
.symbolVMAddr
;
2707 uint64_t diffValue
= targetAddress
- (loadAddress
+ runtimeOffset
+ 4);
2708 *((uint32_t*)fixupLoc
) = (uint32_t)diffValue
;
2711 dylibDiag
.error("Unexpected bind type: %d", bindType
);
2719 if ( foundSymbol
&& !foundUseOfMagicSymbol
) {
2720 foundUseOfMagicSymbol
= (strcmp(symbolName
, missingWeakImportSymbolName
) == 0);
2724 for (const std::string
& dependencyID
: dependencies
) {
2725 auto depIt
= dylibsToSymbols
.find(dependencyID
);
2726 if (depIt
== dylibsToSymbols
.end()) {
2727 dylibDiag
.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
2728 symbolName
, dependencyID
.c_str());
2733 const DylibSymbols
& dylibSymbols
= depIt
->second
;
2734 auto exportIt
= dylibSymbols
.globals
.find(symbolName
);
2735 if ( exportIt
== dylibSymbols
.globals
.end() )
2737 findDependencyWithSymbol(symbolName
, dependencies
);
2742 // If the symbol is weak, and we didn't find it in our listed
2743 // dependencies, then use our own definition
2744 if ( !foundSymbol
&& (libOrdinal
== BIND_SPECIAL_DYLIB_WEAK_LOOKUP
) ) {
2745 auto dylibIt
= dylibsToSymbols
.find(dylibID
);
2746 if (dylibIt
== dylibsToSymbols
.end()) {
2747 dylibDiag
.error("Failed to bind weak '%s' as could not find a define in self",
2753 const DylibSymbols
& dylibSymbols
= dylibIt
->second
;
2754 auto exportIt
= dylibSymbols
.globals
.find(symbolName
);
2755 if ( exportIt
!= dylibSymbols
.globals
.end() ) {
2756 // Set the bind to the target address since we found it
2757 uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2758 if ( bindType
== BIND_TYPE_POINTER
) {
2760 *((uint64_t*)fixupLoc
) = exportIt
->second
;
2762 *((uint32_t*)fixupLoc
) = (uint32_t)exportIt
->second
;
2764 // Only track regular fixups for ASLR, not branch fixups
2765 fixupLocs
[fixupLoc
] = dylibSymbols
.dylibLevel
;
2766 resolvedBindLocations
[(const uint8_t*)fixupLoc
] = bindSymbol
;
2767 } else if ( bindType
== BIND_TYPE_TEXT_PCREL32
) {
2768 // We should never have a branch to a weak bind as we should have had a GOT for these
2769 dylibDiag
.error("Unexpected weak bind type: %d", bindType
);
2773 dylibDiag
.error("Unexpected bind type: %d", bindType
);
2782 if ( !foundSymbol
&& weakImport
) {
2783 if ( bindType
!= BIND_TYPE_POINTER
) {
2784 dylibDiag
.error("Unexpected bind type: %d", bindType
);
2788 // Find _gOSKextUnresolved in the kernel
2789 // Weak imports are not compared against null, but instead against the address of that symbol
2790 auto kernelSymbolsIt
= dylibsToSymbols
.find(kernelID
);
2791 assert(kernelSymbolsIt
!= dylibsToSymbols
.end());
2792 const DylibSymbols
& kernelSymbols
= kernelSymbolsIt
->second
;
2793 auto exportIt
= kernelSymbols
.globals
.find(missingWeakImportSymbolName
);
2794 if (exportIt
!= kernelSymbols
.globals
.end()) {
2795 foundMissingWeakImport
= true;
2797 uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2799 *((uint64_t*)fixupLoc
) = exportIt
->second
;
2801 *((uint32_t*)fixupLoc
) = (uint32_t)exportIt
->second
;
2803 // Only track regular fixups for ASLR, not branch fixups
2804 fixupLocs
[fixupLoc
] = kernelSymbols
.dylibLevel
;
2807 dylibDiag
.error("Weak bind symbol '%s' not found in kernel", missingWeakImportSymbolName
);
2811 if ( !foundSymbol
) {
2812 // Store missing binds for later. They may be fixed by vtable patching
2813 const uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2814 missingBindLocations
[fixupLoc
] = bindSymbol
;
2816 }, ^(const char *symbolName
) {
2817 dylibDiag
.error("Strong binds are not supported right now");
2820 if ( foundMissingWeakImport
&& !foundUseOfMagicSymbol
) {
2821 dylibDiag
.error("Has weak references but does not test for them. "
2822 "Test for weak references with OSKextSymbolIsResolved().");
2826 ma
->forEachRebase(dylibDiag
, false, ^(uint64_t runtimeOffset
, bool &stop
) {
2827 uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2828 fixupLocs
[fixupLoc
] = (uint8_t)~0U;
2832 // A helper to automatically call CFRelease when we go out of scope
2833 struct AutoReleaseTypeRef
{
2834 AutoReleaseTypeRef() = default;
2835 ~AutoReleaseTypeRef() {
2836 if ( ref
!= nullptr ) {
2840 void setRef(CFTypeRef typeRef
) {
2841 assert(ref
== nullptr);
2845 CFTypeRef ref
= nullptr;
2848 static std::unique_ptr
<std::unordered_set
<std::string
>> getKPI(Diagnostics
& diags
, const dyld3::MachOAnalyzer
* ma
,
2849 std::string_view dylibID
) {
2850 bool isAppleKext
= (dylibID
.find("com.apple") == 0);
2854 __block
std::list
<std::string
> nonASCIIStrings
;
2855 auto getString
= ^(Diagnostics
& diags
, CFStringRef symbolNameRef
) {
2856 const char* symbolName
= CFStringGetCStringPtr(symbolNameRef
, kCFStringEncodingUTF8
);
2857 if ( symbolName
!= nullptr )
2860 CFIndex len
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef
), kCFStringEncodingUTF8
);
2861 char buffer
[len
+ 1];
2862 if ( !CFStringGetCString(symbolNameRef
, buffer
, len
, kCFStringEncodingUTF8
) ) {
2863 diags
.error("Could not convert string to ASCII");
2864 return (const char*)nullptr;
2867 nonASCIIStrings
.push_back(buffer
);
2868 return nonASCIIStrings
.back().c_str();
2871 uint64_t symbolSetsSize
= 0;
2872 const void* symbolSetsContent
= ma
->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize
);
2873 if ( symbolSetsContent
== nullptr )
2876 AutoReleaseTypeRef dataRefReleaser
;
2877 AutoReleaseTypeRef plistRefReleaser
;
2879 std::unordered_set
<std::string
> symbols
;
2880 CFDataRef dataRef
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
, (const uint8_t*)symbolSetsContent
, symbolSetsSize
, kCFAllocatorNull
);
2881 if ( dataRef
== nullptr ) {
2882 diags
.error("Could not create data ref for kpi");
2885 dataRefReleaser
.setRef(dataRef
);
2887 CFErrorRef errorRef
= nullptr;
2888 CFPropertyListRef plistRef
= CFPropertyListCreateWithData(kCFAllocatorDefault
, dataRef
, kCFPropertyListImmutable
, nullptr, &errorRef
);
2889 if (errorRef
!= nullptr) {
2890 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
2891 diags
.error("Could not load plist because :%s", CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
2892 CFRelease(errorRef
);
2895 if ( plistRef
== nullptr ) {
2896 diags
.error("Could not create plist ref for kpi");
2899 plistRefReleaser
.setRef(plistRef
);
2901 if ( CFGetTypeID(plistRef
) != CFDictionaryGetTypeID() ) {
2902 diags
.error("kpi plist should be a dictionary");
2906 CFDictionaryRef symbolSetsDictRef
= (CFDictionaryRef
)plistRef
;
2908 // CFBundleIdentifier
2909 CFStringRef bundleIDRef
= (CFStringRef
)CFDictionaryGetValue(symbolSetsDictRef
, CFSTR("CFBundleIdentifier"));
2910 if ( (bundleIDRef
== nullptr) || (CFGetTypeID(bundleIDRef
) != CFStringGetTypeID()) ) {
2911 diags
.error("kpi bundle ID should be a string");
2915 const char* bundleID
= getString(diags
, bundleIDRef
);
2916 if ( bundleID
== nullptr )
2919 if ( dylibID
!= bundleID
) {
2920 diags
.error("kpi bundle ID doesn't match kext");
2924 CFArrayRef symbolsArrayRef
= (CFArrayRef
)CFDictionaryGetValue(symbolSetsDictRef
, CFSTR("Symbols"));
2925 if ( symbolsArrayRef
!= nullptr ) {
2926 if ( CFGetTypeID(symbolsArrayRef
) != CFArrayGetTypeID() ) {
2927 diags
.error("Symbols value should be an array");
2930 for (CFIndex symbolSetIndex
= 0; symbolSetIndex
!= CFArrayGetCount(symbolsArrayRef
); ++symbolSetIndex
) {
2931 CFStringRef symbolNameRef
= (CFStringRef
)CFArrayGetValueAtIndex(symbolsArrayRef
, symbolSetIndex
);
2932 if ( (symbolNameRef
== nullptr) || (CFGetTypeID(symbolNameRef
) != CFStringGetTypeID()) ) {
2933 diags
.error("Symbol name should be a string");
2937 const char* symbolName
= getString(diags
, symbolNameRef
);
2938 if ( symbolName
== nullptr )
2940 symbols
.insert(symbolName
);
2944 return std::make_unique
<std::unordered_set
<std::string
>>(std::move(symbols
));
2947 void AppCacheBuilder::processFixups()
2949 auto dylibsToSymbolsOwner
= std::make_unique
<std::map
<std::string
, DylibSymbols
>>();
2950 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
= *dylibsToSymbolsOwner
.get();
2952 auto vtablePatcherOwner
= std::make_unique
<VTablePatcher
>(numFixupLevels
);
2953 VTablePatcher
& vtablePatcher
= *vtablePatcherOwner
.get();
2955 const uint32_t kernelLevel
= 0;
2956 uint8_t currentLevel
= getCurrentFixupLevel();
2958 // Keep track of missing binds until later. They may be "resolved" by vtable patching
2959 std::map
<const uint8_t*, const VTableBindSymbol
> missingBindLocations
;
2961 __block
std::string kernelID
;
2962 __block
const dyld3::MachOAnalyzer
* kernelMA
= nullptr;
2963 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::kernel
) {
2964 kernelMA
= getKernelStaticExecutableFromCache();
2965 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
2966 DylibStripMode stripMode
, const std::vector
<std::string
> &dependencies
,
2967 Diagnostics
& dylibDiag
,
2969 if ( ma
== kernelMA
) {
2974 assert(!kernelID
.empty());
2976 assert(existingKernelCollection
!= nullptr);
2977 existingKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *name
, bool &stop
) {
2978 if ( ma
->isStaticExecutable() ) {
2983 if ( kernelMA
== nullptr ) {
2984 _diagnostics
.error("Could not find kernel in kernel collection");
2989 auto getGlobals
= [](Diagnostics
& diags
, const dyld3::MachOAnalyzer
*ma
) -> std::map
<std::string_view
, uint64_t> {
2990 // Note we don't put __block on the variable directly as then it gets copied in to the return value
2991 std::map
<std::string_view
, uint64_t> exports
;
2992 __block
std::map
<std::string_view
, uint64_t>& exportsRef
= exports
;
2993 ma
->forEachGlobalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
,
2994 uint8_t n_type
, uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
2995 exportsRef
[symbolName
] = n_value
;
3000 auto getLocals
= [](Diagnostics
& diags
, const dyld3::MachOAnalyzer
*ma
) -> std::map
<std::string_view
, uint64_t> {
3001 // Note we don't put __block on the variable directly as then it gets copied in to the return value
3002 std::map
<std::string_view
, uint64_t> exports
;
3003 __block
std::map
<std::string_view
, uint64_t>& exportsRef
= exports
;
3004 ma
->forEachLocalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
,
3005 uint8_t n_type
, uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
3006 exportsRef
[symbolName
] = n_value
;
3011 dylibsToSymbols
[kernelID
] = {
3012 getGlobals(_diagnostics
, kernelMA
),
3013 getLocals(_diagnostics
, kernelMA
),
3016 std::string(kernelID
)
3019 // Add all the codeless kext's as kext's can list them as dependencies
3020 // Note we add placeholders here which can be legitimately replaced by symbol sets
3021 for (const InputDylib
& dylib
: codelessKexts
) {
3022 dylibsToSymbols
[dylib
.dylibID
] = { };
3025 // Similarly, add placeholders for codeless kexts in the baseKC
3026 if ( existingKernelCollection
!= nullptr ) {
3027 existingKernelCollection
->forEachPrelinkInfoLibrary(_diagnostics
,
3028 ^(const char *bundleName
, const char* relativePath
,
3029 const std::vector
<const char *> &deps
) {
3030 dylibsToSymbols
[bundleName
] = { };
3034 // And placeholders for codeless kexts in the pageableKC
3035 if ( pageableKernelCollection
!= nullptr ) {
3036 pageableKernelCollection
->forEachPrelinkInfoLibrary(_diagnostics
,
3037 ^(const char *bundleName
, const char* relativePath
,
3038 const std::vector
<const char *> &deps
) {
3039 dylibsToSymbols
[bundleName
] = { };
3043 // Get the symbol sets
3044 AutoReleaseTypeRef dataRefReleaser
;
3045 AutoReleaseTypeRef plistRefReleaser
;
3047 __block
std::list
<std::string
> nonASCIIStrings
;
3048 auto getString
= ^(Diagnostics
& diags
, CFStringRef symbolNameRef
) {
3049 const char* symbolName
= CFStringGetCStringPtr(symbolNameRef
, kCFStringEncodingUTF8
);
3050 if ( symbolName
!= nullptr )
3053 CFIndex len
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef
), kCFStringEncodingUTF8
);
3054 char buffer
[len
+ 1];
3055 if ( !CFStringGetCString(symbolNameRef
, buffer
, len
, kCFStringEncodingUTF8
) ) {
3056 diags
.error("Could not convert string to ASCII");
3057 return (const char*)nullptr;
3060 nonASCIIStrings
.push_back(buffer
);
3061 return nonASCIIStrings
.back().c_str();
3064 uint64_t symbolSetsSize
= 0;
3065 const void* symbolSetsContent
= kernelMA
->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize
);
3066 if ( symbolSetsContent
!= nullptr ) {
3067 const DylibSymbols
& kernelSymbols
= dylibsToSymbols
[kernelID
];
3069 CFDataRef dataRef
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
, (const uint8_t*)symbolSetsContent
, symbolSetsSize
, kCFAllocatorNull
);
3070 if ( dataRef
== nullptr ) {
3071 _diagnostics
.error("Could not create data ref for symbol sets");
3074 dataRefReleaser
.setRef(dataRef
);
3076 CFErrorRef errorRef
= nullptr;
3077 CFPropertyListRef plistRef
= CFPropertyListCreateWithData(kCFAllocatorDefault
, dataRef
, kCFPropertyListImmutable
, nullptr, &errorRef
);
3078 if (errorRef
!= nullptr) {
3079 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
3080 _diagnostics
.error("Could not load plist because :%s",
3081 CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
3082 CFRelease(errorRef
);
3085 if ( plistRef
== nullptr ) {
3086 _diagnostics
.error("Could not create plist ref for symbol sets");
3089 plistRefReleaser
.setRef(plistRef
);
3091 if ( CFGetTypeID(plistRef
) != CFDictionaryGetTypeID() ) {
3092 _diagnostics
.error("Symbol set plist should be a dictionary");
3095 CFDictionaryRef symbolSetsDictRef
= (CFDictionaryRef
)plistRef
;
3096 CFArrayRef symbolSetArrayRef
= (CFArrayRef
)CFDictionaryGetValue(symbolSetsDictRef
, CFSTR("SymbolsSets"));
3097 if ( symbolSetArrayRef
!= nullptr ) {
3098 if ( CFGetTypeID(symbolSetArrayRef
) != CFArrayGetTypeID() ) {
3099 _diagnostics
.error("SymbolsSets value should be an array");
3102 for (CFIndex symbolSetIndex
= 0; symbolSetIndex
!= CFArrayGetCount(symbolSetArrayRef
); ++symbolSetIndex
) {
3103 CFDictionaryRef symbolSetDictRef
= (CFDictionaryRef
)CFArrayGetValueAtIndex(symbolSetArrayRef
, symbolSetIndex
);
3104 if ( CFGetTypeID(symbolSetDictRef
) != CFDictionaryGetTypeID() ) {
3105 _diagnostics
.error("Symbol set element should be a dictionary");
3109 // CFBundleIdentifier
3110 CFStringRef bundleIDRef
= (CFStringRef
)CFDictionaryGetValue(symbolSetDictRef
, CFSTR("CFBundleIdentifier"));
3111 if ( (bundleIDRef
== nullptr) || (CFGetTypeID(bundleIDRef
) != CFStringGetTypeID()) ) {
3112 _diagnostics
.error("Symbol set bundle ID should be a string");
3117 CFArrayRef symbolsArrayRef
= (CFArrayRef
)CFDictionaryGetValue(symbolSetDictRef
, CFSTR("Symbols"));
3118 if ( (symbolsArrayRef
== nullptr) || (CFGetTypeID(symbolsArrayRef
) != CFArrayGetTypeID()) ) {
3119 _diagnostics
.error("Symbol set symbols should be an array");
3123 std::map
<std::string_view
, uint64_t> symbolSetGlobals
;
3124 std::map
<std::string_view
, uint64_t> symbolSetLocals
;
3125 for (CFIndex symbolIndex
= 0; symbolIndex
!= CFArrayGetCount(symbolsArrayRef
); ++symbolIndex
) {
3126 CFDictionaryRef symbolDictRef
= (CFDictionaryRef
)CFArrayGetValueAtIndex(symbolsArrayRef
, symbolIndex
);
3127 if ( CFGetTypeID(symbolDictRef
) != CFDictionaryGetTypeID() ) {
3128 _diagnostics
.error("Symbols array element should be a dictionary");
3133 CFStringRef symbolPrefixRef
= (CFStringRef
)CFDictionaryGetValue(symbolDictRef
, CFSTR("SymbolPrefix"));
3134 if ( symbolPrefixRef
!= nullptr ) {
3135 if ( CFGetTypeID(symbolPrefixRef
) != CFStringGetTypeID() ) {
3136 _diagnostics
.error("Symbol prefix should be a string");
3140 const char* symbolPrefix
= getString(_diagnostics
, symbolPrefixRef
);
3141 if ( symbolPrefix
== nullptr )
3143 size_t symbolPrefixLen
= strlen(symbolPrefix
);
3145 // FIXME: Brute force might not be the best thing here
3146 for (std::pair
<std::string_view
, uint64_t> kernelGlobal
: kernelSymbols
.globals
) {
3147 if ( strncmp(kernelGlobal
.first
.data(), symbolPrefix
, symbolPrefixLen
) == 0 ) {
3148 symbolSetGlobals
[kernelGlobal
.first
] = kernelGlobal
.second
;
3151 for (std::pair
<std::string_view
, uint64_t> kernelLocal
: kernelSymbols
.locals
) {
3152 if ( strncmp(kernelLocal
.first
.data(), symbolPrefix
, symbolPrefixLen
) == 0 ) {
3153 symbolSetLocals
[kernelLocal
.first
] = kernelLocal
.second
;
3160 CFStringRef symbolNameRef
= (CFStringRef
)CFDictionaryGetValue(symbolDictRef
, CFSTR("SymbolName"));
3161 if ( (symbolNameRef
== nullptr) || (CFGetTypeID(symbolNameRef
) != CFStringGetTypeID()) ) {
3162 _diagnostics
.error("Symbol name should be a string");
3166 // AliasTarget [Optional]
3167 CFStringRef aliasTargetRef
= (CFStringRef
)CFDictionaryGetValue(symbolDictRef
, CFSTR("AliasTarget"));
3168 if ( aliasTargetRef
== nullptr ) {
3170 const char* symbolName
= getString(_diagnostics
, symbolNameRef
);
3171 if ( symbolName
== nullptr )
3174 // Find the symbol in xnu
3175 auto globalIt
= kernelSymbols
.globals
.find(symbolName
);
3176 if (globalIt
!= kernelSymbols
.globals
.end()) {
3177 symbolSetGlobals
[symbolName
] = globalIt
->second
;
3180 auto localIt
= kernelSymbols
.locals
.find(symbolName
);
3181 if (localIt
!= kernelSymbols
.locals
.end()) {
3182 symbolSetLocals
[symbolName
] = localIt
->second
;
3186 if ( CFGetTypeID(aliasTargetRef
) != CFStringGetTypeID() ) {
3187 _diagnostics
.error("Alias should be a string");
3191 const char* symbolName
= getString(_diagnostics
, symbolNameRef
);
3192 if ( symbolName
== nullptr )
3194 const char* aliasTargetName
= getString(_diagnostics
, aliasTargetRef
);
3195 if ( aliasTargetName
== nullptr )
3198 // Find the alias symbol in xnu
3199 auto globalIt
= kernelSymbols
.globals
.find(aliasTargetName
);
3200 if (globalIt
!= kernelSymbols
.globals
.end()) {
3201 symbolSetGlobals
[symbolName
] = globalIt
->second
;
3203 _diagnostics
.error("Alias '%s' not found in kernel", aliasTargetName
);
3207 auto localIt
= kernelSymbols
.locals
.find(aliasTargetName
);
3208 if (localIt
!= kernelSymbols
.locals
.end()) {
3209 symbolSetLocals
[symbolName
] = localIt
->second
;
3211 // This is not an error, as aliases from symbol sets from the kernel
3212 // are only for vtable patching, not general binding
3216 const char* dylibID
= getString(_diagnostics
, bundleIDRef
);
3217 if ( dylibID
== nullptr )
3220 // HACK: kxld aliases __ZN15OSMetaClassBase25_RESERVEDOSMetaClassBase3Ev to __ZN15OSMetaClassBase8DispatchE5IORPC
3221 auto metaclassHackIt
= symbolSetGlobals
.find("__ZN15OSMetaClassBase8DispatchE5IORPC");
3222 if ( metaclassHackIt
!= symbolSetGlobals
.end() )
3223 symbolSetGlobals
["__ZN15OSMetaClassBase25_RESERVEDOSMetaClassBase3Ev"] = metaclassHackIt
->second
;
3224 dylibsToSymbols
[dylibID
] = {
3225 std::move(symbolSetGlobals
),
3226 std::move(symbolSetLocals
),
3235 auto processBinary
= ^(Diagnostics
& dylibDiags
, const dyld3::MachOAnalyzer
*ma
,
3236 const std::string
& dylibID
, uint32_t dylibLevel
) {
3237 // We dont support export trie's for now
3238 uint32_t unusedExportTrieOffset
= 0;
3239 uint32_t unusedExportTrieSize
= 0;
3240 if (ma
->hasExportTrie(unusedExportTrieOffset
, unusedExportTrieSize
))
3243 // Already done the kernel before.
3244 if ( ma
== kernelMA
)
3248 dylibsToSymbols
[dylibID
] = {
3249 getGlobals(dylibDiags
, ma
),
3250 getLocals(dylibDiags
, ma
),
3251 getKPI(dylibDiags
, ma
, dylibID
),
3256 // Process binary symbols in parallel
3259 const dyld3::MachOAnalyzer
* ma
= nullptr;
3260 Diagnostics
& dylibDiag
;
3261 const std::string
& dylibID
;
3264 __block
std::vector
<DylibData
> dylibDatas
;
3265 dylibDatas
.reserve(sortedDylibs
.size());
3266 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
, DylibStripMode stripMode
,
3267 const std::vector
<std::string
> &dependencies
, Diagnostics
&dylibDiag
, bool &stop
) {
3268 // Already done the kernel before.
3269 if ( ma
== kernelMA
)
3272 // Make space for all the map entries so that we know they are there when we write their values later
3273 dylibsToSymbols
[dylibID
] = { };
3274 dylibDatas
.emplace_back((DylibData
){ ma
, dylibDiag
, dylibID
});
3277 dispatch_apply(dylibDatas
.size(), DISPATCH_APPLY_AUTO
, ^(size_t index
) {
3278 DylibData
& dylibData
= dylibDatas
[index
];
3279 processBinary(dylibData
.dylibDiag
, dylibData
.ma
, dylibData
.dylibID
, currentLevel
);
3283 // Add exports from the kernel collection if we have it
3284 if ( existingKernelCollection
!= nullptr ) {
3285 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::kernel
);
3286 existingKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *name
, bool &stop
) {
3287 processBinary(_diagnostics
, ma
, name
, fixupLevel
);
3291 // Add exports from the pageable collection if we have it
3292 if ( pageableKernelCollection
!= nullptr ) {
3293 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::pageableKC
);
3294 pageableKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *name
, bool &stop
) {
3295 processBinary(_diagnostics
, ma
, name
, fixupLevel
);
3299 // Map from an offset in to a KC to a synthesized stub which branches to that offset
3300 struct CacheOffsetHash
3302 size_t operator() (const CacheOffset
& cacheOffset
) const
3304 return std::hash
<uint32_t>{}(cacheOffset
.first
) ^ std::hash
<uint64_t>{}(cacheOffset
.second
);
3307 std::unordered_map
<CacheOffset
, uint64_t, CacheOffsetHash
> branchStubs
;
3309 // Clear the branch regions sizes so that we fill them up to their buffer sizes as we go
3310 branchStubsRegion
.sizeInUse
= 0;
3311 branchGOTsRegion
.sizeInUse
= 0;
3314 // Map from each symbol to the list of dylibs which export it
3315 auto symbolMapOwner
= std::make_unique
<std::unordered_map
<std::string_view
, std::vector
<DylibSymbolLocation
>>>();
3316 __block
auto& symbolMap
= *symbolMapOwner
.get();
3317 for (const auto& dylibNameAndSymbols
: dylibsToSymbols
) {
3318 const DylibSymbols
& dylibSymbols
= dylibNameAndSymbols
.second
;
3319 for (const auto& symbolNameAndAddress
: dylibSymbols
.globals
) {
3320 // By default, everything i KPI, ie, can be linked by third parties.
3321 // If a symbol is is provided, even an empty one, then it can override this
3323 if ( dylibSymbols
.dylibName
== "com.apple.kpi.private" ) {
3324 // com.apple.kpi.private is always hidden from third parties. They shouldn't even list it as a dependency
3326 } else if ( dylibSymbols
.kpiSymbols
) {
3327 const std::unordered_set
<std::string
>* kpiSymbols
= dylibSymbols
.kpiSymbols
.get();
3328 if ( kpiSymbols
->count(symbolNameAndAddress
.first
.data()) == 0 )
3331 symbolMap
[symbolNameAndAddress
.first
].push_back({ &dylibSymbols
, symbolNameAndAddress
.second
, isKPI
});
3335 auto dylibFixupsOwner
= std::make_unique
<std::vector
<DylibFixups
>>();
3336 __block
auto& dylibFixups
= *dylibFixupsOwner
.get();
3337 dylibFixups
.reserve(sortedDylibs
.size());
3338 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
, DylibStripMode stripMode
,
3339 const std::vector
<std::string
> &dependencies
, Diagnostics
&dylibDiag
, bool &stop
) {
3341 auto dylibSymbolsIt
= dylibsToSymbols
.find(dylibID
);
3342 assert(dylibSymbolsIt
!= dylibsToSymbols
.end());
3344 dylibFixups
.emplace_back((DylibFixups
){
3346 .dylibSymbols
= dylibSymbolsIt
->second
,
3347 .dylibDiag
= dylibDiag
,
3348 .dependencies
= dependencies
3352 dispatch_apply(dylibFixups
.size(), DISPATCH_APPLY_AUTO
, ^(size_t index
) {
3353 DylibFixups
& dylibFixup
= dylibFixups
[index
];
3354 dylibFixup
.processFixups(dylibsToSymbols
, symbolMap
, kernelID
, _aslrTracker
);
3357 // Merge all the dylib results in serial
3358 for (DylibFixups
& dylibFixup
: dylibFixups
) {
3360 if ( dylibFixup
.dylibDiag
.hasError() ) {
3361 if ( !_diagnostics
.hasError() ) {
3362 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
3367 if ( !dylibFixup
.missingBindLocations
.empty() ) {
3368 missingBindLocations
.insert(dylibFixup
.missingBindLocations
.begin(),
3369 dylibFixup
.missingBindLocations
.end());
3372 if ( !dylibFixup
.fixupLocs
.empty() ) {
3373 for (auto fixupLocAndLevel
: dylibFixup
.fixupLocs
) {
3374 _aslrTracker
.add(fixupLocAndLevel
.first
, fixupLocAndLevel
.second
);
3378 if ( !dylibFixup
.fixupHigh8s
.empty() ) {
3379 for (auto fixupLocAndHigh8
: dylibFixup
.fixupHigh8s
) {
3380 _aslrTracker
.setHigh8(fixupLocAndHigh8
.first
, fixupLocAndHigh8
.second
);
3384 if ( !dylibFixup
.fixupAuths
.empty() ) {
3385 for (auto fixupLocAndAuth
: dylibFixup
.fixupAuths
) {
3386 _aslrTracker
.setAuthData(fixupLocAndAuth
.first
, fixupLocAndAuth
.second
.diversity
,
3387 fixupLocAndAuth
.second
.addrDiv
, fixupLocAndAuth
.second
.key
);
3391 // Emit branch stubs
3392 const uint64_t loadAddress
= dylibFixup
.ma
->preferredLoadAddress();
3393 for (const DylibFixups::BranchStubData
& branchData
: dylibFixup
.branchStubs
) {
3394 // Branching from the auxKC to baseKC. ld64 doesn't emit a stub in x86_64 kexts
3395 // so we need to synthesize one now
3396 uint64_t targetAddress
= 0;
3397 const CacheOffset
& targetCacheOffset
= branchData
.targetCacheOffset
;
3398 auto itAndInserted
= branchStubs
.insert({ targetCacheOffset
, 0 });
3399 if ( itAndInserted
.second
) {
3400 // We inserted the branch location, so we need to create new stubs and GOTs
3401 if ( branchStubsRegion
.sizeInUse
== branchStubsRegion
.bufferSize
) {
3402 _diagnostics
.error("Overflow in branch stubs region");
3405 if ( branchGOTsRegion
.sizeInUse
== branchGOTsRegion
.bufferSize
) {
3406 _diagnostics
.error("Overflow in branch GOTs region");
3409 uint64_t stubAddress
= branchStubsRegion
.unslidLoadAddress
+ branchStubsRegion
.sizeInUse
;
3410 uint8_t* stubBuffer
= branchStubsRegion
.buffer
+ branchStubsRegion
.sizeInUse
;
3411 uint64_t gotAddress
= branchGOTsRegion
.unslidLoadAddress
+ branchGOTsRegion
.sizeInUse
;
3412 uint8_t* gotBuffer
= branchGOTsRegion
.buffer
+ branchGOTsRegion
.sizeInUse
;
3415 // ff 25 aa bb cc dd jmpq *0xddccbbaa(%rip)
3416 uint64_t diffValue
= gotAddress
- (stubAddress
+ 6);
3417 stubBuffer
[0] = 0xFF;
3418 stubBuffer
[1] = 0x25;
3419 memcpy(&stubBuffer
[2], &diffValue
, sizeof(uint32_t));
3421 // And write the GOT
3422 uint8_t symbolCacheLevel
= targetCacheOffset
.first
;
3423 uint64_t symbolVMAddr
= targetCacheOffset
.second
;
3425 *((uint64_t*)gotBuffer
) = symbolVMAddr
;
3427 *((uint32_t*)gotBuffer
) = (uint32_t)symbolVMAddr
;
3428 _aslrTracker
.add(gotBuffer
, symbolCacheLevel
);
3430 branchStubsRegion
.sizeInUse
+= 6;
3431 branchGOTsRegion
.sizeInUse
+= 8;
3432 targetAddress
= stubAddress
;
3433 itAndInserted
.first
->second
= targetAddress
;
3435 // The stub already existed, so use it
3436 targetAddress
= itAndInserted
.first
->second
;
3438 uint64_t diffValue
= targetAddress
- (loadAddress
+ branchData
.fixupVMOffset
+ 4);
3439 *((uint32_t*)branchData
.fixupLoc
) = (uint32_t)diffValue
;
3443 // FIXME: We could move symbolOwner and dylibFixupsOwner to a worker thread to be destroyed
3446 // Now that we've processes all rebases/binds, patch all the vtables
3448 // Add all the collections to the vtable patcher
3449 if ( existingKernelCollection
!= nullptr ) {
3450 // The baseKC for x86_64 has __HIB mapped first , so we need to get either the __DATA or __TEXT depending on what is earliest
3451 // The kernel base address is still __TEXT, even if __DATA or __HIB is mapped prior to that.
3452 // The loader may have loaded something before __TEXT, but the existingKernelCollection pointer still corresponds to __TEXT
3453 __block
uint64_t baseAddress
= ~0ULL;
3454 existingKernelCollection
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
& info
, bool& stop
) {
3455 baseAddress
= std::min(baseAddress
, info
.vmAddr
);
3458 // The existing collection is a pointer to the mach_header for the baseKC, but __HIB and other segments may be before that
3459 // Offset those here
3460 uint64_t basePointerOffset
= existingKernelCollection
->preferredLoadAddress() - baseAddress
;
3461 const uint8_t* basePointer
= (uint8_t*)existingKernelCollection
- basePointerOffset
;
3463 vtablePatcher
.addKernelCollection(existingKernelCollection
, Options::AppCacheKind::kernel
,
3464 basePointer
, baseAddress
);
3467 if ( pageableKernelCollection
!= nullptr ) {
3468 // The baseKC for x86_64 has __HIB mapped first , so we need to get either the __DATA or __TEXT depending on what is earliest
3469 // The kernel base address is still __TEXT, even if __DATA or __HIB is mapped prior to that.
3470 // The loader may have loaded something before __TEXT, but the existingKernelCollection pointer still corresponds to __TEXT
3471 __block
uint64_t baseAddress
= ~0ULL;
3472 pageableKernelCollection
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
& info
, bool& stop
) {
3473 baseAddress
= std::min(baseAddress
, info
.vmAddr
);
3476 // The existing collection is a pointer to the mach_header for the baseKC, but __HIB and other segments may be before that
3477 // Offset those here
3478 uint64_t basePointerOffset
= pageableKernelCollection
->preferredLoadAddress() - baseAddress
;
3479 const uint8_t* basePointer
= (uint8_t*)pageableKernelCollection
- basePointerOffset
;
3481 vtablePatcher
.addKernelCollection(pageableKernelCollection
, Options::AppCacheKind::pageableKC
,
3482 basePointer
, baseAddress
);
3486 vtablePatcher
.addKernelCollection((const dyld3::MachOAppCache
*)cacheHeader
.header
, appCacheOptions
.cacheKind
,
3487 (const uint8_t*)_fullAllocatedBuffer
, cacheBaseAddress
);
3489 // Add all the dylibs to the patcher
3491 if ( existingKernelCollection
!= nullptr ) {
3492 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::kernel
);
3494 __block
std::map
<std::string
, std::vector
<std::string
>> kextDependencies
;
3495 kextDependencies
[kernelID
] = {};
3496 existingKernelCollection
->forEachPrelinkInfoLibrary(_diagnostics
,
3497 ^(const char *bundleName
, const char* relativePath
,
3498 const std::vector
<const char *> &deps
) {
3499 std::vector
<std::string
>& dependencies
= kextDependencies
[bundleName
];
3500 dependencies
.insert(dependencies
.end(), deps
.begin(), deps
.end());
3503 existingKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *dylibID
, bool &stop
) {
3504 auto depsIt
= kextDependencies
.find(dylibID
);
3505 assert(depsIt
!= kextDependencies
.end());
3506 vtablePatcher
.addDylib(_diagnostics
, ma
, dylibID
, depsIt
->second
, fixupLevel
);
3510 if ( pageableKernelCollection
!= nullptr ) {
3511 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::pageableKC
);
3513 __block
std::map
<std::string
, std::vector
<std::string
>> kextDependencies
;
3514 pageableKernelCollection
->forEachPrelinkInfoLibrary(_diagnostics
,
3515 ^(const char *bundleName
, const char* relativePath
,
3516 const std::vector
<const char *> &deps
) {
3517 std::vector
<std::string
>& dependencies
= kextDependencies
[bundleName
];
3518 dependencies
.insert(dependencies
.end(), deps
.begin(), deps
.end());
3521 pageableKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *dylibID
, bool &stop
) {
3522 auto depsIt
= kextDependencies
.find(dylibID
);
3523 assert(depsIt
!= kextDependencies
.end());
3524 vtablePatcher
.addDylib(_diagnostics
, ma
, dylibID
, depsIt
->second
, fixupLevel
);
3528 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
, DylibStripMode stripMode
,
3529 const std::vector
<std::string
> &dependencies
, Diagnostics
& dylibDiag
, bool &stop
) {
3530 vtablePatcher
.addDylib(dylibDiag
, ma
, dylibID
, dependencies
, currentLevel
);
3534 vtablePatcher
.findMetaclassDefinitions(dylibsToSymbols
, kernelID
, kernelMA
, appCacheOptions
.cacheKind
);
3535 vtablePatcher
.findExistingFixups(_diagnostics
, existingKernelCollection
, pageableKernelCollection
);
3536 if ( _diagnostics
.hasError() )
3539 // Add vtables from the base KC if we have one
3540 if ( existingKernelCollection
!= nullptr ) {
3541 vtablePatcher
.findBaseKernelVTables(_diagnostics
, existingKernelCollection
, dylibsToSymbols
);
3542 if ( _diagnostics
.hasError() )
3546 // Add vtables from the pageable KC if we have one
3547 if ( pageableKernelCollection
!= nullptr ) {
3548 vtablePatcher
.findPageableKernelVTables(_diagnostics
, pageableKernelCollection
, dylibsToSymbols
);
3549 if ( _diagnostics
.hasError() )
3553 // Add vables from our level
3554 vtablePatcher
.findVTables(currentLevel
, kernelMA
, dylibsToSymbols
, _aslrTracker
, missingBindLocations
);
3556 // Don't run the patcher if we have a failure finding the vtables
3557 if ( vtablePatcher
.hasError() ) {
3558 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
3562 // Now patch all of the vtables.
3563 vtablePatcher
.patchVTables(_diagnostics
, missingBindLocations
, _aslrTracker
, currentLevel
);
3564 if ( _diagnostics
.hasError() )
3567 if ( vtablePatcher
.hasError() ) {
3568 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
3572 // FIXME: We could move vtablePatcherOwner to a worker thread to be destroyed
3573 vtablePatcherOwner
.reset();
3575 // Also error out if we have an error on any of the dylib diagnostic objects
3577 // Log any binds which are still missing
3578 for (const auto& missingLocationAndBind
: missingBindLocations
) {
3579 const uint8_t* missingBindLoc
= missingLocationAndBind
.first
;
3580 const VTableBindSymbol
& missingBind
= missingLocationAndBind
.second
;
3582 // Work out which segment and section this missing bind was in
3583 __block
bool reportedError
= false;
3584 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
, DylibStripMode stripMode
,
3585 const std::vector
<std::string
> &dependencies
, Diagnostics
& dylibDiag
, bool &stopDylib
) {
3586 intptr_t slide
= ma
->getSlide();
3587 ma
->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo
§Info
,
3588 bool malformedSectionRange
, bool &stopSection
) {
3589 const uint8_t* content
= (uint8_t*)(sectInfo
.sectAddr
+ slide
);
3590 const uint8_t* start
= (uint8_t*)content
;
3591 const uint8_t* end
= start
+ sectInfo
.sectSize
;
3592 if ( (missingBindLoc
>= start
) && (missingBindLoc
< end
) ) {
3593 std::string segmentName
= sectInfo
.segInfo
.segName
;
3594 std::string sectionName
= sectInfo
.sectName
;
3595 uint64_t sectionOffset
= (missingBindLoc
- start
);
3597 dylibDiag
.error("Failed to bind '%s' in '%s' (at offset 0x%llx in %s, %s) as "
3598 "could not find a kext which exports this symbol",
3599 missingBind
.symbolName
.c_str(), missingBind
.binaryID
.data(),
3600 sectionOffset
, segmentName
.c_str(), sectionName
.c_str());
3602 reportedError
= true;
3609 if ( !reportedError
) {
3610 _diagnostics
.error("Failed to bind '%s' in '%s' as could not find a kext which exports this symbol",
3611 missingBind
.symbolName
.c_str(), missingBind
.binaryID
.data());
3615 // If we had missing binds and reported no other errors, then generate an error to give the diagnostics something to track
3616 if ( !missingBindLocations
.empty() && _diagnostics
.noError() ) {
3617 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
3620 // FIXME: We could move dylibsToSymbolsOwner to a worker thread to be destroyed
3627 ByteBuffer(uint8_t* storage
, uintptr_t allocCount
) {
3628 buffer
.setInitialStorage(storage
, allocCount
);
3631 uint8_t* makeSpace(size_t bytesNeeded
) {
3632 // Make space in the buffer
3633 for (size_t i
= 0; i
!= bytesNeeded
; ++i
)
3634 buffer
.default_constuct_back();
3636 // Grab a pointer to our position in the buffer
3637 uint8_t* data
= buffer
.begin();
3639 // Move the buffer to start after our data
3640 dyld3::Array
<uint8_t> newBuffer(buffer
.end(), buffer
.freeCount(), 0);
3646 const uint8_t* begin() const {
3647 return buffer
.begin();
3650 const uint8_t* end() const {
3651 return buffer
.end();
3655 dyld3::Array
<uint8_t> buffer
;
3660 void AppCacheBuilder::writeFixups()
3662 if ( fixupsSubRegion
.sizeInUse
== 0 )
3665 __block ByteBuffer
byteBuffer(fixupsSubRegion
.buffer
, fixupsSubRegion
.bufferSize
);
3667 // Keep track of where we put the fixups
3668 const uint8_t* classicRelocsBufferStart
= nullptr;
3669 const uint8_t* classicRelocsBufferEnd
= nullptr;
3671 // If the kernel needs classic relocs, emit those first
3672 CacheHeader64
& header
= cacheHeader
;
3673 if ( header
.dynSymbolTable
!= nullptr ) {
3674 classicRelocsBufferStart
= byteBuffer
.begin();
3676 dyld3::MachOAnalyzer
* cacheMA
= (dyld3::MachOAnalyzer
*)header
.header
;
3677 __block
uint64_t localRelocBaseAddress
= 0;
3678 cacheMA
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
3679 if ( info
.protections
& VM_PROT_WRITE
) {
3680 localRelocBaseAddress
= info
.vmAddr
;
3685 const std::vector
<void*> allRebaseTargets
= _aslrTracker
.getRebaseTargets();
3687 const dyld3::MachOAnalyzer
* kernelMA
= getKernelStaticExecutableFromCache();
3688 kernelMA
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
3689 std::vector
<void*> segmentRebaseTargets
;
3690 uint64_t segmentVMOffset
= info
.vmAddr
- cacheBaseAddress
;
3691 const uint8_t* segmentStartAddr
= (const uint8_t*)(_fullAllocatedBuffer
+ segmentVMOffset
);
3692 const uint8_t* segmentEndAddr
= (const uint8_t*)(segmentStartAddr
+ info
.vmSize
);
3693 for (void* target
: allRebaseTargets
) {
3694 if ( (target
>= segmentStartAddr
) && (target
< segmentEndAddr
) ) {
3695 segmentRebaseTargets
.push_back(target
);
3698 std::sort(segmentRebaseTargets
.begin(), segmentRebaseTargets
.end());
3700 for (void* target
: segmentRebaseTargets
) {
3701 uint64_t targetSegmentOffset
= (uint64_t)target
- (uint64_t)segmentStartAddr
;
3702 //printf("Target: %s + 0x%llx: %p\n", info.segName, targetSegmentOffset, target);
3704 uint64_t offsetFromBaseAddress
= (info
.vmAddr
+ targetSegmentOffset
) - localRelocBaseAddress
;
3705 relocation_info
* reloc
= (relocation_info
*)byteBuffer
.makeSpace(sizeof(relocation_info
));
3706 reloc
->r_address
= (uint32_t)offsetFromBaseAddress
;
3707 reloc
->r_symbolnum
= 0;
3708 reloc
->r_pcrel
= false;
3709 reloc
->r_length
= 0;
3710 reloc
->r_extern
= 0;
3713 uint32_t vmAddr32
= 0;
3714 uint64_t vmAddr64
= 0;
3715 if ( _aslrTracker
.hasRebaseTarget32(target
, &vmAddr32
) ) {
3716 reloc
->r_length
= 2;
3717 *(uint32_t*)target
= vmAddr32
;
3718 } else if ( _aslrTracker
.hasRebaseTarget64(target
, &vmAddr64
) ) {
3719 reloc
->r_length
= 3;
3720 *(uint64_t*)target
= vmAddr64
;
3724 // Remove these fixups so that we don't also emit chained fixups for them
3725 for (void* target
: segmentRebaseTargets
)
3726 _aslrTracker
.remove(target
);
3729 classicRelocsBufferEnd
= byteBuffer
.begin();
3732 // TODO: 32-bit pointer format
3734 const uint8_t currentLevel
= getCurrentFixupLevel();
3736 // We can have up to 4 levels in the fixup format. These are the base addresses from
3737 // which each level starts
3738 BLOCK_ACCCESSIBLE_ARRAY(uint64_t, levelBaseAddresses
, 4);
3739 for (unsigned i
= 0; i
!= numFixupLevels
; ++i
)
3740 levelBaseAddresses
[i
] = 0;
3742 levelBaseAddresses
[currentLevel
] = cacheBaseAddress
;
3743 if ( appCacheOptions
.cacheKind
!= Options::AppCacheKind::kernel
) {
3744 assert(existingKernelCollection
!= nullptr);
3745 // The auxKC is mapped with __DATA first, so we need to get either the __DATA or __TEXT depending on what is earliest
3746 __block
uint64_t baseAddress
= ~0ULL;
3747 existingKernelCollection
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
& info
, bool& stop
) {
3748 baseAddress
= std::min(baseAddress
, info
.vmAddr
);
3750 levelBaseAddresses
[0] = baseAddress
;
3753 if ( pageableKernelCollection
!= nullptr ) {
3754 // We may have __DATA first, so we need to get either the __DATA or __TEXT depending on what is earliest
3755 __block
uint64_t baseAddress
= ~0ULL;
3756 pageableKernelCollection
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
& info
, bool& stop
) {
3757 baseAddress
= std::min(baseAddress
, info
.vmAddr
);
3759 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::pageableKC
);
3760 levelBaseAddresses
[fixupLevel
] = baseAddress
;
3763 // We have a dyld_chained_starts_in_segment plus an offset for each page
3764 struct SegmentFixups
{
3765 //const Region* region = nullptr;
3766 uint8_t* segmentBuffer
= nullptr;
3767 uint64_t segmentIndex
= 0;
3768 uint64_t unslidLoadAddress
= 0;
3769 uint64_t sizeInUse
= 0;
3770 dyld_chained_starts_in_segment
* starts
= nullptr;
3771 uint64_t startsByteSize
= 0;
3772 uint64_t numPagesToFixup
= 0;
3775 auto buildChainedFixups
= ^(uint64_t baseAddress
, uint64_t segmentCount
, std::vector
<SegmentFixups
>& startsInSegments
) {
3777 const uint8_t* chainedFixupsBufferStart
= nullptr;
3778 const uint8_t* chainedFixupsBufferEnd
= nullptr;
3780 chainedFixupsBufferStart
= byteBuffer
.begin();
3782 // Start with dyld_chained_fixups_header which is fixed size
3783 dyld_chained_fixups_header
* fixupsHeader
= (dyld_chained_fixups_header
*)byteBuffer
.makeSpace(sizeof(dyld_chained_fixups_header
));
3785 // We have a dyld_chained_starts_in_image plus an offset for each segment
3786 dyld_chained_starts_in_image
* startsInImage
= (dyld_chained_starts_in_image
*)byteBuffer
.makeSpace(sizeof(dyld_chained_starts_in_image
) + (segmentCount
* sizeof(uint32_t)));
3788 const uint8_t* endOfStarts
= nullptr;
3789 for (SegmentFixups
& segmentFixups
: startsInSegments
) {
3790 uint64_t startsInSegmentByteSize
= sizeof(dyld_chained_starts_in_segment
) + (segmentFixups
.numPagesToFixup
* sizeof(uint16_t));
3791 dyld_chained_starts_in_segment
* startsInSegment
= (dyld_chained_starts_in_segment
*)byteBuffer
.makeSpace(startsInSegmentByteSize
);
3792 endOfStarts
= (const uint8_t*)startsInSegment
+ startsInSegmentByteSize
;
3794 segmentFixups
.starts
= startsInSegment
;
3795 segmentFixups
.startsByteSize
= startsInSegmentByteSize
;
3799 startsInImage
->seg_count
= (uint32_t)segmentCount
;
3800 for (uint32_t segmentIndex
= 0; segmentIndex
!= segmentCount
; ++segmentIndex
) {
3801 startsInImage
->seg_info_offset
[segmentIndex
] = 0;
3803 for (const SegmentFixups
& segmentFixups
: startsInSegments
) {
3804 dyld_chained_starts_in_segment
* startsInSegment
= segmentFixups
.starts
;
3805 uint64_t segmentIndex
= segmentFixups
.segmentIndex
;
3806 assert(segmentIndex
< segmentCount
);
3807 assert(startsInImage
->seg_info_offset
[segmentIndex
] == 0);
3808 startsInImage
->seg_info_offset
[segmentIndex
] = (uint32_t)((uint8_t*)startsInSegment
- (uint8_t*)startsInImage
);
3811 const unsigned chainedPointerStride
= dyld3::MachOAnalyzer::ChainedFixupPointerOnDisk::strideSize(chainedPointerFormat
);
3813 // Starts in segment
3814 for (const SegmentFixups
& segmentFixups
: startsInSegments
) {
3815 dyld_chained_starts_in_segment
* startsInSegment
= segmentFixups
.starts
;
3816 startsInSegment
->size
= (uint32_t)segmentFixups
.startsByteSize
;
3817 startsInSegment
->page_size
= fixupsPageSize();
3818 startsInSegment
->pointer_format
= chainedPointerFormat
;
3819 startsInSegment
->segment_offset
= segmentFixups
.unslidLoadAddress
- baseAddress
;
3820 startsInSegment
->max_valid_pointer
= 0; // FIXME: Needed in 32-bit only
3821 startsInSegment
->page_count
= (segmentFixups
.sizeInUse
+ startsInSegment
->page_size
- 1) / startsInSegment
->page_size
;
3822 for (uint64_t pageIndex
= 0; pageIndex
!= startsInSegment
->page_count
; ++pageIndex
) {
3823 startsInSegment
->page_start
[pageIndex
] = DYLD_CHAINED_PTR_START_NONE
;
3824 uint8_t* lastLoc
= nullptr;
3825 // Note we always walk in 1-byte at a time as x86_64 has unaligned fixups
3826 for (uint64_t pageOffset
= 0; pageOffset
!= startsInSegment
->page_size
; pageOffset
+= 1) {
3827 uint8_t* fixupLoc
= segmentFixups
.segmentBuffer
+ (pageIndex
* startsInSegment
->page_size
) + pageOffset
;
3828 uint8_t fixupLevel
= currentLevel
;
3829 if ( !_aslrTracker
.has(fixupLoc
, &fixupLevel
) )
3831 assert((pageOffset
% chainedPointerStride
) == 0);
3833 // Patch last loc to point here
3835 dyld_chained_ptr_64_kernel_cache_rebase
* lastLocBits
= (dyld_chained_ptr_64_kernel_cache_rebase
*)lastLoc
;
3836 assert(lastLocBits
->next
== 0);
3837 uint64_t next
= (fixupLoc
- lastLoc
) / chainedPointerStride
;
3838 lastLocBits
->next
= next
;
3839 assert(lastLocBits
->next
== next
&& "next location truncated");
3841 // First fixup on this page
3842 startsInSegment
->page_start
[pageIndex
] = pageOffset
;
3846 uint64_t targetVMAddr
= *(uint64_t*)fixupLoc
;
3848 uint8_t highByte
= 0;
3849 if ( _aslrTracker
.hasHigh8(fixupLoc
, &highByte
) ) {
3850 uint64_t tbi
= (uint64_t)highByte
<< 56;
3851 targetVMAddr
|= tbi
;
3854 assert(fixupLevel
< numFixupLevels
);
3855 uint64_t targetVMOffset
= targetVMAddr
- levelBaseAddresses
[fixupLevel
];
3857 // Pack the vmAddr on this location in to the fixup format
3858 dyld_chained_ptr_64_kernel_cache_rebase
* locBits
= (dyld_chained_ptr_64_kernel_cache_rebase
*)fixupLoc
;
3863 if ( _aslrTracker
.hasAuthData(fixupLoc
, &diversity
, &hasAddrDiv
, &key
) ) {
3864 locBits
->target
= targetVMOffset
;
3865 locBits
->cacheLevel
= fixupLevel
;
3866 locBits
->diversity
= diversity
;
3867 locBits
->addrDiv
= hasAddrDiv
;
3870 locBits
->isAuth
= 1;
3871 assert(locBits
->target
== targetVMOffset
&& "target truncated");
3874 locBits
->target
= targetVMOffset
;
3875 locBits
->cacheLevel
= fixupLevel
;
3876 locBits
->diversity
= 0;
3877 locBits
->addrDiv
= 0;
3880 locBits
->isAuth
= 0;
3881 assert(locBits
->target
== targetVMOffset
&& "target truncated");
3887 chainedFixupsBufferEnd
= byteBuffer
.begin();
3890 fixupsHeader
->fixups_version
= 0;
3891 fixupsHeader
->starts_offset
= (uint32_t)((uint8_t*)startsInImage
- (uint8_t*)fixupsHeader
);
3892 fixupsHeader
->imports_offset
= (uint32_t)((uint8_t*)chainedFixupsBufferEnd
- (uint8_t*)fixupsHeader
);
3893 fixupsHeader
->symbols_offset
= fixupsHeader
->imports_offset
;
3894 fixupsHeader
->imports_count
= 0;
3895 fixupsHeader
->imports_format
= DYLD_CHAINED_IMPORT
; // The validate code wants a value here
3896 fixupsHeader
->symbols_format
= 0;
3898 return std::make_pair(chainedFixupsBufferStart
, chainedFixupsBufferEnd
);
3901 if ( fixupsArePerKext() ) {
3902 // The pageableKC (and sometimes auxKC) has one LC_DYLD_CHAINED_FIXUPS per kext, not 1 total
3903 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
3904 DylibStripMode stripMode
, const std::vector
<std::string
> &dependencies
,
3905 Diagnostics
& dylibDiag
, bool &stop
) {
3906 uint64_t loadAddress
= ma
->preferredLoadAddress();
3908 __block
uint64_t numSegments
= 0;
3909 __block
std::vector
<SegmentFixups
> segmentFixups
;
3910 ma
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
3911 // Third party kexts have writable __TEXT, so we need to add starts for all segments
3912 // other than LINKEDIT
3913 bool segmentCanHaveFixups
= false;
3914 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::pageableKC
) {
3915 segmentCanHaveFixups
= (info
.protections
& VM_PROT_WRITE
) != 0;
3918 segmentCanHaveFixups
= (strcmp(info
.segName
, "__LINKEDIT") != 0);
3921 if ( segmentCanHaveFixups
) {
3922 SegmentFixups segmentToFixup
;
3923 segmentToFixup
.segmentBuffer
= (uint8_t*)ma
+ (info
.vmAddr
- loadAddress
);
3924 segmentToFixup
.segmentIndex
= info
.segIndex
;
3925 segmentToFixup
.unslidLoadAddress
= info
.vmAddr
;
3926 segmentToFixup
.sizeInUse
= info
.vmSize
;
3927 segmentToFixup
.starts
= nullptr;
3928 segmentToFixup
.startsByteSize
= 0;
3929 segmentToFixup
.numPagesToFixup
= numWritablePagesToFixup(info
.vmSize
);
3930 segmentFixups
.push_back(segmentToFixup
);
3937 std::pair
<const uint8_t*, const uint8_t*> chainedFixupsRange
= buildChainedFixups(loadAddress
,
3938 numSegments
, segmentFixups
);
3939 const uint8_t* chainedFixupsBufferStart
= chainedFixupsRange
.first
;
3940 const uint8_t* chainedFixupsBufferEnd
= chainedFixupsRange
.second
;
3942 if ( chainedFixupsBufferStart
!= chainedFixupsBufferEnd
) {
3943 // Add the load command to our file
3945 uint64_t fixupsOffset
= (uint64_t)chainedFixupsBufferStart
- (uint64_t)fixupsSubRegion
.buffer
;
3946 uint64_t fixupsSize
= (uint64_t)chainedFixupsBufferEnd
- (uint64_t)chainedFixupsBufferStart
;
3950 typedef Pointer64
<LittleEndian
> P
;
3952 uint32_t freeSpace
= ma
->loadCommandsFreeSpace();
3953 assert(freeSpace
>= sizeof(macho_linkedit_data_command
<P
>));
3954 uint8_t* endOfLoadCommands
= (uint8_t*)ma
+ sizeof(macho_header
<P
>) + ma
->sizeofcmds
;
3956 // update mach_header to account for new load commands
3957 macho_header
<P
>* mh
= (macho_header
<P
>*)ma
;
3958 mh
->set_sizeofcmds(mh
->sizeofcmds() + sizeof(macho_linkedit_data_command
<P
>));
3959 mh
->set_ncmds(mh
->ncmds() + 1);
3961 // Add the new load command
3962 macho_linkedit_data_command
<P
>* cmd
= (macho_linkedit_data_command
<P
>*)endOfLoadCommands
;
3963 cmd
->set_cmd(LC_DYLD_CHAINED_FIXUPS
);
3964 cmd
->set_cmdsize(sizeof(linkedit_data_command
));
3965 cmd
->set_dataoff((uint32_t)(_readOnlyRegion
.cacheFileOffset
+ _readOnlyRegion
.sizeInUse
+ fixupsOffset
));
3966 cmd
->set_datasize((uint32_t)fixupsSize
);
3970 // Also build chained fixups on the top level for the branch stub GOTs
3971 // FIXME: We don't need numRegions() here, but instead just up to an including the RW region
3972 uint64_t segmentCount
= numRegions();
3973 __block
std::vector
<SegmentFixups
> segmentFixups
;
3975 if ( branchGOTsRegion
.sizeInUse
!= 0 ) {
3976 SegmentFixups segmentToFixup
;
3977 segmentToFixup
.segmentBuffer
= branchGOTsRegion
.buffer
;
3978 segmentToFixup
.segmentIndex
= branchGOTsRegion
.index
;
3979 segmentToFixup
.unslidLoadAddress
= branchGOTsRegion
.unslidLoadAddress
;
3980 segmentToFixup
.sizeInUse
= branchGOTsRegion
.sizeInUse
;
3981 segmentToFixup
.starts
= nullptr;
3982 segmentToFixup
.startsByteSize
= 0;
3983 segmentToFixup
.numPagesToFixup
= numWritablePagesToFixup(branchGOTsRegion
.bufferSize
);
3984 segmentFixups
.push_back(segmentToFixup
);
3987 std::pair
<const uint8_t*, const uint8_t*> chainedFixupsRange
= buildChainedFixups(cacheHeaderRegion
.unslidLoadAddress
,
3988 segmentCount
, segmentFixups
);
3989 const uint8_t* chainedFixupsBufferStart
= chainedFixupsRange
.first
;
3990 const uint8_t* chainedFixupsBufferEnd
= chainedFixupsRange
.second
;
3992 if ( chainedFixupsBufferStart
!= chainedFixupsBufferEnd
) {
3993 uint64_t fixupsOffset
= (uint64_t)chainedFixupsBufferStart
- (uint64_t)fixupsSubRegion
.buffer
;
3994 uint64_t fixupsSize
= (uint64_t)chainedFixupsBufferEnd
- (uint64_t)chainedFixupsBufferStart
;
3995 header
.chainedFixups
->dataoff
= (uint32_t)_readOnlyRegion
.cacheFileOffset
+ (uint32_t)_readOnlyRegion
.sizeInUse
+ (uint32_t)fixupsOffset
;;
3996 header
.chainedFixups
->datasize
= (uint32_t)fixupsSize
;
3999 // Build the chained fixups for just the kernel collection itself
4000 // FIXME: We don't need numRegions() here, but instead just up to an including the RW region
4001 uint64_t segmentCount
= numRegions();
4002 __block
std::vector
<SegmentFixups
> segmentFixups
;
4004 auto addSegmentStarts
= ^(const Region
& region
) {
4005 SegmentFixups segmentToFixup
;
4006 segmentToFixup
.segmentBuffer
= region
.buffer
;
4007 segmentToFixup
.segmentIndex
= region
.index
;
4008 segmentToFixup
.unslidLoadAddress
= region
.unslidLoadAddress
;
4009 segmentToFixup
.sizeInUse
= region
.sizeInUse
;
4010 segmentToFixup
.starts
= nullptr;
4011 segmentToFixup
.startsByteSize
= 0;
4012 segmentToFixup
.numPagesToFixup
= numWritablePagesToFixup(region
.bufferSize
);
4013 segmentFixups
.push_back(segmentToFixup
);
4016 if ( dataConstRegion
.sizeInUse
!= 0 )
4017 addSegmentStarts(dataConstRegion
);
4018 if ( branchGOTsRegion
.sizeInUse
!= 0 )
4019 addSegmentStarts(branchGOTsRegion
);
4020 if ( readWriteRegion
.sizeInUse
!= 0 )
4021 addSegmentStarts(readWriteRegion
);
4022 if ( hibernateRegion
.sizeInUse
!= 0 )
4023 addSegmentStarts(hibernateRegion
);
4024 for (const Region
& region
: nonSplitSegRegions
) {
4025 // Assume writable regions have fixups to emit
4026 // Note, third party kext's have __TEXT fixups, so assume all of these have fixups
4027 // LINKEDIT is already elsewhere
4028 addSegmentStarts(region
);
4031 std::pair
<const uint8_t*, const uint8_t*> chainedFixupsRange
= buildChainedFixups(cacheHeaderRegion
.unslidLoadAddress
,
4032 segmentCount
, segmentFixups
);
4033 const uint8_t* chainedFixupsBufferStart
= chainedFixupsRange
.first
;
4034 const uint8_t* chainedFixupsBufferEnd
= chainedFixupsRange
.second
;
4036 if ( chainedFixupsBufferStart
!= chainedFixupsBufferEnd
) {
4037 uint64_t fixupsOffset
= (uint64_t)chainedFixupsBufferStart
- (uint64_t)fixupsSubRegion
.buffer
;
4038 uint64_t fixupsSize
= (uint64_t)chainedFixupsBufferEnd
- (uint64_t)chainedFixupsBufferStart
;
4039 header
.chainedFixups
->dataoff
= (uint32_t)_readOnlyRegion
.cacheFileOffset
+ (uint32_t)_readOnlyRegion
.sizeInUse
+ (uint32_t)fixupsOffset
;;
4040 header
.chainedFixups
->datasize
= (uint32_t)fixupsSize
;
4044 // Move the fixups to the end of __LINKEDIT
4045 if ( classicRelocsBufferStart
!= classicRelocsBufferEnd
) {
4046 uint64_t fixupsOffset
= (uint64_t)classicRelocsBufferStart
- (uint64_t)fixupsSubRegion
.buffer
;
4047 uint64_t fixupsSize
= (uint64_t)classicRelocsBufferEnd
- (uint64_t)classicRelocsBufferStart
;
4048 header
.dynSymbolTable
->locreloff
= (uint32_t)_readOnlyRegion
.cacheFileOffset
+ (uint32_t)_readOnlyRegion
.sizeInUse
+ (uint32_t)fixupsOffset
;
4049 header
.dynSymbolTable
->nlocrel
= (uint32_t)fixupsSize
/ sizeof(fixupsSize
);
4052 uint64_t fixupsSpace
= (uint64_t)byteBuffer
.end() - (uint64_t)fixupsSubRegion
.buffer
;
4054 uint8_t* linkeditEnd
= _readOnlyRegion
.buffer
+ _readOnlyRegion
.sizeInUse
;
4055 memcpy(linkeditEnd
, fixupsSubRegion
.buffer
, fixupsSpace
);
4056 uint8_t* fixupsEnd
= linkeditEnd
+ fixupsSpace
;
4058 _readOnlyRegion
.sizeInUse
+= align(fixupsSpace
, _is64
? 3 : 2);
4059 _readOnlyRegion
.sizeInUse
= align(_readOnlyRegion
.sizeInUse
, 14);
4060 _readOnlyRegion
.bufferSize
= _readOnlyRegion
.sizeInUse
;
4062 // Zero the alignment gap, just in case there's any unoptimized LINKEDIT in there
4063 uint8_t* alignedBufferEnd
= _readOnlyRegion
.buffer
+ _readOnlyRegion
.sizeInUse
;
4064 if ( fixupsEnd
!= alignedBufferEnd
){
4065 memset(fixupsEnd
, 0, alignedBufferEnd
- fixupsEnd
);
4069 dyld3::MachOAnalyzer
* cacheMA
= (dyld3::MachOAnalyzer
*)header
.header
;
4070 uint64_t cachePreferredLoadAddress
= cacheMA
->preferredLoadAddress();
4071 cacheMA
->forEachRebase(_diagnostics
, false, ^(uint64_t runtimeOffset
, bool &stop
) {
4072 printf("Rebase: 0x%llx = 0x%llx\n", runtimeOffset
, runtimeOffset
+ cachePreferredLoadAddress
);
4077 void AppCacheBuilder::allocateBuffer()
4079 // Whether to order the regions __TEXT, __DATA, __LINKEDIT or __DATA, __TEXT, __LINKEDIT in VM address order
4080 bool dataRegionFirstInVMOrder
= false;
4081 bool hibernateRegionFirstInVMOrder
= false;
4082 switch (appCacheOptions
.cacheKind
) {
4083 case Options::AppCacheKind::none
:
4084 assert(0 && "Cache kind should have been set");
4086 case Options::AppCacheKind::kernel
:
4087 if ( hibernateAddress
!= 0 )
4088 hibernateRegionFirstInVMOrder
= true;
4090 case Options::AppCacheKind::pageableKC
:
4091 // There's no interesting ordering for the pageableKC
4093 case Options::AppCacheKind::kernelCollectionLevel2
:
4094 assert(0 && "Unimplemented");
4096 case Options::AppCacheKind::auxKC
:
4097 dataRegionFirstInVMOrder
= true;
4101 // Count how many bytes we need from all our regions
4102 __block
uint64_t numRegionFileBytes
= 0;
4103 __block
uint64_t numRegionVMBytes
= 0;
4105 std::vector
<std::pair
<Region
*, uint64_t>> regions
;
4106 std::vector
<std::pair
<Region
*, uint64_t>> regionsVMOrder
;
4107 std::map
<const Region
*, uint32_t> sectionsToAddToRegions
;
4109 if ( hibernateRegionFirstInVMOrder
) {
4110 regionsVMOrder
.push_back({ &hibernateRegion
, numRegionVMBytes
});
4111 // Pad out the VM offset so that the cache header starts where the base address
4113 uint64_t paddedSize
= cacheBaseAddress
- hibernateAddress
;
4114 if ( hibernateRegion
.bufferSize
> paddedSize
) {
4115 _diagnostics
.error("Could not lay out __HIB segment");
4118 numRegionVMBytes
= paddedSize
;
4119 // Set the base address to the hibernate address so that we actually put the
4120 // hibernate segment there
4121 cacheBaseAddress
= hibernateAddress
;
4123 // Add a section too
4124 sectionsToAddToRegions
[&hibernateRegion
] = 1;
4125 } else if ( dataRegionFirstInVMOrder
) {
4126 if ( prelinkInfoDict
!= nullptr ) {
4127 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4128 regionsVMOrder
.push_back({ &prelinkInfoRegion
, numRegionVMBytes
});
4129 numRegionVMBytes
+= prelinkInfoRegion
.bufferSize
;
4131 if ( readWriteRegion
.sizeInUse
!= 0 ) {
4132 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4133 regionsVMOrder
.push_back({ &readWriteRegion
, numRegionVMBytes
});
4134 numRegionVMBytes
+= readWriteRegion
.bufferSize
;
4139 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4140 regions
.push_back({ &cacheHeaderRegion
, 0 });
4141 regionsVMOrder
.push_back({ &cacheHeaderRegion
, numRegionVMBytes
});
4146 readOnlyTextRegion
.cacheFileOffset
= numRegionFileBytes
;
4147 numRegionFileBytes
+= readOnlyTextRegion
.bufferSize
;
4148 regions
.push_back({ &readOnlyTextRegion
, 0 });
4150 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4151 regionsVMOrder
.push_back({ &readOnlyTextRegion
, numRegionVMBytes
});
4152 numRegionVMBytes
+= readOnlyTextRegion
.bufferSize
;
4154 // Add a section too
4155 sectionsToAddToRegions
[&readOnlyTextRegion
] = 1;
4158 // Split seg __TEXT_EXEC
4159 if ( readExecuteRegion
.sizeInUse
!= 0 ) {
4161 readExecuteRegion
.cacheFileOffset
= numRegionFileBytes
;
4162 numRegionFileBytes
+= readExecuteRegion
.bufferSize
;
4163 regions
.push_back({ &readExecuteRegion
, 0 });
4165 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4166 regionsVMOrder
.push_back({ &readExecuteRegion
, numRegionVMBytes
});
4167 numRegionVMBytes
+= readExecuteRegion
.bufferSize
;
4171 if ( branchStubsRegion
.bufferSize
!= 0 ) {
4173 branchStubsRegion
.cacheFileOffset
= numRegionFileBytes
;
4174 numRegionFileBytes
+= branchStubsRegion
.bufferSize
;
4175 regions
.push_back({ &branchStubsRegion
, 0 });
4177 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4178 regionsVMOrder
.push_back({ &branchStubsRegion
, numRegionVMBytes
});
4179 numRegionVMBytes
+= branchStubsRegion
.bufferSize
;
4183 if ( dataConstRegion
.sizeInUse
!= 0 ) {
4185 dataConstRegion
.cacheFileOffset
= numRegionFileBytes
;
4186 numRegionFileBytes
+= dataConstRegion
.bufferSize
;
4187 regions
.push_back({ &dataConstRegion
, 0 });
4189 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4190 regionsVMOrder
.push_back({ &dataConstRegion
, numRegionVMBytes
});
4191 numRegionVMBytes
+= dataConstRegion
.bufferSize
;
4195 if ( branchGOTsRegion
.bufferSize
!= 0 ) {
4197 branchGOTsRegion
.cacheFileOffset
= numRegionFileBytes
;
4198 numRegionFileBytes
+= branchGOTsRegion
.bufferSize
;
4199 regions
.push_back({ &branchGOTsRegion
, 0 });
4201 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4202 regionsVMOrder
.push_back({ &branchGOTsRegion
, numRegionVMBytes
});
4203 numRegionVMBytes
+= branchGOTsRegion
.bufferSize
;
4207 // Align to 16k before we lay out all contiguous regions
4208 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4209 for (CustomSegment
& customSegment
: customSegments
) {
4210 Region
& region
= *customSegment
.parentRegion
;
4212 region
.cacheFileOffset
= numRegionFileBytes
;
4213 numRegionFileBytes
+= region
.bufferSize
;
4214 regions
.push_back({ ®ion
, 0 });
4216 // Note we can't align the vm offset in here
4217 assert( (numRegionVMBytes
% 4096) == 0);
4218 regionsVMOrder
.push_back({ ®ion
, numRegionVMBytes
});
4219 numRegionVMBytes
+= region
.bufferSize
;
4221 // Maybe add sections too
4222 uint32_t sectionsToAdd
= 0;
4223 if ( customSegment
.sections
.size() > 1 ) {
4224 // More than one section, so they all need names
4225 sectionsToAdd
= (uint32_t)customSegment
.sections
.size();
4226 } else if ( !customSegment
.sections
.front().sectionName
.empty() ) {
4227 // Only one section, but it has a name
4230 sectionsToAddToRegions
[®ion
] = sectionsToAdd
;
4232 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4236 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4237 if ( prelinkInfoDict
!= nullptr )
4240 prelinkInfoRegion
.cacheFileOffset
= numRegionFileBytes
;
4241 numRegionFileBytes
+= prelinkInfoRegion
.bufferSize
;
4242 regions
.push_back({ &prelinkInfoRegion
, 0 });
4244 if ( !dataRegionFirstInVMOrder
) {
4246 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4247 regionsVMOrder
.push_back({ &prelinkInfoRegion
, numRegionVMBytes
});
4248 numRegionVMBytes
+= prelinkInfoRegion
.bufferSize
;
4251 // Add a section too
4252 sectionsToAddToRegions
[&prelinkInfoRegion
] = 1;
4257 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4258 if ( readWriteRegion
.sizeInUse
!= 0 ) {
4260 readWriteRegion
.cacheFileOffset
= numRegionFileBytes
;
4261 numRegionFileBytes
+= readWriteRegion
.bufferSize
;
4262 regions
.push_back({ &readWriteRegion
, 0 });
4264 if ( !dataRegionFirstInVMOrder
) {
4266 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4267 regionsVMOrder
.push_back({ &readWriteRegion
, numRegionVMBytes
});
4268 numRegionVMBytes
+= readWriteRegion
.bufferSize
;
4274 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4275 if ( hibernateRegion
.sizeInUse
!= 0 ) {
4277 hibernateRegion
.cacheFileOffset
= numRegionFileBytes
;
4278 numRegionFileBytes
+= hibernateRegion
.bufferSize
;
4279 regions
.push_back({ &hibernateRegion
, 0 });
4281 // VM offset was already handled earlier
4284 // Non split seg regions
4285 // Align to 16k before we lay out all contiguous regions
4286 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4287 for (Region
& region
: nonSplitSegRegions
) {
4288 region
.cacheFileOffset
= numRegionFileBytes
;
4289 numRegionFileBytes
+= region
.bufferSize
;
4290 regions
.push_back({ ®ion
, 0 });
4292 // Note we can't align the vm offset in here
4293 assert( (numRegionVMBytes
% 4096) == 0);
4294 regionsVMOrder
.push_back({ ®ion
, numRegionVMBytes
});
4295 numRegionVMBytes
+= region
.bufferSize
;
4297 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4302 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4303 _readOnlyRegion
.cacheFileOffset
= numRegionFileBytes
;
4304 numRegionFileBytes
+= _readOnlyRegion
.bufferSize
;
4305 regions
.push_back({ &_readOnlyRegion
, 0 });
4307 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4308 regionsVMOrder
.push_back({ &_readOnlyRegion
, numRegionVMBytes
});
4309 numRegionVMBytes
+= _readOnlyRegion
.bufferSize
;
4311 // __LINKEDIT fixups sub region
4313 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4314 if ( fixupsSubRegion
.sizeInUse
!= 0 ) {
4315 fixupsSubRegion
.cacheFileOffset
= numRegionFileBytes
;
4316 numRegionFileBytes
+= fixupsSubRegion
.bufferSize
;
4317 //regions.push_back({ &fixupsSubRegion, 0 });
4320 regionsVMOrder
.push_back({ &fixupsSubRegion
, numRegionVMBytes
});
4321 numRegionVMBytes
+= fixupsSubRegion
.bufferSize
;
4324 const thread_command
* unixThread
= nullptr;
4325 if (const DylibInfo
* dylib
= getKernelStaticExecutableInputFile()) {
4326 unixThread
= dylib
->input
->mappedFile
.mh
->unixThreadLoadCommand();
4331 const uint64_t cacheHeaderSize
= sizeof(mach_header_64
);
4332 uint64_t cacheLoadCommandsSize
= 0;
4333 uint64_t cacheNumLoadCommands
= 0;
4336 ++cacheNumLoadCommands
;
4337 uint64_t uuidOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4338 cacheLoadCommandsSize
+= sizeof(uuid_command
);
4341 ++cacheNumLoadCommands
;
4342 uint64_t buildVersionOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4343 cacheLoadCommandsSize
+= sizeof(build_version_command
);
4346 uint64_t unixThreadOffset
= 0;
4347 if ( unixThread
!= nullptr ) {
4348 ++cacheNumLoadCommands
;
4349 unixThreadOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4350 cacheLoadCommandsSize
+= unixThread
->cmdsize
;
4353 // SYMTAB and DYSYMTAB
4354 uint64_t symbolTableOffset
= 0;
4355 uint64_t dynSymbolTableOffset
= 0;
4356 if (const DylibInfo
* dylib
= getKernelStaticExecutableInputFile()) {
4357 if ( dylib
->input
->mappedFile
.mh
->usesClassicRelocationsInKernelCollection() ) {
4359 ++cacheNumLoadCommands
;
4360 symbolTableOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4361 cacheLoadCommandsSize
+= sizeof(symtab_command
);
4364 ++cacheNumLoadCommands
;
4365 dynSymbolTableOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4366 cacheLoadCommandsSize
+= sizeof(dysymtab_command
);
4370 // LC_DYLD_CHAINED_FIXUPS
4371 // The pageableKC has one LC_DYLD_CHAINED_FIXUPS per kext, and 1 more on the top-level
4372 // for the branch GOTs
4373 uint64_t chainedFixupsOffset
= 0;
4374 if ( fixupsSubRegion
.bufferSize
!= 0 ) {
4375 ++cacheNumLoadCommands
;
4376 chainedFixupsOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4377 cacheLoadCommandsSize
+= sizeof(linkedit_data_command
);
4380 // Add an LC_SEGMENT_64 for each region
4381 for (auto& regionAndOffset
: regions
) {
4382 ++cacheNumLoadCommands
;
4383 regionAndOffset
.second
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4384 cacheLoadCommandsSize
+= sizeof(segment_command_64
);
4386 // Add space for any sections too
4387 auto sectionIt
= sectionsToAddToRegions
.find(regionAndOffset
.first
);
4388 if ( sectionIt
!= sectionsToAddToRegions
.end() ) {
4389 uint32_t numSections
= sectionIt
->second
;
4390 cacheLoadCommandsSize
+= sizeof(section_64
) * numSections
;
4394 // Add an LC_FILESET_ENTRY for each dylib
4395 std::vector
<std::pair
<const DylibInfo
*, uint64_t>> dylibs
;
4396 for (const auto& dylib
: sortedDylibs
) {
4397 ++cacheNumLoadCommands
;
4398 const char* dylibID
= dylib
.dylibID
.c_str();
4399 dylibs
.push_back({ &dylib
, cacheHeaderSize
+ cacheLoadCommandsSize
});
4400 uint64_t size
= align(sizeof(fileset_entry_command
) + strlen(dylibID
) + 1, 3);
4401 cacheLoadCommandsSize
+= size
;
4404 uint64_t cacheHeaderRegionSize
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4406 // Align the app cache header before the rest of the bytes
4407 cacheHeaderRegionSize
= align(cacheHeaderRegionSize
, 14);
4409 assert(numRegionFileBytes
<= numRegionVMBytes
);
4411 _allocatedBufferSize
= cacheHeaderRegionSize
+ numRegionVMBytes
;
4413 // The fixup format cannot handle a KC over 1GB (64MB for arm64e auxKC). Error out if we exceed that
4414 uint64_t cacheLimit
= 1 << 30;
4415 if ( (appCacheOptions
.cacheKind
== Options::AppCacheKind::auxKC
) && (_options
.archs
== &dyld3::GradedArchs::arm64e
) )
4416 cacheLimit
= 64 * (1 << 20);
4417 if ( _allocatedBufferSize
>= cacheLimit
) {
4418 _diagnostics
.error("kernel collection size exceeds maximum size of %lld vs actual size of %lld",
4419 cacheLimit
, _allocatedBufferSize
);
4423 if ( vm_allocate(mach_task_self(), &_fullAllocatedBuffer
, _allocatedBufferSize
, VM_FLAGS_ANYWHERE
) != 0 ) {
4424 _diagnostics
.error("could not allocate buffer");
4428 // Assign region vm and buffer addresses now that we know the size of
4431 // All vm offsets prior to the cache header are already correct
4432 // All those after the cache header need to be shifted by the cache
4434 bool seenCacheHeader
= false;
4435 for (const auto& regionAndVMOffset
: regionsVMOrder
) {
4436 Region
* region
= regionAndVMOffset
.first
;
4437 uint64_t vmOffset
= regionAndVMOffset
.second
;
4438 region
->unslidLoadAddress
= cacheBaseAddress
+ vmOffset
;
4439 if ( seenCacheHeader
) {
4440 // Shift by the cache header size
4441 region
->unslidLoadAddress
+= cacheHeaderRegionSize
;
4443 // The offset is correct but add in the base address
4444 seenCacheHeader
= (region
== &cacheHeaderRegion
);
4446 region
->buffer
= (uint8_t*)_fullAllocatedBuffer
+ (region
->unslidLoadAddress
- cacheBaseAddress
);
4451 cacheHeaderRegion
.bufferSize
= cacheHeaderRegionSize
;
4452 cacheHeaderRegion
.sizeInUse
= cacheHeaderRegion
.bufferSize
;
4453 cacheHeaderRegion
.cacheFileOffset
= 0;
4454 cacheHeaderRegion
.permissions
= VM_PROT_READ
;
4455 cacheHeaderRegion
.name
= "__TEXT";
4458 for (const auto& regionAndVMOffset
: regionsVMOrder
) {
4459 printf("0x%llx : %s\n", regionAndVMOffset
.first
->unslidLoadAddress
, regionAndVMOffset
.first
->name
.c_str());
4463 CacheHeader64
& header
= cacheHeader
;
4464 header
.header
= (mach_header_64
*)cacheHeaderRegion
.buffer
;
4465 header
.numLoadCommands
= cacheNumLoadCommands
;
4466 header
.loadCommandsSize
= cacheLoadCommandsSize
;
4467 header
.uuid
= (uuid_command
*)(cacheHeaderRegion
.buffer
+ uuidOffset
);
4468 header
.buildVersion
= (build_version_command
*)(cacheHeaderRegion
.buffer
+ buildVersionOffset
);
4469 if ( unixThread
!= nullptr ) {
4470 header
.unixThread
= (thread_command
*)(cacheHeaderRegion
.buffer
+ unixThreadOffset
);
4471 // Copy the contents here while we have the source pointer available
4472 memcpy(header
.unixThread
, unixThread
, unixThread
->cmdsize
);
4475 if ( symbolTableOffset
!= 0 ) {
4476 header
.symbolTable
= (symtab_command
*)(cacheHeaderRegion
.buffer
+ symbolTableOffset
);
4479 if ( dynSymbolTableOffset
!= 0 ) {
4480 header
.dynSymbolTable
= (dysymtab_command
*)(cacheHeaderRegion
.buffer
+ dynSymbolTableOffset
);
4483 if ( chainedFixupsOffset
!= 0 ) {
4484 header
.chainedFixups
= (linkedit_data_command
*)(cacheHeaderRegion
.buffer
+ chainedFixupsOffset
);
4487 for (auto& regionAndOffset
: regions
) {
4488 assert(regionAndOffset
.first
->permissions
!= 0);
4489 segment_command_64
* loadCommand
= (segment_command_64
*)(cacheHeaderRegion
.buffer
+ regionAndOffset
.second
);
4490 header
.segments
.push_back({ loadCommand
, regionAndOffset
.first
});
4492 for (const auto& dylibAndOffset
: dylibs
) {
4493 fileset_entry_command
* loadCommand
= (fileset_entry_command
*)(cacheHeaderRegion
.buffer
+ dylibAndOffset
.second
);
4494 header
.dylibs
.push_back({ loadCommand
, dylibAndOffset
.first
});
4497 // Move the offsets of all the other regions
4499 readOnlyTextRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4501 // Split seg __TEXT_EXEC
4502 readExecuteRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4505 branchStubsRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4507 // Split seg __DATA_CONST
4508 dataConstRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4511 branchGOTsRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4514 readWriteRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4517 hibernateRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4520 for (Region
& region
: customDataRegions
) {
4521 region
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4524 // Non split seg regions
4525 for (Region
& region
: nonSplitSegRegions
) {
4526 region
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4530 prelinkInfoRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4533 _readOnlyRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4535 // __LINKEDIT fixups sub region
4536 fixupsSubRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4542 void AppCacheBuilder::generateCacheHeader() {
4544 assert(0 && "Unimplemented");
4548 typedef Pointer64
<LittleEndian
> P
;
4549 CacheHeader64
& header
= cacheHeader
;
4552 macho_header
<P
>* mh
= (macho_header
<P
>*)header
.header
;
4553 mh
->set_magic(MH_MAGIC_64
);
4554 mh
->set_cputype(_options
.archs
->_orderedCpuTypes
[0].type
);
4555 mh
->set_cpusubtype(_options
.archs
->_orderedCpuTypes
[0].subtype
);
4556 mh
->set_filetype(MH_FILESET
);
4557 mh
->set_ncmds((uint32_t)header
.numLoadCommands
);
4558 mh
->set_sizeofcmds((uint32_t)header
.loadCommandsSize
);
4560 mh
->set_reserved(0);
4562 // FIXME: Move this to writeAppCacheHeader
4564 macho_uuid_command
<P
>* cmd
= (macho_uuid_command
<P
>*)header
.uuid
;
4565 cmd
->set_cmd(LC_UUID
);
4566 cmd
->set_cmdsize(sizeof(uuid_command
));
4567 cmd
->set_uuid((uuid_t
){});
4570 // FIXME: Move this to writeAppCacheHeader
4572 macho_build_version_command
<P
>* cmd
= (macho_build_version_command
<P
>*)header
.buildVersion
;
4573 cmd
->set_cmd(LC_BUILD_VERSION
);
4574 cmd
->set_cmdsize(sizeof(build_version_command
));
4575 cmd
->set_platform((uint32_t)_options
.platform
);
4581 // FIXME: Move this to writeAppCacheHeader
4582 // LC_UNIXTHREAD was already memcpy()'ed from the source dylib when we allocated space for it
4583 // We still need to slide its PC value here before we lose the information about the slide
4584 if ( header
.unixThread
!= nullptr ) {
4585 const DylibInfo
* dylib
= getKernelStaticExecutableInputFile();
4586 const dyld3::MachOAnalyzer
* ma
= dylib
->input
->mappedFile
.mh
;
4587 ma
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
4588 uint64_t startAddress
= dylib
->input
->mappedFile
.mh
->entryAddrFromThreadCmd(header
.unixThread
);
4589 if ( (startAddress
< info
.vmAddr
) || (startAddress
>= (info
.vmAddr
+ info
.vmSize
)) )
4592 uint64_t segSlide
= dylib
->cacheLocation
[info
.segIndex
].dstCacheUnslidAddress
- info
.vmAddr
;
4593 startAddress
+= segSlide
;
4595 macho_thread_command
<P
>* cmd
= (macho_thread_command
<P
>*)header
.unixThread
;
4596 cmd
->set_thread_register(ma
->entryAddrRegisterIndexForThreadCmd(), startAddress
);
4602 if ( header
.symbolTable
!= nullptr ) {
4603 macho_symtab_command
<P
>* cmd
= (macho_symtab_command
<P
>*)header
.symbolTable
;
4604 cmd
->set_cmd(LC_SYMTAB
);
4605 cmd
->set_cmdsize(sizeof(symtab_command
));
4609 cmd
->set_strsize(0);
4612 if ( header
.dynSymbolTable
!= nullptr ) {
4613 macho_dysymtab_command
<P
>* cmd
= (macho_dysymtab_command
<P
>*)header
.dynSymbolTable
;
4614 cmd
->set_cmd(LC_DYSYMTAB
);
4615 cmd
->set_cmdsize(sizeof(dysymtab_command
));
4616 cmd
->set_ilocalsym(0);
4617 cmd
->set_nlocalsym(0);
4618 cmd
->set_iextdefsym(0);
4619 cmd
->set_nextdefsym(0);
4620 cmd
->set_iundefsym(0);
4621 cmd
->set_nundefsym(0);
4624 cmd
->set_modtaboff(0);
4625 cmd
->set_nmodtab(0);
4626 cmd
->set_extrefsymoff(0);
4627 cmd
->set_nextrefsyms(0);
4628 cmd
->set_indirectsymoff(0);
4629 cmd
->set_nindirectsyms(0);
4630 cmd
->set_extreloff(0);
4631 cmd
->set_nextrel(0);
4632 cmd
->set_locreloff(0);
4633 cmd
->set_nlocrel(0);
4636 if ( header
.chainedFixups
!= nullptr ) {
4637 macho_linkedit_data_command
<P
>* cmd
= (macho_linkedit_data_command
<P
>*)header
.chainedFixups
;
4638 cmd
->set_cmd(LC_DYLD_CHAINED_FIXUPS
);
4639 cmd
->set_cmdsize(sizeof(linkedit_data_command
));
4640 cmd
->set_dataoff(0);
4641 cmd
->set_datasize(0);
4644 // FIXME: Move this to writeAppCacheHeader
4645 uint64_t segmentIndex
= 0;
4646 for (CacheHeader64::SegmentCommandAndRegion
& cmdAndInfo
: header
.segments
) {
4647 macho_segment_command
<P
>* cmd
= (macho_segment_command
<P
>*)cmdAndInfo
.first
;
4648 Region
* region
= cmdAndInfo
.second
;
4649 region
->index
= segmentIndex
;
4652 assert(region
->permissions
!= 0);
4654 const char* name
= region
->name
.c_str();
4656 cmd
->set_cmd(LC_SEGMENT_64
);
4657 cmd
->set_cmdsize(sizeof(segment_command_64
));
4658 cmd
->set_segname(name
);
4659 cmd
->set_vmaddr(region
->unslidLoadAddress
);
4660 cmd
->set_vmsize(region
->sizeInUse
);
4661 cmd
->set_fileoff(region
->cacheFileOffset
);
4662 cmd
->set_filesize(region
->sizeInUse
);
4663 cmd
->set_maxprot(region
->permissions
);
4664 cmd
->set_initprot(region
->permissions
);
4668 if ( region
== &readOnlyTextRegion
) {
4669 // __PRELINK_TEXT should also get a section
4670 cmd
->set_cmdsize(cmd
->cmdsize() + sizeof(section_64
));
4673 macho_section
<P
>* section
= (macho_section
<P
>*)((uint64_t)cmd
+ sizeof(*cmd
));
4674 section
->set_sectname("__text");
4675 section
->set_segname(name
);
4676 section
->set_addr(region
->unslidLoadAddress
);
4677 section
->set_size(region
->sizeInUse
);
4678 section
->set_offset((uint32_t)region
->cacheFileOffset
);
4679 section
->set_align(0);
4680 section
->set_reloff(0);
4681 section
->set_nreloc(0);
4682 section
->set_flags(S_REGULAR
| S_ATTR_SOME_INSTRUCTIONS
| S_ATTR_PURE_INSTRUCTIONS
);
4683 section
->set_reserved1(0);
4684 section
->set_reserved2(0);
4685 } else if ( region
== &prelinkInfoRegion
) {
4686 // __PRELINK_INFO should also get a section
4687 cmd
->set_cmdsize(cmd
->cmdsize() + sizeof(section_64
));
4690 macho_section
<P
>* section
= (macho_section
<P
>*)((uint64_t)cmd
+ sizeof(*cmd
));
4691 section
->set_sectname("__info");
4692 section
->set_segname(name
);
4693 section
->set_addr(region
->unslidLoadAddress
);
4694 section
->set_size(region
->sizeInUse
);
4695 section
->set_offset((uint32_t)region
->cacheFileOffset
);
4696 section
->set_align(0);
4697 section
->set_reloff(0);
4698 section
->set_nreloc(0);
4699 section
->set_flags(S_REGULAR
);
4700 section
->set_reserved1(0);
4701 section
->set_reserved2(0);
4702 } else if ( region
== &hibernateRegion
) {
4703 // __HIB should also get a section
4704 cmd
->set_cmdsize(cmd
->cmdsize() + sizeof(section_64
));
4707 macho_section
<P
>* section
= (macho_section
<P
>*)((uint64_t)cmd
+ sizeof(*cmd
));
4708 section
->set_sectname("__text");
4709 section
->set_segname(name
);
4710 section
->set_addr(region
->unslidLoadAddress
);
4711 section
->set_size(region
->sizeInUse
);
4712 section
->set_offset((uint32_t)region
->cacheFileOffset
);
4713 section
->set_align(0);
4714 section
->set_reloff(0);
4715 section
->set_nreloc(0);
4716 section
->set_flags(S_REGULAR
| S_ATTR_SOME_INSTRUCTIONS
);
4717 section
->set_reserved1(0);
4718 section
->set_reserved2(0);
4720 // Custom segments may have sections
4721 for (CustomSegment
&customSegment
: customSegments
) {
4722 if ( region
!= customSegment
.parentRegion
)
4725 // Found a segment for this region. Now work out how many sections to emit
4726 // Maybe add sections too
4727 uint32_t sectionsToAdd
= 0;
4728 if ( customSegment
.sections
.size() > 1 ) {
4729 // More than one section, so they all need names
4730 sectionsToAdd
= (uint32_t)customSegment
.sections
.size();
4731 } else if ( !customSegment
.sections
.front().sectionName
.empty() ) {
4732 // Only one section, but it has a name
4735 // Only 1 section, and it has no name, so don't add a section
4739 cmd
->set_cmdsize(cmd
->cmdsize() + (sizeof(section_64
) * sectionsToAdd
));
4740 cmd
->set_nsects(sectionsToAdd
);
4741 uint8_t* bufferPos
= (uint8_t*)cmd
+ sizeof(*cmd
);
4742 for (const CustomSegment::CustomSection
& customSection
: customSegment
.sections
) {
4743 macho_section
<P
>* section
= (macho_section
<P
>*)bufferPos
;
4744 section
->set_sectname(customSection
.sectionName
.c_str());
4745 section
->set_segname(name
);
4746 section
->set_addr(region
->unslidLoadAddress
+ customSection
.offsetInRegion
);
4747 section
->set_size(customSection
.data
.size());
4748 section
->set_offset((uint32_t)(region
->cacheFileOffset
+ customSection
.offsetInRegion
));
4749 section
->set_align(0);
4750 section
->set_reloff(0);
4751 section
->set_nreloc(0);
4752 section
->set_flags(S_REGULAR
);
4753 section
->set_reserved1(0);
4754 section
->set_reserved2(0);
4756 bufferPos
+= sizeof(section_64
);
4762 // Write the dylibs. These are all we need for now to be able to walk the
4764 for (CacheHeader64::DylibCommandAndInfo
& cmdAndInfo
: header
.dylibs
) {
4765 macho_fileset_entry_command
<P
>* cmd
= (macho_fileset_entry_command
<P
>*)cmdAndInfo
.first
;
4766 const DylibInfo
* dylib
= cmdAndInfo
.second
;
4768 const char* dylibID
= dylib
->dylibID
.c_str();
4769 uint64_t size
= align(sizeof(fileset_entry_command
) + strlen(dylibID
) + 1, 3);
4771 cmd
->set_cmd(LC_FILESET_ENTRY
);
4772 cmd
->set_cmdsize((uint32_t)size
);
4773 cmd
->set_vmaddr(dylib
->cacheLocation
[0].dstCacheUnslidAddress
);
4774 cmd
->set_fileoff(dylib
->cacheLocation
[0].dstCacheFileOffset
);
4775 cmd
->set_entry_id(dylibID
);
4780 void AppCacheBuilder::generatePrelinkInfo() {
4781 if ( prelinkInfoDict
== nullptr ) {
4782 // The kernel doesn't need a prelink dictionary just for itself
4783 bool needsPrelink
= true;
4784 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::kernel
) {
4785 if ( sortedDylibs
.size() == 1 )
4786 needsPrelink
= false;
4788 if ( needsPrelink
) {
4789 _diagnostics
.error("Expected prelink info dictionary");
4794 CFMutableArrayRef arrayRef
= (CFMutableArrayRef
)CFDictionaryGetValue(prelinkInfoDict
,
4795 CFSTR("_PrelinkInfoDictionary"));
4796 if ( arrayRef
== nullptr ) {
4797 _diagnostics
.error("Expected prelink info dictionary array");
4801 typedef std::pair
<const dyld3::MachOAnalyzer
*, Diagnostics
*> DylibAndDiag
;
4802 __block
std::unordered_map
<std::string_view
, DylibAndDiag
> dylibs
;
4803 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
4804 DylibStripMode stripMode
, const std::vector
<std::string
>& dependencies
,
4805 Diagnostics
& dylibDiag
, bool& stop
) {
4806 dylibs
[dylibID
] = { ma
, &dylibDiag
};
4808 for (const InputDylib
& dylib
: codelessKexts
) {
4809 dylibs
[dylib
.dylibID
] = { nullptr, nullptr };
4812 __block
std::list
<std::string
> nonASCIIStrings
;
4813 auto getString
= ^(Diagnostics
& diags
, CFStringRef symbolNameRef
) {
4814 const char* symbolName
= CFStringGetCStringPtr(symbolNameRef
, kCFStringEncodingUTF8
);
4815 if ( symbolName
!= nullptr )
4818 CFIndex len
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef
), kCFStringEncodingUTF8
);
4819 char buffer
[len
+ 1];
4820 if ( !CFStringGetCString(symbolNameRef
, buffer
, len
, kCFStringEncodingUTF8
) ) {
4821 diags
.error("Could not convert string to ASCII");
4822 return (const char*)nullptr;
4825 nonASCIIStrings
.push_back(buffer
);
4826 return nonASCIIStrings
.back().c_str();
4829 bool badKext
= false;
4830 CFIndex arrayCount
= CFArrayGetCount(arrayRef
);
4831 for (CFIndex i
= 0; i
!= arrayCount
; ++i
) {
4832 CFMutableDictionaryRef dictRef
= (CFMutableDictionaryRef
)CFArrayGetValueAtIndex(arrayRef
, i
);
4834 CFStringRef bundleIDRef
= (CFStringRef
)CFDictionaryGetValue(dictRef
, CFSTR("CFBundleIdentifier"));
4835 if ( bundleIDRef
== nullptr ) {
4836 _diagnostics
.error("Cannot get bundle ID for dylib");
4840 const char* bundleIDStr
= getString(_diagnostics
, bundleIDRef
);
4841 if ( _diagnostics
.hasError() )
4844 auto dylibIt
= dylibs
.find(bundleIDStr
);
4845 if ( dylibIt
== dylibs
.end() ) {
4846 _diagnostics
.error("Cannot get dylib for bundle ID %s", bundleIDStr
);
4849 const dyld3::MachOAnalyzer
*ma
= dylibIt
->second
.first
;
4850 Diagnostics
* dylibDiag
= dylibIt
->second
.second
;
4851 // Skip codeless kext's
4852 if ( ma
== nullptr )
4854 uint64_t loadAddress
= ma
->preferredLoadAddress();
4856 // _PrelinkExecutableLoadAddr
4857 CFNumberRef loadAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &loadAddress
);
4858 CFDictionarySetValue(dictRef
, CFSTR("_PrelinkExecutableLoadAddr"), loadAddrRef
);
4859 CFRelease(loadAddrRef
);
4861 // _PrelinkExecutableSourceAddr
4862 CFNumberRef sourceAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &loadAddress
);
4863 CFDictionarySetValue(dictRef
, CFSTR("_PrelinkExecutableSourceAddr"), sourceAddrRef
);
4864 CFRelease(sourceAddrRef
);
4867 __block
uint64_t kmodInfoAddress
= 0;
4869 // Check for a global first
4870 __block
bool found
= false;
4872 dyld3::MachOAnalyzer::FoundSymbol foundInfo
;
4873 found
= ma
->findExportedSymbol(_diagnostics
, "_kmod_info", true, foundInfo
, nullptr);
4875 kmodInfoAddress
= loadAddress
+ foundInfo
.value
;
4878 // And fall back to a local if we need to
4880 ma
->forEachLocalSymbol(_diagnostics
, ^(const char* aSymbolName
, uint64_t n_value
, uint8_t n_type
,
4881 uint8_t n_sect
, uint16_t n_desc
, bool& stop
) {
4882 if ( strcmp(aSymbolName
, "_kmod_info") == 0 ) {
4883 kmodInfoAddress
= n_value
;
4891 CFNumberRef kmodInfoAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &kmodInfoAddress
);
4892 CFDictionarySetValue(dictRef
, CFSTR("_PrelinkKmodInfo"), kmodInfoAddrRef
);
4893 CFRelease(kmodInfoAddrRef
);
4895 // Since we have a reference to the kmod info anyway, set its address field to the correct value
4897 uint64_t kmodInfoVMOffset
= kmodInfoAddress
- loadAddress
;
4898 dyld3::MachOAppCache::KModInfo64_v1
* kmodInfo
= (dyld3::MachOAppCache::KModInfo64_v1
*)((uint8_t*)ma
+ kmodInfoVMOffset
);
4899 if ( kmodInfo
->info_version
!= 1 ) {
4900 dylibDiag
->error("unsupported kmod_info version of %d", kmodInfo
->info_version
);
4904 __block
uint64_t textSegmnentVMAddr
= 0;
4905 __block
uint64_t textSegmnentVMSize
= 0;
4906 ma
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
4907 if ( !strcmp(info
.segName
, "__TEXT") ) {
4908 textSegmnentVMAddr
= info
.vmAddr
;
4909 textSegmnentVMSize
= info
.vmSize
;
4913 kmodInfo
->address
= textSegmnentVMAddr
;
4914 kmodInfo
->size
= textSegmnentVMSize
;
4918 CFErrorRef errorRef
= nullptr;
4919 CFDataRef xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, prelinkInfoDict
,
4920 kCFPropertyListXMLFormat_v1_0
, 0, &errorRef
);
4921 if (errorRef
!= nullptr) {
4922 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
4923 _diagnostics
.error("Could not serialise plist because :%s",
4924 CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
4926 CFRelease(errorRef
);
4929 CFIndex xmlDataLength
= CFDataGetLength(xmlData
);
4930 if ( xmlDataLength
> prelinkInfoRegion
.bufferSize
) {
4931 _diagnostics
.error("Overflow in prelink info segment. 0x%llx vs 0x%llx",
4932 (uint64_t)xmlDataLength
, prelinkInfoRegion
.bufferSize
);
4937 // Write the prelink info in to the buffer
4938 memcpy(prelinkInfoRegion
.buffer
, CFDataGetBytePtr(xmlData
), xmlDataLength
);
4942 if ( badKext
&& _diagnostics
.noError() ) {
4943 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
4947 bool AppCacheBuilder::addCustomSection(const std::string
& segmentName
,
4948 CustomSegment::CustomSection section
) {
4949 for (CustomSegment
& segment
: customSegments
) {
4950 if ( segment
.segmentName
!= segmentName
)
4953 // Found a matching segment
4954 // Make sure we don't have a section with this name already
4955 if ( section
.sectionName
.empty() ) {
4956 // We can't add a segment only section if other sections exist
4957 _diagnostics
.error("Cannot add empty section name with segment '%s' as other sections exist on that segment",
4958 segmentName
.c_str());
4962 for (const CustomSegment::CustomSection
& existingSection
: segment
.sections
) {
4963 if ( existingSection
.sectionName
.empty() ) {
4964 // We can't add a section with a name if an existing section exists with no name
4965 _diagnostics
.error("Cannot add section named '%s' with segment '%s' as segment has existing nameless section",
4966 segmentName
.c_str(), section
.sectionName
.c_str());
4969 if ( existingSection
.sectionName
== section
.sectionName
) {
4970 // We can't add a section with the same name as an existing one
4971 _diagnostics
.error("Cannot add section named '%s' with segment '%s' as section already exists",
4972 segmentName
.c_str(), section
.sectionName
.c_str());
4976 segment
.sections
.push_back(section
);
4980 // Didn't find a segment, so add a new one
4981 CustomSegment segment
;
4982 segment
.segmentName
= segmentName
;
4983 segment
.sections
.push_back(section
);
4984 customSegments
.push_back(segment
);
4988 void AppCacheBuilder::setExistingKernelCollection(const dyld3::MachOAppCache
* appCacheMA
) {
4989 existingKernelCollection
= appCacheMA
;
4992 void AppCacheBuilder::setExistingPageableKernelCollection(const dyld3::MachOAppCache
* appCacheMA
) {
4993 pageableKernelCollection
= appCacheMA
;
4996 void AppCacheBuilder::setExtraPrelinkInfo(CFDictionaryRef dictionary
) {
4997 extraPrelinkInfo
= dictionary
;
5001 inline uint32_t absolutetime_to_milliseconds(uint64_t abstime
)
5003 return (uint32_t)(abstime
/1000/1000);
5006 void AppCacheBuilder::buildAppCache(const std::vector
<InputDylib
>& dylibs
)
5008 uint64_t t1
= mach_absolute_time();
5010 // make copy of dylib list and sort
5011 makeSortedDylibs(dylibs
);
5013 // Set the chained pointer format
5014 // x86_64 uses unaligned fixups
5015 if ( (_options
.archs
== &dyld3::GradedArchs::x86_64
) || (_options
.archs
== &dyld3::GradedArchs::x86_64h
) ) {
5016 chainedPointerFormat
= DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE
;
5018 chainedPointerFormat
= DYLD_CHAINED_PTR_64_KERNEL_CACHE
;
5021 // If we have only codeless kexts, then error out
5022 if ( sortedDylibs
.empty() ) {
5023 if ( codelessKexts
.empty() ) {
5024 _diagnostics
.error("No binaries or codeless kexts were provided");
5026 _diagnostics
.error("Cannot build collection without binaries as only %lx codeless kexts provided",
5027 codelessKexts
.size());
5032 // assign addresses for each segment of each dylib in new cache
5033 assignSegmentRegionsAndOffsets();
5034 if ( _diagnostics
.hasError() )
5037 // allocate space used by largest possible cache plus room for LINKEDITS before optimization
5039 if ( _diagnostics
.hasError() )
5042 assignSegmentAddresses();
5044 generateCacheHeader();
5046 // copy all segments into cache
5047 uint64_t t2
= mach_absolute_time();
5050 // rebase all dylibs for new location in cache
5051 uint64_t t3
= mach_absolute_time();
5052 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::auxKC
) {
5053 // We can have text fixups in the auxKC so ASLR should just track the whole buffer
5054 __block
const Region
* firstDataRegion
= nullptr;
5055 __block
const Region
* lastDataRegion
= nullptr;
5056 forEachRegion(^(const Region
®ion
) {
5057 if ( (firstDataRegion
== nullptr) || (region
.buffer
< firstDataRegion
->buffer
) )
5058 firstDataRegion
= ®ion
;
5059 if ( (lastDataRegion
== nullptr) || (region
.buffer
> lastDataRegion
->buffer
) )
5060 lastDataRegion
= ®ion
;
5063 if ( firstDataRegion
!= nullptr ) {
5064 uint64_t size
= (lastDataRegion
->buffer
- firstDataRegion
->buffer
) + lastDataRegion
->bufferSize
;
5065 _aslrTracker
.setDataRegion(firstDataRegion
->buffer
, size
);
5068 const Region
* firstDataRegion
= nullptr;
5069 const Region
* lastDataRegion
= nullptr;
5070 if ( hibernateRegion
.sizeInUse
!= 0 ) {
5071 firstDataRegion
= &hibernateRegion
;
5072 lastDataRegion
= &hibernateRegion
;
5075 if ( dataConstRegion
.sizeInUse
!= 0 ) {
5076 if ( firstDataRegion
== nullptr )
5077 firstDataRegion
= &dataConstRegion
;
5078 if ( (lastDataRegion
== nullptr) || (dataConstRegion
.buffer
> lastDataRegion
->buffer
) )
5079 lastDataRegion
= &dataConstRegion
;
5082 if ( branchGOTsRegion
.bufferSize
!= 0 ) {
5083 if ( firstDataRegion
== nullptr )
5084 firstDataRegion
= &branchGOTsRegion
;
5085 if ( (lastDataRegion
== nullptr) || (branchGOTsRegion
.buffer
> lastDataRegion
->buffer
) )
5086 lastDataRegion
= &branchGOTsRegion
;
5089 if ( readWriteRegion
.sizeInUse
!= 0 ) {
5090 // __DATA might be before __DATA_CONST in an auxKC
5091 if ( (firstDataRegion
== nullptr) || (readWriteRegion
.buffer
< firstDataRegion
->buffer
) )
5092 firstDataRegion
= &readWriteRegion
;
5093 if ( (lastDataRegion
== nullptr) || (readWriteRegion
.buffer
> lastDataRegion
->buffer
) )
5094 lastDataRegion
= &readWriteRegion
;
5097 for (const Region
& region
: nonSplitSegRegions
) {
5098 // Assume writable regions have fixups to emit
5099 // Note, third party kext's have __TEXT fixups, so assume all of these have fixups
5100 // LINKEDIT is already elsewhere
5101 if ( readWriteRegion
.sizeInUse
!= 0 ) {
5102 assert(region
.buffer
>= readWriteRegion
.buffer
);
5104 if ( firstDataRegion
== nullptr )
5105 firstDataRegion
= ®ion
;
5106 if ( (lastDataRegion
== nullptr) || (region
.buffer
> lastDataRegion
->buffer
) )
5107 lastDataRegion
= ®ion
;
5110 if ( firstDataRegion
!= nullptr ) {
5111 uint64_t size
= (lastDataRegion
->buffer
- firstDataRegion
->buffer
) + lastDataRegion
->bufferSize
;
5112 _aslrTracker
.setDataRegion(firstDataRegion
->buffer
, size
);
5115 adjustAllImagesForNewSegmentLocations(cacheBaseAddress
, _aslrTracker
, nullptr, nullptr);
5116 if ( _diagnostics
.hasError() )
5119 // Once we have the final addresses, we can emit the prelink info segment
5120 generatePrelinkInfo();
5121 if ( _diagnostics
.hasError() )
5124 // build ImageArray for dyld3, which has side effect of binding all cached dylibs
5125 uint64_t t4
= mach_absolute_time();
5127 if ( _diagnostics
.hasError() )
5130 uint64_t t5
= mach_absolute_time();
5132 // optimize away stubs
5133 uint64_t t6
= mach_absolute_time();
5135 __block
std::vector
<std::pair
<const mach_header
*, const char*>> images
;
5136 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
5137 DylibStripMode stripMode
, const std::vector
<std::string
>& dependencies
,
5138 Diagnostics
& dylibDiag
, bool& stop
) {
5139 images
.push_back({ ma
, dylibID
.c_str() });
5141 // FIXME: Should we keep the same never stub eliminate symbols? Eg, for gmalloc.
5142 const char* const neverStubEliminateSymbols
[] = {
5146 uint64_t cacheUnslidAddr
= cacheBaseAddress
;
5147 int64_t cacheSlide
= (long)_fullAllocatedBuffer
- cacheUnslidAddr
;
5148 optimizeAwayStubs(images
, cacheSlide
, cacheUnslidAddr
,
5149 nullptr, neverStubEliminateSymbols
);
5152 // FIPS seal corecrypto, This must be done after stub elimination (so that __TEXT,__text is not changed after sealing)
5155 // merge and compact LINKEDIT segments
5156 uint64_t t7
= mach_absolute_time();
5158 __block
std::vector
<std::tuple
<const mach_header
*, const char*, DylibStripMode
>> images
;
5159 __block
std::set
<const mach_header
*> imagesToStrip
;
5160 __block
const dyld3::MachOAnalyzer
* kernelMA
= nullptr;
5161 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
5162 DylibStripMode stripMode
, const std::vector
<std::string
>& dependencies
,
5163 Diagnostics
& dylibDiag
, bool& stop
) {
5164 if ( stripMode
== DylibStripMode::stripNone
) {
5165 // If the binary didn't have a strip mode, then use the global mode
5166 switch (appCacheOptions
.cacheKind
) {
5167 case Options::AppCacheKind::none
:
5168 assert("Unhandled kind");
5170 case Options::AppCacheKind::kernel
:
5171 switch (appCacheOptions
.stripMode
) {
5172 case Options::StripMode::none
:
5174 case Options::StripMode::all
:
5175 stripMode
= CacheBuilder::DylibStripMode::stripAll
;
5177 case Options::StripMode::allExceptKernel
:
5178 // Strip all binaries which are not the kernel
5179 if ( kernelMA
== nullptr ) {
5180 kernelMA
= getKernelStaticExecutableFromCache();
5182 if ( ma
!= kernelMA
)
5183 stripMode
= CacheBuilder::DylibStripMode::stripAll
;
5187 case Options::AppCacheKind::pageableKC
:
5188 assert("Unhandled kind");
5190 case Options::AppCacheKind::kernelCollectionLevel2
:
5191 assert("Unhandled kind");
5193 case Options::AppCacheKind::auxKC
:
5194 assert("Unhandled kind");
5198 images
.push_back({ ma
, dylibID
.c_str(), stripMode
});
5200 optimizeLinkedit(nullptr, images
);
5202 // update final readOnly region size
5204 assert(0 && "Unimplemented");
5208 typedef Pointer64
<LittleEndian
> P
;
5209 CacheHeader64
& header
= cacheHeader
;
5211 for (CacheHeader64::SegmentCommandAndRegion
& cmdAndRegion
: header
.segments
) {
5212 if (cmdAndRegion
.second
!= &_readOnlyRegion
)
5214 cmdAndRegion
.first
->vmsize
= _readOnlyRegion
.sizeInUse
;
5215 cmdAndRegion
.first
->filesize
= _readOnlyRegion
.sizeInUse
;
5221 uint64_t t8
= mach_absolute_time();
5223 uint64_t t9
= mach_absolute_time();
5225 // Add fixups to rebase/bind the app cache
5229 assert(0 && "Unimplemented");
5231 // update final readOnly region size
5234 typedef Pointer64
<LittleEndian
> P
;
5235 CacheHeader64
& header
= cacheHeader
;
5237 for (CacheHeader64::SegmentCommandAndRegion
& cmdAndRegion
: header
.segments
) {
5238 if (cmdAndRegion
.second
!= &_readOnlyRegion
)
5240 cmdAndRegion
.first
->vmsize
= _readOnlyRegion
.sizeInUse
;
5241 cmdAndRegion
.first
->filesize
= _readOnlyRegion
.sizeInUse
;
5247 // FIXME: We could move _aslrTracker to a worker thread to be destroyed as we don't need it
5250 uint64_t t10
= mach_absolute_time();
5253 if ( _diagnostics
.hasError() )
5256 uint64_t t11
= mach_absolute_time();
5258 if ( _options
.verbose
) {
5259 fprintf(stderr
, "time to layout cache: %ums\n", absolutetime_to_milliseconds(t2
-t1
));
5260 fprintf(stderr
, "time to copy cached dylibs into buffer: %ums\n", absolutetime_to_milliseconds(t3
-t2
));
5261 fprintf(stderr
, "time to adjust segments for new split locations: %ums\n", absolutetime_to_milliseconds(t4
-t3
));
5262 fprintf(stderr
, "time to bind all images: %ums\n", absolutetime_to_milliseconds(t5
-t4
));
5263 fprintf(stderr
, "time to optimize Objective-C: %ums\n", absolutetime_to_milliseconds(t6
-t5
));
5264 fprintf(stderr
, "time to do stub elimination: %ums\n", absolutetime_to_milliseconds(t7
-t6
));
5265 fprintf(stderr
, "time to optimize LINKEDITs: %ums\n", absolutetime_to_milliseconds(t8
-t7
));
5266 fprintf(stderr
, "time to compute slide info: %ums\n", absolutetime_to_milliseconds(t10
-t9
));
5267 fprintf(stderr
, "time to compute UUID and codesign cache file: %ums\n", absolutetime_to_milliseconds(t11
-t10
));
5271 void AppCacheBuilder::fipsSign()
5273 if ( appCacheOptions
.cacheKind
!= Options::AppCacheKind::kernel
)
5276 // find com.apple.kec.corecrypto in collection being built
5277 __block
const dyld3::MachOAnalyzer
* kextMA
= nullptr;
5278 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
5279 DylibStripMode stripMode
, const std::vector
<std::string
>& dependencies
,
5280 Diagnostics
& dylibDiag
, bool& stop
) {
5281 if ( dylibID
== "com.apple.kec.corecrypto" ) {
5287 if ( kextMA
== nullptr ) {
5288 _diagnostics
.warning("Could not find com.apple.kec.corecrypto, skipping FIPS sealing");
5292 // find location in com.apple.kec.corecrypto to store hash of __text section
5293 uint64_t hashStoreSize
;
5294 const void* hashStoreLocation
= kextMA
->findSectionContent("__TEXT", "__fips_hmacs", hashStoreSize
);
5295 if ( hashStoreLocation
== nullptr ) {
5296 _diagnostics
.warning("Could not find __TEXT/__fips_hmacs section in com.apple.kec.corecrypto, skipping FIPS sealing");
5299 if ( hashStoreSize
!= 32 ) {
5300 _diagnostics
.warning("__TEXT/__fips_hmacs section in com.apple.kec.corecrypto is not 32 bytes in size, skipping FIPS sealing");
5304 // compute hmac hash of __text section. It may be in __TEXT_EXEC or __TEXT
5306 const void* textLocation
= kextMA
->findSectionContent("__TEXT", "__text", textSize
);
5307 if ( textLocation
== nullptr ) {
5308 textLocation
= kextMA
->findSectionContent("__TEXT_EXEC", "__text", textSize
);
5310 if ( textLocation
== nullptr ) {
5311 _diagnostics
.warning("Could not find __TEXT/__text section in com.apple.kec.corecrypto, skipping FIPS sealing");
5314 unsigned char hmac_key
= 0;
5315 CCHmac(kCCHmacAlgSHA256
, &hmac_key
, 1, textLocation
, textSize
, (void*)hashStoreLocation
); // store hash directly into hashStoreLocation
5318 void AppCacheBuilder::generateUUID() {
5319 uint8_t* uuidLoc
= cacheHeader
.uuid
->uuid
;
5320 assert(uuid_is_null(uuidLoc
));
5322 CCDigestRef digestRef
= CCDigestCreate(kCCDigestSHA256
);
5323 forEachRegion(^(const Region
®ion
) {
5324 if ( _diagnostics
.hasError() )
5326 if ( region
.sizeInUse
== 0 )
5328 int result
= CCDigestUpdate(digestRef
, region
.buffer
, region
.sizeInUse
);
5329 if ( result
!= 0 ) {
5330 _diagnostics
.error("Could not generate UUID: %d", result
);
5334 if ( !_diagnostics
.hasError() ) {
5335 uint8_t buffer
[CCDigestGetOutputSize(kCCDigestSHA256
)];
5336 int result
= CCDigestFinal(digestRef
, buffer
);
5337 memcpy(cacheHeader
.uuid
->uuid
, buffer
, sizeof(cacheHeader
.uuid
->uuid
));
5338 if ( result
!= 0 ) {
5339 _diagnostics
.error("Could not finalize UUID: %d", result
);
5342 CCDigestDestroy(digestRef
);
5343 if ( _diagnostics
.hasError() )
5346 // Update the prelink info dictionary too
5347 if ( prelinkInfoDict
!= nullptr ) {
5348 CFDataRef dataRef
= CFDataCreate(kCFAllocatorDefault
, &cacheHeader
.uuid
->uuid
[0], sizeof(cacheHeader
.uuid
->uuid
));
5349 CFDictionarySetValue(prelinkInfoDict
, CFSTR("_PrelinkKCID"), dataRef
);
5352 CFErrorRef errorRef
= nullptr;
5353 CFDataRef xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, prelinkInfoDict
,
5354 kCFPropertyListXMLFormat_v1_0
, 0, &errorRef
);
5355 if (errorRef
!= nullptr) {
5356 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
5357 _diagnostics
.error("Could not serialise plist because :%s",
5358 CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
5360 CFRelease(errorRef
);
5363 CFIndex xmlDataLength
= CFDataGetLength(xmlData
);
5364 if ( xmlDataLength
> prelinkInfoRegion
.bufferSize
) {
5365 _diagnostics
.error("Overflow in prelink info segment. 0x%llx vs 0x%llx",
5366 (uint64_t)xmlDataLength
, prelinkInfoRegion
.bufferSize
);
5371 // Write the prelink info in to the buffer
5372 memcpy(prelinkInfoRegion
.buffer
, CFDataGetBytePtr(xmlData
), xmlDataLength
);
5379 void AppCacheBuilder::writeFile(const std::string
& path
)
5381 std::string pathTemplate
= path
+ "-XXXXXX";
5382 size_t templateLen
= strlen(pathTemplate
.c_str())+2;
5383 BLOCK_ACCCESSIBLE_ARRAY(char, pathTemplateSpace
, templateLen
);
5384 strlcpy(pathTemplateSpace
, pathTemplate
.c_str(), templateLen
);
5385 int fd
= mkstemp(pathTemplateSpace
);
5387 _diagnostics
.error("could not open file %s", pathTemplateSpace
);
5390 uint64_t cacheFileSize
= 0;
5391 // FIXME: Do we ever need to avoid allocating space for zero fill?
5392 cacheFileSize
= _readOnlyRegion
.cacheFileOffset
+ _readOnlyRegion
.sizeInUse
;
5394 // set final cache file size (may help defragment file)
5395 ::ftruncate(fd
, cacheFileSize
);
5397 // Write the whole buffer
5398 uint64_t writtenSize
= pwrite(fd
, (const uint8_t*)_fullAllocatedBuffer
, cacheFileSize
, 0);
5399 if (writtenSize
== cacheFileSize
) {
5400 ::fchmod(fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
5401 if ( ::rename(pathTemplateSpace
, path
.c_str()) == 0) {
5406 _diagnostics
.error("could not write whole file. %lld bytes out of %lld were written",
5407 writtenSize
, cacheFileSize
);
5411 ::unlink(pathTemplateSpace
);
5414 void AppCacheBuilder::writeBuffer(uint8_t*& buffer
, uint64_t& bufferSize
) const {
5415 bufferSize
= _readOnlyRegion
.cacheFileOffset
+ _readOnlyRegion
.sizeInUse
;
5416 buffer
= (uint8_t*)malloc(bufferSize
);
5418 forEachRegion(^(const Region
®ion
) {
5419 if ( region
.sizeInUse
== 0 )
5421 memcpy(buffer
+ region
.cacheFileOffset
, (const uint8_t*)region
.buffer
, region
.sizeInUse
);