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
.initProt
= VM_PROT_READ
;
390 readOnlyTextRegion
.maxProt
= VM_PROT_READ
;
391 readOnlyTextRegion
.name
= "__PRELINK_TEXT";
394 // __TEXT segments with r/x permissions
396 // __TEXT segments with r/x permissions
397 __block
uint64_t offsetInRegion
= 0;
398 for (DylibInfo
& dylib
: sortedDylibs
) {
399 bool canBePacked
= dylib
.input
->mappedFile
.mh
->hasSplitSeg();
403 __block
uint64_t textSegVmAddr
= 0;
404 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
405 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
406 textSegVmAddr
= segInfo
.vmAddr
;
407 if ( strcmp(segInfo
.segName
, "__HIB") == 0 )
409 if ( segInfo
.protections
!= (VM_PROT_READ
| VM_PROT_EXECUTE
) )
411 // kxld packs __TEXT_EXEC so we will do
412 // Note we align to at least 16-bytes as LDR's can scale up to 16 from their address
413 // and aligning them less than 16 would break that
414 uint32_t minAlignmentP2
= getMinAlignment(dylib
.input
->mappedFile
.mh
);
415 offsetInRegion
= align(offsetInRegion
, std::max(segInfo
.p2align
, 4U));
416 offsetInRegion
= align(offsetInRegion
, minAlignmentP2
);
417 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
418 uint64_t dstCacheSegmentSize
= align(segInfo
.sizeOfSections
, minAlignmentP2
);
419 SegmentMappingInfo loc
;
420 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
421 loc
.segName
= segInfo
.segName
;
422 loc
.dstSegment
= nullptr;
423 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
424 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
425 loc
.dstCacheSegmentSize
= (uint32_t)dstCacheSegmentSize
;
426 loc
.dstCacheFileSize
= (uint32_t)copySize
;
427 loc
.copySegmentSize
= (uint32_t)copySize
;
428 loc
.srcSegmentIndex
= segInfo
.segIndex
;
429 loc
.parentRegion
= &readExecuteRegion
;
430 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
431 offsetInRegion
+= loc
.dstCacheSegmentSize
;
435 // align r/x region end
436 readExecuteRegion
.bufferSize
= align(offsetInRegion
, 14);
437 readExecuteRegion
.sizeInUse
= readExecuteRegion
.bufferSize
;
438 readExecuteRegion
.initProt
= VM_PROT_READ
| VM_PROT_EXECUTE
;
439 readExecuteRegion
.maxProt
= VM_PROT_READ
| VM_PROT_EXECUTE
;
440 readExecuteRegion
.name
= "__TEXT_EXEC";
443 if ( branchTargetsFromKexts
!= 0 ) {
445 branchStubsRegion
.bufferSize
= align(branchTargetsFromKexts
* 6, 14);
446 branchStubsRegion
.sizeInUse
= branchStubsRegion
.bufferSize
;
447 branchStubsRegion
.initProt
= VM_PROT_READ
| VM_PROT_EXECUTE
;
448 branchStubsRegion
.maxProt
= VM_PROT_READ
| VM_PROT_EXECUTE
;
449 branchStubsRegion
.name
= "__BRANCH_STUBS";
452 // __DATA_CONST segments
454 __block
uint64_t offsetInRegion
= 0;
455 for (DylibInfo
& dylib
: sortedDylibs
) {
456 if (!dylib
.input
->mappedFile
.mh
->hasSplitSeg())
459 __block
uint64_t textSegVmAddr
= 0;
460 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
461 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
462 textSegVmAddr
= segInfo
.vmAddr
;
463 if ( (segInfo
.protections
& VM_PROT_EXECUTE
) != 0 )
465 if ( (strcmp(segInfo
.segName
, "__DATA_CONST") != 0)
466 && (strcmp(segInfo
.segName
, "__PPLDATA_CONST") != 0)
467 && (strcmp(segInfo
.segName
, "__LASTDATA_CONST") != 0) )
469 // kxld packs __DATA_CONST so we will do
470 uint32_t minAlignmentP2
= getMinAlignment(dylib
.input
->mappedFile
.mh
);
471 offsetInRegion
= align(offsetInRegion
, segInfo
.p2align
);
472 offsetInRegion
= align(offsetInRegion
, minAlignmentP2
);
473 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
474 uint64_t dstCacheSegmentSize
= align(segInfo
.sizeOfSections
, minAlignmentP2
);
475 SegmentMappingInfo loc
;
476 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
477 loc
.segName
= segInfo
.segName
;
478 loc
.dstSegment
= nullptr;
479 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
480 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
481 loc
.dstCacheSegmentSize
= (uint32_t)dstCacheSegmentSize
;
482 loc
.dstCacheFileSize
= (uint32_t)copySize
;
483 loc
.copySegmentSize
= (uint32_t)copySize
;
484 loc
.srcSegmentIndex
= segInfo
.segIndex
;
485 loc
.parentRegion
= &dataConstRegion
;
486 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
487 offsetInRegion
+= loc
.dstCacheSegmentSize
;
491 // align r/o region end
492 dataConstRegion
.bufferSize
= align(offsetInRegion
, 14);
493 dataConstRegion
.sizeInUse
= dataConstRegion
.bufferSize
;
494 dataConstRegion
.initProt
= VM_PROT_READ
;
495 dataConstRegion
.maxProt
= VM_PROT_READ
;
496 dataConstRegion
.name
= "__DATA_CONST";
500 if ( branchTargetsFromKexts
!= 0 ) {
502 branchGOTsRegion
.bufferSize
= align(branchTargetsFromKexts
* 8, 14);
503 branchGOTsRegion
.sizeInUse
= branchGOTsRegion
.bufferSize
;
504 branchGOTsRegion
.initProt
= VM_PROT_READ
| VM_PROT_WRITE
;
505 branchGOTsRegion
.maxProt
= VM_PROT_READ
| VM_PROT_WRITE
;
506 branchGOTsRegion
.name
= "__BRANCH_GOTS";
511 __block
uint64_t offsetInRegion
= 0;
512 for (DylibInfo
& dylib
: sortedDylibs
) {
513 if (!dylib
.input
->mappedFile
.mh
->hasSplitSeg())
516 __block
uint64_t textSegVmAddr
= 0;
517 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
518 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
519 textSegVmAddr
= segInfo
.vmAddr
;
520 if ( strcmp(segInfo
.segName
, "__HIB") == 0 )
522 if ( (strcmp(segInfo
.segName
, "__DATA_CONST") == 0)
523 || (strcmp(segInfo
.segName
, "__PPLDATA_CONST") == 0)
524 || (strcmp(segInfo
.segName
, "__LASTDATA_CONST") == 0) )
526 if ( segInfo
.protections
!= (VM_PROT_READ
| VM_PROT_WRITE
) )
528 // kxld packs __DATA so we will do
529 uint32_t minAlignmentP2
= getMinAlignment(dylib
.input
->mappedFile
.mh
);
530 offsetInRegion
= align(offsetInRegion
, segInfo
.p2align
);
531 offsetInRegion
= align(offsetInRegion
, minAlignmentP2
);
532 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
533 uint64_t dstCacheSegmentSize
= align(segInfo
.sizeOfSections
, minAlignmentP2
);
534 SegmentMappingInfo loc
;
535 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
536 loc
.segName
= segInfo
.segName
;
537 loc
.dstSegment
= nullptr;
538 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
539 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
540 loc
.dstCacheSegmentSize
= (uint32_t)dstCacheSegmentSize
;
541 loc
.dstCacheFileSize
= (uint32_t)copySize
;
542 loc
.copySegmentSize
= (uint32_t)copySize
;
543 loc
.srcSegmentIndex
= segInfo
.segIndex
;
544 loc
.parentRegion
= &readWriteRegion
;
545 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
546 offsetInRegion
+= loc
.dstCacheSegmentSize
;
550 // align r/w region end
551 readWriteRegion
.bufferSize
= align(offsetInRegion
, 14);
552 readWriteRegion
.sizeInUse
= readWriteRegion
.bufferSize
;
553 readWriteRegion
.initProt
= VM_PROT_READ
| VM_PROT_WRITE
;
554 readWriteRegion
.maxProt
= VM_PROT_READ
| VM_PROT_WRITE
;
555 readWriteRegion
.name
= "__DATA";
560 __block
uint64_t offsetInRegion
= 0;
561 for (DylibInfo
& dylib
: sortedDylibs
) {
562 if ( !dylib
.input
->mappedFile
.mh
->isStaticExecutable() )
565 __block
uint64_t textSegVmAddr
= 0;
566 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
567 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
568 textSegVmAddr
= segInfo
.vmAddr
;
569 if ( strcmp(segInfo
.segName
, "__HIB") != 0 )
571 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
572 SegmentMappingInfo loc
;
573 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
574 loc
.segName
= segInfo
.segName
;
575 loc
.dstSegment
= nullptr;
576 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
577 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
578 loc
.dstCacheSegmentSize
= (uint32_t)segInfo
.vmSize
;
579 loc
.dstCacheFileSize
= (uint32_t)copySize
;
580 loc
.copySegmentSize
= (uint32_t)copySize
;
581 loc
.srcSegmentIndex
= segInfo
.segIndex
;
582 loc
.parentRegion
= &hibernateRegion
;
583 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
584 offsetInRegion
+= loc
.dstCacheSegmentSize
;
586 hibernateAddress
= segInfo
.vmAddr
;
589 // Only xnu has __HIB, so no need to continue once we've found it.
593 hibernateRegion
.bufferSize
= align(offsetInRegion
, 14);
594 hibernateRegion
.sizeInUse
= hibernateRegion
.bufferSize
;
595 hibernateRegion
.initProt
= VM_PROT_READ
| VM_PROT_WRITE
| VM_PROT_EXECUTE
;
596 hibernateRegion
.maxProt
= VM_PROT_READ
| VM_PROT_WRITE
| VM_PROT_EXECUTE
;
597 hibernateRegion
.name
= "__HIB";
600 // __TEXT and __DATA from non-split seg dylibs, if we have any
602 for (DylibInfo
& dylib
: sortedDylibs
) {
603 bool canBePacked
= dylib
.input
->mappedFile
.mh
->hasSplitSeg();
607 __block
uint64_t textSegVmAddr
= 0;
608 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
609 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
610 textSegVmAddr
= segInfo
.vmAddr
;
611 if ( strcmp(segInfo
.segName
, "__LINKEDIT") == 0 )
614 nonSplitSegRegions
.emplace_back();
615 nonSplitSegRegions
.back().initProt
= segInfo
.protections
;
616 nonSplitSegRegions
.back().maxProt
= segInfo
.protections
;
617 nonSplitSegRegions
.back().name
= "__REGION" + std::to_string(nonSplitSegRegions
.size() - 1);
619 // Note we don't align the region offset as we have no split seg
620 uint64_t offsetInRegion
= 0;
621 SegmentMappingInfo loc
;
622 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
623 loc
.segName
= segInfo
.segName
;
624 loc
.dstSegment
= nullptr;
625 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
626 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
627 loc
.dstCacheSegmentSize
= (uint32_t)segInfo
.vmSize
;
628 loc
.dstCacheFileSize
= (uint32_t)segInfo
.fileSize
;
629 loc
.copySegmentSize
= (uint32_t)segInfo
.fileSize
;
630 loc
.srcSegmentIndex
= segInfo
.segIndex
;
631 loc
.parentRegion
= &nonSplitSegRegions
.back();
632 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
633 offsetInRegion
+= loc
.dstCacheSegmentSize
;
635 // record non-split seg region end
636 nonSplitSegRegions
.back().bufferSize
= offsetInRegion
;
637 nonSplitSegRegions
.back().sizeInUse
= nonSplitSegRegions
.back().bufferSize
;
643 if ( !customSegments
.empty() ) {
644 for (CustomSegment
& segment
: customSegments
) {
645 uint64_t offsetInRegion
= 0;
646 for (CustomSegment::CustomSection
& section
: segment
.sections
) {
647 section
.offsetInRegion
= offsetInRegion
;
648 offsetInRegion
+= section
.data
.size();
651 Region
& customRegion
= customDataRegions
.emplace_back();
652 segment
.parentRegion
= &customRegion
;
655 customRegion
.bufferSize
= align(offsetInRegion
, 14);
656 customRegion
.sizeInUse
= customRegion
.bufferSize
;
657 customRegion
.initProt
= VM_PROT_READ
;
658 customRegion
.maxProt
= VM_PROT_READ
;
659 customRegion
.name
= segment
.segmentName
;
665 // This is populated with regular kexts and codeless kexts
667 CFDictionaryRef infoPlist
= nullptr;
668 const dyld3::MachOAnalyzer
* ma
= nullptr;
669 std::string_view bundlePath
;
670 std::string_view executablePath
;
672 std::vector
<PrelinkInfo
> infos
;
673 for (AppCacheDylibInfo
& dylib
: sortedDylibs
) {
674 if (dylib
.infoPlist
== nullptr)
676 infos
.push_back({ dylib
.infoPlist
, dylib
.input
->mappedFile
.mh
, dylib
.bundlePath
, dylib
.input
->loadedFileInfo
.path
});
678 for (InputDylib
& dylib
: codelessKexts
) {
679 infos
.push_back({ dylib
.infoPlist
, nullptr, dylib
.bundlePath
, "" });
682 CFMutableArrayRef bundlesArrayRef
= CFArrayCreateMutable(kCFAllocatorDefault
, 0,
683 &kCFTypeArrayCallBacks
);
684 for (PrelinkInfo
& info
: infos
) {
685 CFDictionaryRef infoPlist
= info
.infoPlist
;
686 // Create a copy of the dictionary so that we can add more fields
687 CFMutableDictionaryRef dictCopyRef
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, infoPlist
);
689 // _PrelinkBundlePath
690 CFStringRef bundlePath
= CFStringCreateWithCStringNoCopy(kCFAllocatorDefault
, info
.bundlePath
.data(),
691 kCFStringEncodingASCII
, kCFAllocatorNull
);
692 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkBundlePath"), bundlePath
);
693 CFRelease(bundlePath
);
695 // Note we want this address to be a large enough integer in the xml format that we have enough space
696 // to replace it with its real address later
697 const uint64_t largeAddress
= 0x7FFFFFFFFFFFFFFF;
699 // _PrelinkExecutableLoadAddr
700 // Leave a placeholder for this for now just so that we have enough space for it later
701 CFNumberRef loadAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &largeAddress
);
702 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkExecutableLoadAddr"), loadAddrRef
);
703 CFRelease(loadAddrRef
);
705 // _PrelinkExecutableRelativePath
706 if ( info
.executablePath
!= "" ) {
707 const char* relativePath
= info
.executablePath
.data();
708 if ( strncmp(relativePath
, info
.bundlePath
.data(), info
.bundlePath
.size()) == 0 ) {
709 relativePath
= relativePath
+ info
.bundlePath
.size();
710 if ( relativePath
[0] == '/' )
712 } else if ( const char* lastSlash
= strrchr(relativePath
, '/') )
713 relativePath
= lastSlash
+1;
714 CFStringRef executablePath
= CFStringCreateWithCStringNoCopy(kCFAllocatorDefault
, relativePath
,
715 kCFStringEncodingASCII
, kCFAllocatorNull
);
716 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkExecutableRelativePath"), executablePath
);
717 CFRelease(executablePath
);
720 // _PrelinkExecutableSize
721 // This seems to be the file size of __TEXT
722 __block
uint64_t textSegFileSize
= 0;
723 if ( info
.ma
!= nullptr ) {
724 info
.ma
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
725 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
726 textSegFileSize
= segInfo
.fileSize
;
729 if (textSegFileSize
!= 0) {
730 CFNumberRef fileSizeRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &textSegFileSize
);
731 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkExecutableSize"), fileSizeRef
);
732 CFRelease(fileSizeRef
);
735 // _PrelinkExecutableSourceAddr
736 // Leave a placeholder for this for now just so that we have enough space for it later
737 CFNumberRef sourceAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &largeAddress
);
738 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkExecutableSourceAddr"), sourceAddrRef
);
739 CFRelease(sourceAddrRef
);
742 // Leave a placeholder for this for now just so that we have enough space for it later
743 dyld3::MachOAnalyzer::FoundSymbol foundInfo
;
744 if ( (info
.ma
!= nullptr) ) {
745 // Check for a global first
746 __block
bool found
= false;
747 found
= info
.ma
->findExportedSymbol(_diagnostics
, "_kmod_info", true, foundInfo
, nullptr);
749 // And fall back to a local if we need to
750 info
.ma
->forEachLocalSymbol(_diagnostics
, ^(const char* aSymbolName
, uint64_t n_value
, uint8_t n_type
,
751 uint8_t n_sect
, uint16_t n_desc
, bool& stop
) {
752 if ( strcmp(aSymbolName
, "_kmod_info") == 0 ) {
760 CFNumberRef kmodInfoAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &largeAddress
);
761 CFDictionarySetValue(dictCopyRef
, CFSTR("_PrelinkKmodInfo"), kmodInfoAddrRef
);
762 CFRelease(kmodInfoAddrRef
);
766 CFArrayAppendValue(bundlesArrayRef
, dictCopyRef
);
767 // Release the temporary dictionary now that its in the array
768 CFRelease(dictCopyRef
);
771 prelinkInfoDict
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
772 &kCFTypeDictionaryKeyCallBacks
,
773 &kCFTypeDictionaryValueCallBacks
);
775 // First add any data from addPrelinkInfo()
776 if ( extraPrelinkInfo
!= nullptr ) {
777 CFDictionaryApplierFunction applier
= [](const void *key
, const void *value
, void *context
) {
778 CFMutableDictionaryRef parentDict
= (CFMutableDictionaryRef
)context
;
779 CFDictionaryAddValue(parentDict
, key
, value
);
781 CFDictionaryApplyFunction(extraPrelinkInfo
, applier
, (void*)prelinkInfoDict
);
784 if ( bundlesArrayRef
!= nullptr ) {
785 CFDictionaryAddValue(prelinkInfoDict
, CFSTR("_PrelinkInfoDictionary"), bundlesArrayRef
);
786 CFRelease(bundlesArrayRef
);
789 // Add a placeholder for the collection UUID
792 CFDataRef dataRef
= CFDataCreate(kCFAllocatorDefault
, (const uint8_t*)&uuid
, sizeof(uuid
));
793 CFDictionaryAddValue(prelinkInfoDict
, CFSTR("_PrelinkKCID"), dataRef
);
797 // The pageable/aux KCs should embed the UUID of the base kernel collection
798 if ( existingKernelCollection
!= nullptr ) {
800 bool foundUUID
= existingKernelCollection
->getUuid(uuid
);
802 _diagnostics
.error("Could not find UUID in base kernel collection");
805 CFDataRef dataRef
= CFDataCreate(kCFAllocatorDefault
, (const uint8_t*)&uuid
, sizeof(uuid
));
806 CFDictionaryAddValue(prelinkInfoDict
, CFSTR("_BootKCID"), dataRef
);
810 // The aux KC should embed the UUID of the pageable kernel collection if we have one
811 if ( pageableKernelCollection
!= nullptr ) {
813 bool foundUUID
= pageableKernelCollection
->getUuid(uuid
);
815 _diagnostics
.error("Could not find UUID in pageable kernel collection");
818 CFDataRef dataRef
= CFDataCreate(kCFAllocatorDefault
, (const uint8_t*)&uuid
, sizeof(uuid
));
819 CFDictionaryAddValue(prelinkInfoDict
, CFSTR("_PageableKCID"), dataRef
);
823 CFErrorRef errorRef
= nullptr;
824 CFDataRef xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, prelinkInfoDict
,
825 kCFPropertyListXMLFormat_v1_0
, 0, &errorRef
);
826 if (errorRef
!= nullptr) {
827 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
828 _diagnostics
.error("Could not serialise plist because :%s",
829 CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
834 CFIndex xmlDataLength
= CFDataGetLength(xmlData
);
838 prelinkInfoRegion
.bufferSize
= align(xmlDataLength
, 14);
839 prelinkInfoRegion
.sizeInUse
= prelinkInfoRegion
.bufferSize
;
840 prelinkInfoRegion
.initProt
= VM_PROT_READ
| VM_PROT_WRITE
;
841 prelinkInfoRegion
.maxProt
= VM_PROT_READ
| VM_PROT_WRITE
;
842 prelinkInfoRegion
.name
= "__PRELINK_INFO";
846 // Do all __LINKINFO regardless of split seg
847 _nonLinkEditReadOnlySize
= 0;
848 __block
uint64_t offsetInRegion
= 0;
849 for (DylibInfo
& dylib
: sortedDylibs
) {
850 __block
uint64_t textSegVmAddr
= 0;
851 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
852 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
853 textSegVmAddr
= segInfo
.vmAddr
;
854 if ( segInfo
.protections
!= VM_PROT_READ
)
856 if ( strcmp(segInfo
.segName
, "__LINKINFO") != 0 )
858 // Keep segments 4K or more aligned
859 offsetInRegion
= align(offsetInRegion
, std::max((int)segInfo
.p2align
, (int)12));
860 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
861 SegmentMappingInfo loc
;
862 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
863 loc
.segName
= segInfo
.segName
;
864 loc
.dstSegment
= nullptr;
865 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
866 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
867 loc
.dstCacheSegmentSize
= (uint32_t)align(segInfo
.sizeOfSections
, 12);
868 loc
.dstCacheFileSize
= (uint32_t)copySize
;
869 loc
.copySegmentSize
= (uint32_t)copySize
;
870 loc
.srcSegmentIndex
= segInfo
.segIndex
;
871 loc
.parentRegion
= &_readOnlyRegion
;
872 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
873 offsetInRegion
+= loc
.dstCacheSegmentSize
;
877 // Align the end of the __LINKINFO
878 offsetInRegion
= align(offsetInRegion
, 14);
879 _nonLinkEditReadOnlySize
= offsetInRegion
;
881 // Do all __LINKEDIT, regardless of split seg
882 for (DylibInfo
& dylib
: sortedDylibs
) {
883 __block
uint64_t textSegVmAddr
= 0;
884 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
885 if ( strcmp(segInfo
.segName
, "__TEXT") == 0 )
886 textSegVmAddr
= segInfo
.vmAddr
;
887 if ( segInfo
.protections
!= VM_PROT_READ
)
889 if ( strcmp(segInfo
.segName
, "__LINKEDIT") != 0 )
891 // Keep segments 4K or more aligned
892 offsetInRegion
= align(offsetInRegion
, std::max((int)segInfo
.p2align
, (int)12));
893 size_t copySize
= std::min((size_t)segInfo
.fileSize
, (size_t)segInfo
.sizeOfSections
);
894 SegmentMappingInfo loc
;
895 loc
.srcSegment
= (uint8_t*)dylib
.input
->mappedFile
.mh
+ segInfo
.vmAddr
- textSegVmAddr
;
896 loc
.segName
= segInfo
.segName
;
897 loc
.dstSegment
= nullptr;
898 loc
.dstCacheUnslidAddress
= offsetInRegion
; // This will be updated later once we've assigned addresses
899 loc
.dstCacheFileOffset
= (uint32_t)offsetInRegion
;
900 loc
.dstCacheSegmentSize
= (uint32_t)align(segInfo
.sizeOfSections
, 12);
901 loc
.dstCacheFileSize
= (uint32_t)copySize
;
902 loc
.copySegmentSize
= (uint32_t)copySize
;
903 loc
.srcSegmentIndex
= segInfo
.segIndex
;
904 loc
.parentRegion
= &_readOnlyRegion
;
905 dylib
.cacheLocation
[segInfo
.segIndex
] = loc
;
906 offsetInRegion
+= loc
.dstCacheSegmentSize
;
910 // align r/o region end
911 _readOnlyRegion
.bufferSize
= align(offsetInRegion
, 14);
912 _readOnlyRegion
.sizeInUse
= _readOnlyRegion
.bufferSize
;
913 _readOnlyRegion
.initProt
= VM_PROT_READ
;
914 _readOnlyRegion
.maxProt
= VM_PROT_READ
;
915 _readOnlyRegion
.name
= "__LINKEDIT";
917 // Add space in __LINKEDIT for chained fixups and classic relocs
920 // The pageableKC (and sometimes auxKC) has 1 LC_DYLD_CHAINED_FIXUPS per kext
921 // while other KCs have 1 for the whole KC.
922 // It also tracks each segment in each kext for chained fixups, not the segments on the KC itself
923 __block
uint64_t numSegmentsForChainedFixups
= 0;
924 uint64_t numChainedFixupHeaders
= 0;
925 if ( fixupsArePerKext() ) {
926 for (DylibInfo
& dylib
: sortedDylibs
) {
927 dylib
.input
->mappedFile
.mh
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
& segInfo
, bool& stop
) {
928 ++numSegmentsForChainedFixups
;
931 numChainedFixupHeaders
= sortedDylibs
.size();
933 // Branch stubs need fixups on the GOTs region. So add in a top-level chained fixup entry
934 // and for now all the regions as we don't know what segment index the branch GOTs will be
935 numSegmentsForChainedFixups
+= numRegions();
936 numChainedFixupHeaders
++;
938 numSegmentsForChainedFixups
= numRegions();
939 numChainedFixupHeaders
= 1;
942 uint64_t numBytesForPageStarts
= 0;
943 if ( dataConstRegion
.sizeInUse
!= 0 )
944 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(dataConstRegion
.bufferSize
));
945 if ( branchGOTsRegion
.bufferSize
!= 0 )
946 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(branchGOTsRegion
.bufferSize
));
947 if ( readWriteRegion
.sizeInUse
!= 0 )
948 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(readWriteRegion
.bufferSize
));
949 if ( hibernateRegion
.sizeInUse
!= 0 )
950 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(hibernateRegion
.bufferSize
));
951 for (const Region
& region
: nonSplitSegRegions
) {
952 // Assume writable regions have fixups to emit
953 // Note, third party kext's have __TEXT fixups, so assume all of these have fixups
954 // LINKEDIT is already elsewhere
955 numBytesForPageStarts
+= sizeof(dyld_chained_starts_in_segment
) + (sizeof(uint16_t) * numWritablePagesToFixup(region
.bufferSize
));
958 uint64_t numBytesForChainedFixups
= 0;
959 if ( numBytesForPageStarts
!= 0 ) {
960 numBytesForChainedFixups
= numBytesForPageStarts
;
961 numBytesForChainedFixups
+= sizeof(dyld_chained_fixups_header
) * numChainedFixupHeaders
;
962 numBytesForChainedFixups
+= sizeof(dyld_chained_starts_in_image
) * numChainedFixupHeaders
;
963 numBytesForChainedFixups
+= sizeof(uint32_t) * numSegmentsForChainedFixups
;
966 __block
uint64_t numBytesForClassicRelocs
= 0;
967 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::kernel
) {
968 if ( const DylibInfo
* dylib
= getKernelStaticExecutableInputFile() ) {
969 if ( dylib
->input
->mappedFile
.mh
->usesClassicRelocationsInKernelCollection() ) {
970 dylib
->input
->mappedFile
.mh
->forEachRebase(_diagnostics
, false, ^(uint64_t runtimeOffset
, bool &stop
) {
971 numBytesForClassicRelocs
+= sizeof(relocation_info
);
977 // align fixups region end
978 if ( (numBytesForChainedFixups
!= 0) || (numBytesForClassicRelocs
!= 0) ) {
979 uint64_t numBytes
= align(numBytesForChainedFixups
, 3) + align(numBytesForClassicRelocs
, 3);
980 fixupsSubRegion
.bufferSize
= align(numBytes
, 14);
981 fixupsSubRegion
.sizeInUse
= fixupsSubRegion
.bufferSize
;
982 fixupsSubRegion
.initProt
= VM_PROT_READ
;
983 fixupsSubRegion
.maxProt
= VM_PROT_READ
;
984 fixupsSubRegion
.name
= "__FIXUPS";
989 void AppCacheBuilder::assignSegmentAddresses() {
990 // Segments already have offsets in to their regions. Now assign the regions their addresses
991 // in the full allocated buffer, and then assign all segments in those regions
992 for (DylibInfo
& dylib
: sortedDylibs
) {
993 for (SegmentMappingInfo
& loc
: dylib
.cacheLocation
) {
994 loc
.dstSegment
= loc
.parentRegion
->buffer
+ loc
.dstCacheFileOffset
;
995 loc
.dstCacheUnslidAddress
= loc
.parentRegion
->unslidLoadAddress
+ loc
.dstCacheFileOffset
;
996 loc
.dstCacheFileOffset
= (uint32_t)loc
.parentRegion
->cacheFileOffset
+ loc
.dstCacheFileOffset
;
1001 void AppCacheBuilder::copyRawSegments() {
1002 const bool log
= false;
1004 // Call the base class to copy segment data
1005 CacheBuilder::copyRawSegments();
1007 // The copy any custom sections
1008 for (const CustomSegment
& segment
: customSegments
) {
1009 for (const CustomSegment::CustomSection
& section
: segment
.sections
) {
1010 uint8_t* dstBuffer
= segment
.parentRegion
->buffer
+ section
.offsetInRegion
;
1011 uint64_t dstVMAddr
= segment
.parentRegion
->unslidLoadAddress
+ section
.offsetInRegion
;
1012 if (log
) fprintf(stderr
, "copy %s segment %s %s (0x%08lX bytes) from %p to %p (logical addr 0x%llX)\n",
1013 _options
.archs
->name(), segment
.segmentName
.c_str(), section
.sectionName
.c_str(),
1014 section
.data
.size(), section
.data
.data(), dstBuffer
, dstVMAddr
);
1015 ::memcpy(dstBuffer
, section
.data
.data(), section
.data
.size());
1020 static uint8_t getFixupLevel(AppCacheBuilder::Options::AppCacheKind kind
) {
1021 uint8_t currentLevel
= (uint8_t)~0U;
1023 case AppCacheBuilder::Options::AppCacheKind::none
:
1024 assert(0 && "Cache kind should have been set");
1026 case AppCacheBuilder::Options::AppCacheKind::kernel
:
1029 case AppCacheBuilder::Options::AppCacheKind::pageableKC
:
1030 // The pageableKC sits right above the baseKC which is level 0
1033 case AppCacheBuilder::Options::AppCacheKind::kernelCollectionLevel2
:
1034 assert(0 && "Unimplemented");
1036 case AppCacheBuilder::Options::AppCacheKind::auxKC
:
1040 return currentLevel
;
1043 uint32_t AppCacheBuilder::getCurrentFixupLevel() const {
1044 return getFixupLevel(appCacheOptions
.cacheKind
);
1047 struct VTableBindSymbol
{
1048 std::string_view binaryID
;
1049 std::string symbolName
;
1052 // For every dylib, lets make a map from its exports to its defs
1053 struct DylibSymbols
{
1054 // Define a bunch of constructors so that we know we are getting move constructors not copies
1055 DylibSymbols() = default;
1056 DylibSymbols(const DylibSymbols
&) = delete;
1057 DylibSymbols(DylibSymbols
&&) = default;
1058 DylibSymbols(std::map
<std::string_view
, uint64_t>&& globals
,
1059 std::map
<std::string_view
, uint64_t>&& locals
,
1060 std::unique_ptr
<std::unordered_set
<std::string
>> kpiSymbols
,
1061 uint32_t dylibLevel
, const std::string
& dylibName
)
1062 : globals(std::move(globals
)), locals(std::move(locals
)), kpiSymbols(std::move(kpiSymbols
)),
1063 dylibLevel(dylibLevel
), dylibName(dylibName
) { }
1065 DylibSymbols
& operator=(const DylibSymbols
& other
) = delete;
1066 DylibSymbols
& operator=(DylibSymbols
&& other
) = default;
1068 std::map
<std::string_view
, uint64_t> globals
;
1070 // We also need to track locals as vtable patching supports patching with these too
1071 std::map
<std::string_view
, uint64_t> locals
;
1073 // KPI (ie, a symbol set embedded in this binary)
1074 std::unique_ptr
<std::unordered_set
<std::string
>> kpiSymbols
;
1076 // Kernel collections can reference each other in levels. This is the level
1077 // of the exported dylib. Eg, the base KC is 0, and the aux KC is 3
1078 uint32_t dylibLevel
= 0;
1080 // Store the name of the dylib for fast lookups
1081 std::string dylibName
;
1083 // Keep track of the binds in this dylib as these tell us if a vtable slot is to a local
1084 // or external definition of a function
1085 std::unordered_map
<const uint8_t*, VTableBindSymbol
> resolvedBindLocations
;
1088 class VTablePatcher
{
1091 VTablePatcher(uint32_t numFixupLevels
);
1093 bool hasError() const;
1095 void addKernelCollection(const dyld3::MachOAppCache
* cacheMA
, AppCacheBuilder::Options::AppCacheKind kind
,
1096 const uint8_t* basePointer
, uint64_t baseAddress
);
1097 void addDylib(Diagnostics
& diags
, const dyld3::MachOAnalyzer
* ma
, const std::string
& dylibID
,
1098 const std::vector
<std::string
>& dependencies
, uint8_t cacheLevel
);
1100 void findMetaclassDefinitions(std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
1101 const std::string
& kernelID
, const dyld3::MachOAnalyzer
* kernelMA
,
1102 AppCacheBuilder::Options::AppCacheKind cacheKind
);
1103 void findExistingFixups(Diagnostics
& diags
,
1104 const dyld3::MachOAppCache
* existingKernelCollection
,
1105 const dyld3::MachOAppCache
* pageableKernelCollection
);
1106 void findBaseKernelVTables(Diagnostics
& diags
, const dyld3::MachOAppCache
* existingKernelCollection
,
1107 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
);
1108 void findPageableKernelVTables(Diagnostics
& diags
, const dyld3::MachOAppCache
* existingKernelCollection
,
1109 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
);
1110 void findVTables(uint8_t currentLevel
, const dyld3::MachOAnalyzer
* kernelMA
,
1111 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
1112 const AppCacheBuilder::ASLR_Tracker
& aslrTracker
,
1113 const std::map
<const uint8_t*, const VTableBindSymbol
>& missingBindLocations
);
1114 void calculateSymbols();
1115 void patchVTables(Diagnostics
& diags
,
1116 std::map
<const uint8_t*, const VTableBindSymbol
>& missingBindLocations
,
1117 AppCacheBuilder::ASLR_Tracker
& aslrTracker
,
1118 uint8_t currentLevel
);
1122 void logFunc(const char* format
, ...) {
1123 if ( logPatching
) {
1125 va_start(list
, format
);
1126 vfprintf(stderr
, format
, list
);
1131 void logFuncVerbose(const char* format
, ...) {
1132 if ( logPatchingVerbose
) {
1134 va_start(list
, format
);
1135 vfprintf(stderr
, format
, list
);
1140 // Extract a substring by dropping optional prefix/suffix
1141 std::string_view
extractString(std::string_view str
, std::string_view prefix
, std::string_view suffix
) {
1142 if ( !prefix
.empty() ) {
1143 // Make sure we have the prefix we are looking for
1144 if ( str
.find(prefix
) != 0 ) {
1145 return std::string_view();
1147 str
.remove_prefix(prefix
.size());
1149 if ( !suffix
.empty() ) {
1150 // Make sure we have the prefix we are looking for
1151 size_t pos
= str
.rfind(suffix
);
1152 if ( pos
!= (str
.size() - suffix
.size()) ) {
1153 return std::string_view();
1155 str
.remove_suffix(suffix
.size());
1162 const uint8_t* location
= nullptr;
1163 uint64_t targetVMAddr
= ~0ULL;
1164 uint32_t targetCacheLevel
= ~0;
1166 uint16_t diversity
= 0;
1167 bool hasAddrDiv
= false;
1169 bool hasPointerAuth
= false;
1172 const dyld3::MachOAnalyzer
* ma
= nullptr;
1173 const uint8_t* superVTable
= nullptr;
1174 const DylibSymbols
* dylib
= nullptr;
1175 bool fromParentCollection
= false;
1176 bool patched
= false;
1177 std::string name
= "";
1178 std::vector
<Entry
> entries
;
1181 struct SymbolLocation
{
1182 uint64_t vmAddr
= 0;
1183 bool foundSymbol
= 0;
1185 bool found() const {
1191 uint64_t targetVMAddr
= 0;
1192 uint8_t cacheLevel
= 0;
1194 uint16_t diversity
= 0;
1195 bool hasAddrDiv
= false;
1197 bool hasPointerAuth
= false;
1200 struct VTableDylib
{
1201 Diagnostics
* diags
= nullptr;
1202 const dyld3::MachOAnalyzer
* ma
= nullptr;
1203 std::string dylibID
= "";
1204 std::vector
<std::string
> dependencies
;
1205 uint32_t cacheLevel
= ~0U;
1208 struct KernelCollection
{
1209 const dyld3::MachOAppCache
* ma
= nullptr;
1211 // We need the base pointers to the buffers for every level
1212 // These are the base of the allocated memory, which corresponds to pointing to the lowest
1213 // vmAddr for the buffer. These do *not* necessarily point to a mach_header
1214 const uint8_t* basePointer
= nullptr;
1216 // We also need the base vm addresses to the buffers for every level
1217 uint64_t baseAddress
= ~0ULL;
1219 std::unordered_map
<uint64_t, const char*> symbolNames
;
1220 std::map
<uint64_t, std::string_view
> metaclassDefinitions
;
1223 SymbolLocation
findVTablePatchingSymbol(std::string_view symbolName
, const DylibSymbols
& dylibSymbols
);
1225 std::vector
<VTableDylib
> dylibs
;
1226 std::map
<const uint8_t*, VTable
> vtables
;
1227 std::vector
<KernelCollection
> collections
;
1228 const uint8_t* baseMetaClassVTableLoc
= nullptr;
1230 // Record all the fixup locations in the base/pageable KCs as we need to use them instead of the ASLR tracker
1231 std::map
<const uint8_t*, Fixup
> existingCollectionFixupLocations
;
1233 const uint32_t pointerSize
= 8;
1234 const bool logPatching
= false;
1235 const bool logPatchingVerbose
= false;
1237 // Magic constants for vtable patching
1238 //const char* cxxPrefix = "__Z";
1239 const char* vtablePrefix
= "__ZTV";
1240 const char* osObjPrefix
= "__ZN";
1241 // const char* vtableReservedToken = "_RESERVED";
1242 const char* metaclassToken
= "10gMetaClassE";
1243 const char* superMetaclassPointerToken
= "10superClassE";
1244 const char* metaclassVTablePrefix
= "__ZTVN";
1245 const char* metaclassVTableSuffix
= "9MetaClassE";
1248 VTablePatcher::VTablePatcher(uint32_t numFixupLevels
) {
1249 collections
.resize(numFixupLevels
);
1252 bool VTablePatcher::hasError() const {
1253 for (const VTableDylib
& dylib
: dylibs
) {
1254 if ( dylib
.diags
->hasError() )
1260 void VTablePatcher::addKernelCollection(const dyld3::MachOAppCache
* cacheMA
, AppCacheBuilder::Options::AppCacheKind kind
,
1261 const uint8_t* basePointer
, uint64_t baseAddress
) {
1262 uint8_t cacheLevel
= getFixupLevel(kind
);
1264 assert(cacheLevel
< collections
.size());
1265 assert(collections
[cacheLevel
].ma
== nullptr);
1267 collections
[cacheLevel
].ma
= cacheMA
;
1268 collections
[cacheLevel
].basePointer
= basePointer
;
1269 collections
[cacheLevel
].baseAddress
= baseAddress
;
1272 void VTablePatcher::addDylib(Diagnostics
&diags
, const dyld3::MachOAnalyzer
*ma
,
1273 const std::string
& dylibID
, const std::vector
<std::string
>& dependencies
,
1274 uint8_t cacheLevel
) {
1275 dylibs
.push_back((VTableDylib
){ &diags
, ma
, dylibID
, dependencies
, cacheLevel
});
1278 VTablePatcher::SymbolLocation
VTablePatcher::findVTablePatchingSymbol(std::string_view symbolName
,
1279 const DylibSymbols
& dylibSymbols
) {
1280 // First look in the globals
1281 auto globalsIt
= dylibSymbols
.globals
.find(symbolName
);
1282 if ( globalsIt
!= dylibSymbols
.globals
.end() ) {
1283 return { globalsIt
->second
, true };
1286 // Then again in the locals
1287 auto localsIt
= dylibSymbols
.locals
.find(symbolName
);
1288 if ( localsIt
!= dylibSymbols
.locals
.end() ) {
1289 return { localsIt
->second
, true };
1292 return { ~0ULL, false };
1295 void VTablePatcher::findMetaclassDefinitions(std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
1296 const std::string
& kernelID
, const dyld3::MachOAnalyzer
* kernelMA
,
1297 AppCacheBuilder::Options::AppCacheKind cacheKind
) {
1298 for (VTableDylib
& dylib
: dylibs
) {
1299 auto& metaclassDefinitions
= collections
[dylib
.cacheLevel
].metaclassDefinitions
;
1300 dylib
.ma
->forEachGlobalSymbol(*dylib
.diags
, ^(const char *symbolName
, uint64_t n_value
,
1301 uint8_t n_type
, uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1302 if ( strstr(symbolName
, metaclassToken
) != nullptr )
1303 metaclassDefinitions
[n_value
] = symbolName
;
1305 dylib
.ma
->forEachLocalSymbol(*dylib
.diags
, ^(const char *symbolName
, uint64_t n_value
,
1306 uint8_t n_type
, uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1307 if ( strstr(symbolName
, metaclassToken
) != nullptr )
1308 metaclassDefinitions
[n_value
] = symbolName
;
1312 // Keep track of the root OSMetaClass from which all other metaclasses inherit
1313 DylibSymbols
& kernelDylibSymbols
= dylibsToSymbols
[kernelID
];
1314 SymbolLocation symbolLocation
= findVTablePatchingSymbol("__ZTV11OSMetaClass", kernelDylibSymbols
);
1315 if ( symbolLocation
.found() ) {
1316 baseMetaClassVTableLoc
= (uint8_t*)kernelMA
+ (symbolLocation
.vmAddr
- kernelMA
->preferredLoadAddress());
1318 VTable
& vtable
= vtables
[baseMetaClassVTableLoc
];
1319 vtable
.ma
= kernelMA
;
1320 vtable
.dylib
= &kernelDylibSymbols
;
1321 vtable
.fromParentCollection
= (cacheKind
!= AppCacheBuilder::Options::AppCacheKind::kernel
);
1322 vtable
.patched
= true;
1323 vtable
.name
= "__ZTV11OSMetaClass";
1327 void VTablePatcher::findExistingFixups(Diagnostics
& diags
,
1328 const dyld3::MachOAppCache
* existingKernelCollection
,
1329 const dyld3::MachOAppCache
* pageableKernelCollection
) {
1331 const bool is64
= pointerSize
== 8;
1333 if ( existingKernelCollection
!= nullptr ) {
1334 uint8_t kernelLevel
= getFixupLevel(AppCacheBuilder::Options::AppCacheKind::kernel
);
1335 uint64_t kernelBaseAddress
= collections
[kernelLevel
].baseAddress
;
1336 const uint8_t* kernelBasePointer
= collections
[kernelLevel
].basePointer
;
1338 // We may have both chained and classic fixups. First add chained
1339 if ( existingKernelCollection
->hasChainedFixupsLoadCommand() ) {
1340 existingKernelCollection
->withChainStarts(diags
, 0, ^(const dyld_chained_starts_in_image
* starts
) {
1341 existingKernelCollection
->forEachFixupInAllChains(diags
, starts
, false,
1342 ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk
* fixupLoc
, const dyld_chained_starts_in_segment
* segInfo
, bool& stop
) {
1343 uint64_t vmOffset
= 0;
1344 bool isRebase
= fixupLoc
->isRebase(segInfo
->pointer_format
, kernelBaseAddress
, vmOffset
);
1346 uint64_t targetVMAddr
= kernelBaseAddress
+ vmOffset
;
1347 uint16_t diversity
= fixupLoc
->kernel64
.diversity
;
1348 bool hasAddrDiv
= fixupLoc
->kernel64
.addrDiv
;
1349 uint8_t key
= fixupLoc
->kernel64
.key
;
1350 bool hasPointerAuth
= fixupLoc
->kernel64
.isAuth
;
1351 existingCollectionFixupLocations
[(const uint8_t*)fixupLoc
] = { targetVMAddr
, kernelLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
};
1356 // And add classic if we have them
1357 existingKernelCollection
->forEachRebase(diags
, ^(const char *opcodeName
, const dyld3::MachOAnalyzer::LinkEditInfo
&leInfo
,
1358 const dyld3::MachOAnalyzer::SegmentInfo
*segments
,
1359 bool segIndexSet
, uint32_t pointerSize
, uint8_t segmentIndex
,
1360 uint64_t segmentOffset
, dyld3::MachOAnalyzer::Rebase kind
, bool &stop
) {
1361 uint64_t rebaseVmAddr
= segments
[segmentIndex
].vmAddr
+ segmentOffset
;
1362 uint64_t runtimeOffset
= rebaseVmAddr
- kernelBaseAddress
;
1363 const uint8_t* fixupLoc
= kernelBasePointer
+ runtimeOffset
;
1364 uint64_t targetVMAddr
= 0;
1366 targetVMAddr
= *(uint64_t*)fixupLoc
;
1368 targetVMAddr
= *(uint32_t*)fixupLoc
;
1370 // Classic relocs have no pointer auth
1371 uint16_t diversity
= 0;
1372 bool hasAddrDiv
= false;
1374 bool hasPointerAuth
= false;
1375 existingCollectionFixupLocations
[(const uint8_t*)fixupLoc
] = { targetVMAddr
, kernelLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
};
1379 // Add pageable fixup locations if we have it
1380 if ( pageableKernelCollection
!= nullptr ) {
1381 // We only have chained fixups here to add, but they are on each kext, not on the KC itself
1382 pageableKernelCollection
->forEachDylib(diags
, ^(const dyld3::MachOAnalyzer
*ma
, const char *name
, bool &stop
) {
1383 // Skip kexts without fixups
1384 if ( !ma
->hasChainedFixupsLoadCommand() )
1386 ma
->withChainStarts(diags
, 0, ^(const dyld_chained_starts_in_image
* starts
) {
1387 ma
->forEachFixupInAllChains(diags
, starts
, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk
* fixupLoc
, const dyld_chained_starts_in_segment
* segInfo
, bool& stop
) {
1388 uint64_t vmOffset
= 0;
1389 bool isRebase
= fixupLoc
->isRebase(DYLD_CHAINED_PTR_64_KERNEL_CACHE
, 0, vmOffset
);
1391 uint8_t targetFixupLevel
= fixupLoc
->kernel64
.cacheLevel
;
1392 uint64_t targetVMAddr
= collections
[targetFixupLevel
].baseAddress
+ vmOffset
;
1393 uint16_t diversity
= fixupLoc
->kernel64
.diversity
;
1394 bool hasAddrDiv
= fixupLoc
->kernel64
.addrDiv
;
1395 uint8_t key
= fixupLoc
->kernel64
.key
;
1396 bool hasPointerAuth
= fixupLoc
->kernel64
.isAuth
;
1397 existingCollectionFixupLocations
[(const uint8_t*)fixupLoc
] = { targetVMAddr
, targetFixupLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
};
1404 void VTablePatcher::findBaseKernelVTables(Diagnostics
& diags
, const dyld3::MachOAppCache
* existingKernelCollection
,
1405 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
)
1407 const bool is64
= pointerSize
== 8;
1409 uint8_t kernelLevel
= getFixupLevel(AppCacheBuilder::Options::AppCacheKind::kernel
);
1410 uint64_t kernelBaseAddress
= collections
[kernelLevel
].baseAddress
;
1411 const uint8_t* kernelBasePointer
= collections
[kernelLevel
].basePointer
;
1412 uint16_t chainedPointerFormat
= 0;
1414 if ( existingKernelCollection
->hasChainedFixupsLoadCommand() )
1415 chainedPointerFormat
= existingKernelCollection
->chainedPointerFormat();
1417 // Map from dylibID to list of dependencies
1418 std::map
<std::string
, const std::vector
<std::string
>*> kextDependencies
;
1419 for (VTableDylib
& dylib
: dylibs
) {
1420 if ( dylib
.cacheLevel
!= kernelLevel
)
1422 kextDependencies
[dylib
.dylibID
] = &dylib
.dependencies
;
1425 bool kernelUsesClassicRelocs
= existingKernelCollection
->usesClassicRelocationsInKernelCollection();
1426 existingKernelCollection
->forEachDylib(diags
, ^(const dyld3::MachOAnalyzer
*ma
, const char *dylibID
, bool &stop
) {
1427 uint64_t loadAddress
= ma
->preferredLoadAddress();
1429 auto visitBaseKernelCollectionSymbols
= ^(const char *symbolName
, uint64_t n_value
) {
1430 if ( strstr(symbolName
, superMetaclassPointerToken
) == nullptr )
1432 uint8_t* fixupLoc
= (uint8_t*)ma
+ (n_value
- loadAddress
);
1433 logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName
, dylibID
, fixupLoc
);
1435 // 2 - Derive the name of the class from the super MetaClass pointer.
1436 std::string_view className
= extractString(symbolName
, osObjPrefix
, superMetaclassPointerToken
);
1437 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1438 if ( className
.empty() ) {
1439 logFunc("Unsupported vtable superclass name\n");
1442 logFunc("Class name: '%s'\n", std::string(className
).c_str());
1444 // 3 - Derive the name of the class's vtable from the name of the class
1445 // We support namespaces too which means adding an N before the class name and E after
1446 std::string classVTableName
= std::string(vtablePrefix
) + std::string(className
);
1447 logFunc("Class vtable name: '%s'\n", classVTableName
.c_str());
1449 uint64_t classVTableVMAddr
= 0;
1450 const DylibSymbols
& dylibSymbols
= dylibsToSymbols
[dylibID
];
1452 std::string namespacedVTableName
;
1453 SymbolLocation symbolLocation
= findVTablePatchingSymbol(classVTableName
, dylibSymbols
);
1454 if ( !symbolLocation
.found() ) {
1455 // If we didn't find a name then try again with namespaces
1456 namespacedVTableName
= std::string(vtablePrefix
) + "N" + std::string(className
) + "E";
1457 logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName
.c_str());
1458 symbolLocation
= findVTablePatchingSymbol(namespacedVTableName
, dylibSymbols
);
1460 if ( symbolLocation
.found() ) {
1461 classVTableVMAddr
= symbolLocation
.vmAddr
;
1463 diags
.error("Class vtables '%s' or '%s' is not exported from '%s'",
1464 classVTableName
.c_str(), namespacedVTableName
.c_str(), dylibID
);
1470 logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr
);
1471 const uint8_t* classVTableLoc
= kernelBasePointer
+ (classVTableVMAddr
- kernelBaseAddress
);
1473 // 4 - Follow the super MetaClass pointer to get the address of the super MetaClass's symbol
1474 uint64_t superMetaclassSymbolAddress
= 0;
1475 auto existingKernelCollectionFixupLocIt
= existingCollectionFixupLocations
.find(fixupLoc
);
1476 if ( existingKernelCollectionFixupLocIt
!= existingCollectionFixupLocations
.end() ) {
1477 if ( ma
->isKextBundle() || !kernelUsesClassicRelocs
) {
1478 auto* chainedFixupLoc
= (dyld3::MachOLoaded::ChainedFixupPointerOnDisk
*)fixupLoc
;
1479 uint64_t vmOffset
= 0;
1480 bool isRebase
= chainedFixupLoc
->isRebase(chainedPointerFormat
, kernelBaseAddress
, vmOffset
);
1482 superMetaclassSymbolAddress
= kernelBaseAddress
+ vmOffset
;
1484 // The classic reloc is already the vmAddr so nothing special to do here.
1486 superMetaclassSymbolAddress
= *(uint64_t*)fixupLoc
;
1490 logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress
);
1492 if ( superMetaclassSymbolAddress
== 0 ) {
1493 if ( classVTableName
== "__ZTV8OSObject" ) {
1494 // This is the base class of all objects, so it doesn't have a super class
1495 // We add it as a placeholder and set it to 'true' to show its already been processed
1496 VTable
& vtable
= vtables
[classVTableLoc
];
1498 vtable
.dylib
= &dylibSymbols
;
1499 vtable
.fromParentCollection
= true;
1500 vtable
.patched
= true;
1501 vtable
.name
= classVTableName
;
1506 // 5 - Look up the super MetaClass symbol by address
1507 // FIXME: VTable patching the auxKC with the superclass in the baseKC
1508 uint8_t superclassFixupLevel
= kernelLevel
;
1510 auto& metaclassDefinitions
= collections
[superclassFixupLevel
].metaclassDefinitions
;
1511 auto metaclassIt
= metaclassDefinitions
.find(superMetaclassSymbolAddress
);
1512 if ( metaclassIt
== metaclassDefinitions
.end() ) {
1513 diags
.error("Cannot find symbol for metaclass pointed to by '%s' in '%s'",
1514 symbolName
, dylibID
);
1519 // 6 - Derive the super class's name from the super MetaClass name
1520 std::string_view superClassName
= extractString(metaclassIt
->second
, osObjPrefix
, metaclassToken
);
1521 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1522 if ( superClassName
.empty() ) {
1523 logFunc("Unsupported vtable superclass name\n");
1526 logFunc("Superclass name: '%s'\n", std::string(superClassName
).c_str());
1528 // 7 - Derive the super class's vtable from the super class's name
1529 std::string superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1531 // We support namespaces, so first try the superclass without the namespace, then again with it
1532 const uint8_t* superclassVTableLoc
= nullptr;
1533 for (unsigned i
= 0; i
!= 2; ++i
) {
1535 superclassVTableName
= std::string(vtablePrefix
) + + "N" + std::string(superClassName
) + "E";
1537 logFunc("Superclass vtable name: '%s'\n", superclassVTableName
.c_str());
1539 if ( ma
->isKextBundle() ) {
1540 // First check if the superclass vtable comes from a dependent kext
1541 auto it
= kextDependencies
.find(dylibID
);
1542 assert(it
!= kextDependencies
.end());
1543 const std::vector
<std::string
>& dependencies
= *it
->second
;
1544 for (const std::string
& dependencyID
: dependencies
) {
1545 auto depIt
= dylibsToSymbols
.find(dependencyID
);
1546 if (depIt
== dylibsToSymbols
.end()) {
1547 diags
.error("Failed to bind '%s' in '%s' as could not find a kext with '%s' bundle-id",
1548 symbolName
, dylibID
, dependencyID
.c_str());
1553 const DylibSymbols
& dylibSymbols
= depIt
->second
;
1554 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
1555 if ( !symbolLocation
.found() )
1558 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
1559 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
1560 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
1564 if ( superclassVTableLoc
== nullptr ) {
1565 auto depIt
= dylibsToSymbols
.find(dylibID
);
1566 if (depIt
== dylibsToSymbols
.end()) {
1567 diags
.error("Failed to bind '%s' in '%s' as could not find a binary with '%s' bundle-id",
1568 symbolName
, dylibID
, dylibID
);
1573 const DylibSymbols
& dylibSymbols
= depIt
->second
;
1574 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
1575 if ( symbolLocation
.found() ) {
1576 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
1577 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
1578 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
1582 if ( superclassVTableLoc
!= nullptr )
1586 if ( superclassVTableLoc
== nullptr ) {
1587 superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1588 diags
.error("Superclass vtable '%s' is not exported from '%s' or its dependencies",
1589 superclassVTableName
.c_str(), dylibID
);
1594 // Add an entry for this vtable
1595 VTable
& vtable
= vtables
[classVTableLoc
];
1596 vtable
.superVTable
= superclassVTableLoc
;
1598 vtable
.dylib
= &dylibSymbols
;
1599 vtable
.fromParentCollection
= true;
1600 vtable
.patched
= true;
1601 vtable
.name
= classVTableName
;
1603 // And an entry for the superclass vtable
1604 VTable
& supervtable
= vtables
[superclassVTableLoc
];
1605 supervtable
.fromParentCollection
= true;
1606 supervtable
.patched
= true;
1607 supervtable
.name
= superclassVTableName
;
1610 ma
->forEachGlobalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
1611 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1612 visitBaseKernelCollectionSymbols(symbolName
, n_value
);
1615 if ( diags
.hasError() ) {
1620 ma
->forEachLocalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
1621 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1622 visitBaseKernelCollectionSymbols(symbolName
, n_value
);
1625 if ( diags
.hasError() ) {
1632 void VTablePatcher::findPageableKernelVTables(Diagnostics
& diags
, const dyld3::MachOAppCache
* pageableKernelCollection
,
1633 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
)
1635 uint8_t collectionLevel
= getFixupLevel(AppCacheBuilder::Options::AppCacheKind::pageableKC
);
1636 uint64_t collectionBaseAddress
= collections
[collectionLevel
].baseAddress
;
1637 const uint8_t* collectionBasePointer
= collections
[collectionLevel
].basePointer
;
1639 // Map from dylibID to list of dependencies
1640 std::map
<std::string
, const std::vector
<std::string
>*> kextDependencies
;
1641 for (VTableDylib
& dylib
: dylibs
) {
1642 if ( dylib
.cacheLevel
!= collectionLevel
)
1644 kextDependencies
[dylib
.dylibID
] = &dylib
.dependencies
;
1647 pageableKernelCollection
->forEachDylib(diags
, ^(const dyld3::MachOAnalyzer
*ma
, const char *dylibID
, bool &stop
) {
1648 uint64_t loadAddress
= ma
->preferredLoadAddress();
1649 auto visitPageableKernelCollectionSymbols
= ^(const char *symbolName
, uint64_t n_value
) {
1650 if ( strstr(symbolName
, superMetaclassPointerToken
) == nullptr )
1652 uint8_t* fixupLoc
= (uint8_t*)ma
+ (n_value
- loadAddress
);
1653 logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName
, dylibID
, fixupLoc
);
1655 // 2 - Derive the name of the class from the super MetaClass pointer.
1656 std::string_view className
= extractString(symbolName
, osObjPrefix
, superMetaclassPointerToken
);
1657 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1658 if ( className
.empty() ) {
1659 logFunc("Unsupported vtable superclass name\n");
1662 logFunc("Class name: '%s'\n", std::string(className
).c_str());
1664 // 3 - Derive the name of the class's vtable from the name of the class
1665 // We support namespaces too which means adding an N before the class name and E after
1666 std::string classVTableName
= std::string(vtablePrefix
) + std::string(className
);
1667 logFunc("Class vtable name: '%s'\n", classVTableName
.c_str());
1669 uint64_t classVTableVMAddr
= 0;
1670 const DylibSymbols
& dylibSymbols
= dylibsToSymbols
[dylibID
];
1672 std::string namespacedVTableName
;
1673 SymbolLocation symbolLocation
= findVTablePatchingSymbol(classVTableName
, dylibSymbols
);
1674 if ( !symbolLocation
.found() ) {
1675 // If we didn't find a name then try again with namespaces
1676 namespacedVTableName
= std::string(vtablePrefix
) + "N" + std::string(className
) + "E";
1677 logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName
.c_str());
1678 symbolLocation
= findVTablePatchingSymbol(namespacedVTableName
, dylibSymbols
);
1680 if ( symbolLocation
.found() ) {
1681 classVTableVMAddr
= symbolLocation
.vmAddr
;
1683 diags
.error("Class vtables '%s' or '%s' is not exported from '%s'",
1684 classVTableName
.c_str(), namespacedVTableName
.c_str(), dylibID
);
1690 logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr
);
1691 const uint8_t* classVTableLoc
= collectionBasePointer
+ (classVTableVMAddr
- collectionBaseAddress
);
1693 // 4 - Follow the super MetaClass pointer to get the address of the super MetaClass's symbol
1694 uint8_t superclassFixupLevel
= (uint8_t)~0U;
1695 uint64_t superMetaclassSymbolAddress
= 0;
1696 auto existingKernelCollectionFixupLocIt
= existingCollectionFixupLocations
.find(fixupLoc
);
1697 if ( existingKernelCollectionFixupLocIt
!= existingCollectionFixupLocations
.end() ) {
1698 auto* chainedFixupLoc
= (dyld3::MachOLoaded::ChainedFixupPointerOnDisk
*)fixupLoc
;
1699 uint64_t vmOffset
= 0;
1700 bool isRebase
= chainedFixupLoc
->isRebase(DYLD_CHAINED_PTR_64_KERNEL_CACHE
, 0, vmOffset
);
1702 // The superclass could be in the baseKC, while we are analysing the pageableKC, so we need to get the correct level
1704 superclassFixupLevel
= chainedFixupLoc
->kernel64
.cacheLevel
;
1705 superMetaclassSymbolAddress
= collections
[superclassFixupLevel
].baseAddress
+ vmOffset
;
1708 logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress
);
1710 if ( superMetaclassSymbolAddress
== 0 ) {
1711 if ( classVTableName
== "__ZTV8OSObject" ) {
1712 // This is the base class of all objects, so it doesn't have a super class
1713 // We add it as a placeholder and set it to 'true' to show its already been processed
1714 VTable
& vtable
= vtables
[classVTableLoc
];
1716 vtable
.dylib
= &dylibSymbols
;
1717 vtable
.fromParentCollection
= true;
1718 vtable
.patched
= true;
1719 vtable
.name
= classVTableName
;
1724 // 5 - Look up the super MetaClass symbol by address
1725 auto& metaclassDefinitions
= collections
[superclassFixupLevel
].metaclassDefinitions
;
1726 auto metaclassIt
= metaclassDefinitions
.find(superMetaclassSymbolAddress
);
1727 if ( metaclassIt
== metaclassDefinitions
.end() ) {
1728 diags
.error("Cannot find symbol for metaclass pointed to by '%s' in '%s'",
1729 symbolName
, dylibID
);
1734 // 6 - Derive the super class's name from the super MetaClass name
1735 std::string_view superClassName
= extractString(metaclassIt
->second
, osObjPrefix
, metaclassToken
);
1736 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1737 if ( superClassName
.empty() ) {
1738 logFunc("Unsupported vtable superclass name\n");
1741 logFunc("Superclass name: '%s'\n", std::string(superClassName
).c_str());
1743 // 7 - Derive the super class's vtable from the super class's name
1744 std::string superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1746 // We support namespaces, so first try the superclass without the namespace, then again with it
1747 const uint8_t* superclassVTableLoc
= nullptr;
1748 for (unsigned i
= 0; i
!= 2; ++i
) {
1750 superclassVTableName
= std::string(vtablePrefix
) + + "N" + std::string(superClassName
) + "E";
1752 logFunc("Superclass vtable name: '%s'\n", superclassVTableName
.c_str());
1754 if ( ma
->isKextBundle() ) {
1755 // First check if the superclass vtable comes from a dependent kext
1756 auto it
= kextDependencies
.find(dylibID
);
1757 assert(it
!= kextDependencies
.end());
1758 const std::vector
<std::string
>& dependencies
= *it
->second
;
1759 for (const std::string
& dependencyID
: dependencies
) {
1760 auto depIt
= dylibsToSymbols
.find(dependencyID
);
1761 if (depIt
== dylibsToSymbols
.end()) {
1762 diags
.error("Failed to bind '%s' in '%s' as could not find a kext with '%s' bundle-id",
1763 symbolName
, dylibID
, dependencyID
.c_str());
1768 const DylibSymbols
& dylibSymbols
= depIt
->second
;
1769 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
1770 if ( !symbolLocation
.found() )
1773 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
1774 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
1775 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
1779 if ( superclassVTableLoc
== nullptr ) {
1780 auto depIt
= dylibsToSymbols
.find(dylibID
);
1781 if (depIt
== dylibsToSymbols
.end()) {
1782 diags
.error("Failed to bind '%s' in '%s' as could not find a binary with '%s' bundle-id",
1783 symbolName
, dylibID
, dylibID
);
1788 const DylibSymbols
& dylibSymbols
= depIt
->second
;
1789 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
1790 if ( symbolLocation
.found() ) {
1791 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
1792 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
1793 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
1797 if ( superclassVTableLoc
!= nullptr )
1801 if ( superclassVTableLoc
== nullptr ) {
1802 superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1803 diags
.error("Superclass vtable '%s' is not exported from '%s' or its dependencies",
1804 superclassVTableName
.c_str(), dylibID
);
1809 // Add an entry for this vtable
1810 VTable
& vtable
= vtables
[classVTableLoc
];
1811 vtable
.superVTable
= superclassVTableLoc
;
1813 vtable
.dylib
= &dylibSymbols
;
1814 vtable
.fromParentCollection
= true;
1815 vtable
.patched
= true;
1816 vtable
.name
= classVTableName
;
1818 // And an entry for the superclass vtable
1819 VTable
& supervtable
= vtables
[superclassVTableLoc
];
1820 supervtable
.fromParentCollection
= true;
1821 supervtable
.patched
= true;
1822 supervtable
.name
= superclassVTableName
;
1825 ma
->forEachGlobalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
1826 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1827 visitPageableKernelCollectionSymbols(symbolName
, n_value
);
1830 if ( diags
.hasError() ) {
1835 ma
->forEachLocalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
1836 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
1837 visitPageableKernelCollectionSymbols(symbolName
, n_value
);
1840 if ( diags
.hasError() ) {
1847 void VTablePatcher::findVTables(uint8_t currentLevel
, const dyld3::MachOAnalyzer
* kernelMA
,
1848 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
1849 const AppCacheBuilder::ASLR_Tracker
& aslrTracker
,
1850 const std::map
<const uint8_t*, const VTableBindSymbol
>& missingBindLocations
)
1852 const bool is64
= pointerSize
== 8;
1854 uint64_t collectionBaseAddress
= collections
[currentLevel
].baseAddress
;
1855 const uint8_t* collectionBasePointer
= collections
[currentLevel
].basePointer
;
1857 // VTable patching algorithm (for each symbol...):
1858 // - To find the address of a class vtable:
1859 // - Take symbols with '10superClassE' in their name, eg, __ZN10IOMachPort10superClassE
1860 // - Work out the name of the class from that symbol name, eg, 10IOMachPort
1861 // - Work out the name of the VTable from that class name, eg, __ZTV10IOMachPort
1862 // - Find the address for the export with that vtable name
1863 // - To find the superclass for a given class
1864 // - Take the symbol with '10superClassE' in their name, eg, __ZN10IOMachPort10superClassE
1865 // - Take its address and dereference it as "__ZN10IOMachPort10superClassE = &__ZN8OSObject10gMetaClassE"
1866 // - Find the name of the symbol at this address, eg, work out we have a symbol called __ZN8OSObject10gMetaClassE
1867 // - Get the superclassic from that symbol name, eg, 8OSObject
1868 // - Get the VTable name from that symbol, eg, __ZTV8OSObject
1869 // - Find the superclass vtable address from that name by searching the image and dependents for __ZTV8OSObject
1870 for (VTableDylib
& dylib
: dylibs
) {
1871 // Only process dylibs in the level we are building
1872 // Existing collections were handled elsewhere
1873 if ( dylib
.cacheLevel
!= currentLevel
)
1876 const dyld3::MachOAnalyzer
* ma
= dylib
.ma
;
1877 const std::string
& dylibID
= dylib
.dylibID
;
1878 Diagnostics
& dylibDiags
= *dylib
.diags
;
1879 const std::vector
<std::string
>& dependencies
= dylib
.dependencies
;
1881 uint64_t loadAddress
= ma
->preferredLoadAddress();
1882 bool alreadyPatched
= (ma
== kernelMA
);
1883 auto visitSymbols
= ^(const char *symbolName
, uint64_t n_value
) {
1884 if ( strstr(symbolName
, superMetaclassPointerToken
) == nullptr )
1887 uint8_t* fixupLoc
= (uint8_t*)ma
+ (n_value
- loadAddress
);
1888 logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName
, dylibID
.c_str(), fixupLoc
);
1890 // 2 - Derive the name of the class from the super MetaClass pointer.
1891 std::string_view className
= extractString(symbolName
, osObjPrefix
, superMetaclassPointerToken
);
1892 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1893 if ( className
.empty() ) {
1894 logFunc("Unsupported vtable superclass name\n");
1897 logFunc("Class name: '%s'\n", std::string(className
).c_str());
1899 // 3 - Derive the name of the class's vtable from the name of the class
1900 // We support namespaces too which means adding an N before the class name and E after
1901 std::string classVTableName
= std::string(vtablePrefix
) + std::string(className
);
1902 logFunc("Class vtable name: '%s'\n", classVTableName
.c_str());
1904 uint64_t classVTableVMAddr
= 0;
1905 const DylibSymbols
& dylibSymbols
= dylibsToSymbols
[dylibID
];
1907 std::string namespacedVTableName
;
1908 SymbolLocation symbolLocation
= findVTablePatchingSymbol(classVTableName
, dylibSymbols
);
1909 if ( !symbolLocation
.found() ) {
1910 // If we didn't find a name then try again with namespaces
1911 namespacedVTableName
= std::string(vtablePrefix
) + "N" + std::string(className
) + "E";
1912 logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName
.c_str());
1913 symbolLocation
= findVTablePatchingSymbol(namespacedVTableName
, dylibSymbols
);
1915 if ( symbolLocation
.found() ) {
1916 classVTableVMAddr
= symbolLocation
.vmAddr
;
1918 dylibDiags
.error("Class vtables '%s' or '%s' is not an exported symbol",
1919 classVTableName
.c_str(), namespacedVTableName
.c_str());
1924 logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr
);
1925 const uint8_t* classVTableLoc
= (uint8_t*)ma
+ (classVTableVMAddr
- loadAddress
);
1927 // 4 - Follow the super MetaClass pointer to get the address of the super MetaClass's symbol
1928 uint64_t superMetaclassSymbolAddress
= 0;
1930 uint32_t vmAddr32
= 0;
1931 uint64_t vmAddr64
= 0;
1932 if ( aslrTracker
.hasRebaseTarget32(fixupLoc
, &vmAddr32
) ) {
1933 superMetaclassSymbolAddress
= vmAddr32
;
1934 } else if ( aslrTracker
.hasRebaseTarget64(fixupLoc
, &vmAddr64
) ) {
1935 superMetaclassSymbolAddress
= vmAddr64
;
1938 superMetaclassSymbolAddress
= *(uint64_t*)fixupLoc
;
1940 uint8_t highByte
= 0;
1941 if ( aslrTracker
.hasHigh8(fixupLoc
, &highByte
) ) {
1942 uint64_t tbi
= (uint64_t)highByte
<< 56;
1943 superMetaclassSymbolAddress
|= tbi
;
1946 logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress
);
1948 if ( superMetaclassSymbolAddress
== 0 ) {
1949 if ( classVTableName
== "__ZTV8OSObject" ) {
1950 // This is the base class of all objects, so it doesn't have a super class
1951 // We add it as a placeholder and set it to 'true' to show its already been processed
1952 VTable
& vtable
= vtables
[classVTableLoc
];
1954 vtable
.dylib
= &dylibSymbols
;
1955 vtable
.fromParentCollection
= false;
1956 vtable
.patched
= true;
1957 vtable
.name
= classVTableName
;
1962 // 5 - Look up the super MetaClass symbol by address
1963 // FIXME: VTable patching the auxKC with the superclass in the baseKC
1964 uint8_t superclassFixupLevel
= currentLevel
;
1965 aslrTracker
.has(fixupLoc
, &superclassFixupLevel
);
1967 auto& metaclassDefinitions
= collections
[superclassFixupLevel
].metaclassDefinitions
;
1968 auto metaclassIt
= metaclassDefinitions
.find(superMetaclassSymbolAddress
);
1969 if ( metaclassIt
== metaclassDefinitions
.end() ) {
1970 auto bindIt
= missingBindLocations
.find(fixupLoc
);
1971 if ( bindIt
!= missingBindLocations
.end() ) {
1972 dylibDiags
.error("Cannot find symbol for metaclass pointed to by '%s'. "
1973 "Expected symbol '%s' to be defined in another kext",
1974 symbolName
, bindIt
->second
.symbolName
.c_str());
1976 dylibDiags
.error("Cannot find symbol for metaclass pointed to by '%s'",
1982 // 6 - Derive the super class's name from the super MetaClass name
1983 std::string_view superClassName
= extractString(metaclassIt
->second
, osObjPrefix
, metaclassToken
);
1984 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1985 if ( superClassName
.empty() ) {
1986 logFunc("Unsupported vtable superclass name\n");
1989 logFunc("Superclass name: '%s'\n", std::string(superClassName
).c_str());
1991 // 7 - Derive the super class's vtable from the super class's name
1992 std::string superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
1994 // We support namespaces, so first try the superclass without the namespace, then again with it
1995 const uint8_t* superclassVTableLoc
= nullptr;
1996 bool superVTableIsInParentCollection
= false;
1997 for (unsigned i
= 0; i
!= 2; ++i
) {
1999 superclassVTableName
= std::string(vtablePrefix
) + + "N" + std::string(superClassName
) + "E";
2001 logFunc("Superclass vtable name: '%s'\n", superclassVTableName
.c_str());
2004 // First check if the superclass vtable comes from a dependent kext
2005 for (const std::string
& dependencyID
: dependencies
) {
2006 auto depIt
= dylibsToSymbols
.find(dependencyID
);
2007 if (depIt
== dylibsToSymbols
.end()) {
2008 dylibDiags
.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
2009 symbolName
, dependencyID
.c_str());
2013 const DylibSymbols
& dylibSymbols
= depIt
->second
;
2014 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
2015 if ( !symbolLocation
.found() )
2018 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
2019 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr
);
2020 superclassVTableLoc
= collections
[dylibSymbols
.dylibLevel
].basePointer
+ (superclassVTableVMAddr
- collections
[dylibSymbols
.dylibLevel
].baseAddress
);
2021 superVTableIsInParentCollection
= dylibSymbols
.dylibLevel
!= currentLevel
;
2025 if ( superclassVTableLoc
== nullptr ) {
2026 SymbolLocation symbolLocation
= findVTablePatchingSymbol(superclassVTableName
, dylibSymbols
);
2027 if ( symbolLocation
.found() ) {
2028 uint64_t superclassVTableVMAddr
= symbolLocation
.vmAddr
;
2029 superclassVTableLoc
= (uint8_t*)collectionBasePointer
+ (superclassVTableVMAddr
- collectionBaseAddress
);
2030 superVTableIsInParentCollection
= false;
2035 if ( superclassVTableLoc
!= nullptr )
2039 if ( superclassVTableLoc
== nullptr ) {
2040 superclassVTableName
= std::string(vtablePrefix
) + std::string(superClassName
);
2041 dylibDiags
.error("Superclass vtable '%s' is not exported from kext or its dependencies",
2042 superclassVTableName
.c_str());
2046 // Add an entry for this vtable
2047 VTable
& vtable
= vtables
[classVTableLoc
];
2048 vtable
.superVTable
= superclassVTableLoc
;
2050 vtable
.dylib
= &dylibSymbols
;
2051 vtable
.fromParentCollection
= false;
2052 vtable
.patched
|= alreadyPatched
;
2053 vtable
.name
= classVTableName
;
2055 // And an entry for the superclass vtable
2056 VTable
& supervtable
= vtables
[superclassVTableLoc
];
2057 supervtable
.fromParentCollection
= superVTableIsInParentCollection
;
2058 supervtable
.patched
|= alreadyPatched
;
2059 supervtable
.name
= superclassVTableName
;
2061 // Also calculate the metaclass vtable name so that we can patch it
2062 std::string metaclassVTableName
= std::string(metaclassVTablePrefix
) + std::string(className
) + metaclassVTableSuffix
;
2063 logFunc("Metaclass vtable name: '%s'\n", metaclassVTableName
.c_str());
2066 // Note its safe to just ignore missing metaclass symbols if we can't find them
2067 // If the binary links then kxld would have let it run
2068 SymbolLocation symbolLocation
= findVTablePatchingSymbol(metaclassVTableName
, dylibSymbols
);
2069 if ( symbolLocation
.found() ) {
2070 uint64_t metaclassVTableVMAddr
= symbolLocation
.vmAddr
;
2072 logFunc("Metaclass vtable vmAddr: '0x%llx'\n", metaclassVTableVMAddr
);
2073 uint8_t* metaclassVTableLoc
= (uint8_t*)ma
+ (metaclassVTableVMAddr
- loadAddress
);
2075 // Add an entry for this vtable
2076 VTable
& vtable
= vtables
[metaclassVTableLoc
];
2077 vtable
.superVTable
= baseMetaClassVTableLoc
;
2079 vtable
.dylib
= &dylibSymbols
;
2080 vtable
.fromParentCollection
= false;
2081 vtable
.patched
|= alreadyPatched
;
2082 vtable
.name
= metaclassVTableName
;
2087 ma
->forEachGlobalSymbol(dylibDiags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
2088 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
2089 visitSymbols(symbolName
, n_value
);
2092 ma
->forEachLocalSymbol(dylibDiags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
2093 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
2094 visitSymbols(symbolName
, n_value
);
2099 void VTablePatcher::calculateSymbols() {
2100 for (VTableDylib
& dylib
: dylibs
) {
2101 auto& symbolNames
= collections
[dylib
.cacheLevel
].symbolNames
;
2102 dylib
.ma
->forEachGlobalSymbol(*dylib
.diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
2103 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
2104 symbolNames
[n_value
] = symbolName
;
2106 dylib
.ma
->forEachLocalSymbol(*dylib
.diags
, ^(const char *symbolName
, uint64_t n_value
, uint8_t n_type
,
2107 uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
2108 symbolNames
[n_value
] = symbolName
;
2113 void VTablePatcher::patchVTables(Diagnostics
& diags
,
2114 std::map
<const uint8_t*, const VTableBindSymbol
>& missingBindLocations
,
2115 AppCacheBuilder::ASLR_Tracker
& aslrTracker
,
2116 uint8_t currentLevel
)
2118 const bool is64
= pointerSize
== 8;
2120 // If we have vtables to patch, then make sure we found the OSMetaClass symbol to patch against
2121 if ( (baseMetaClassVTableLoc
== nullptr) && !vtables
.empty() ) {
2122 diags
.error("Could not find OSMetaClass vtable in kernel binary");
2128 auto calculateVTableEntries
= ^(const uint8_t* vtableLoc
, VTable
& vtable
) {
2129 assert(vtable
.patched
);
2130 logFunc("Calculating vtable: '%s'\n", vtable
.name
.c_str());
2132 // The first entry we want to patch is 2 pointers from the start of the vtable
2133 const uint8_t* relocLoc
= vtableLoc
+ (2 * pointerSize
);
2135 if ( vtable
.fromParentCollection
) {
2136 auto it
= existingCollectionFixupLocations
.find(relocLoc
);
2137 while ( it
!= existingCollectionFixupLocations
.end() ) {
2138 const Fixup
& fixup
= it
->second
;
2139 uint64_t targetVMAddr
= fixup
.targetVMAddr
;
2140 uint16_t diversity
= fixup
.diversity
;
2141 bool hasAddrDiv
= fixup
.hasAddrDiv
;
2142 uint8_t key
= fixup
.key
;
2143 bool hasPointerAuth
= fixup
.hasPointerAuth
;
2144 uint32_t cacheLevel
= fixup
.cacheLevel
;
2145 vtable
.entries
.push_back({ relocLoc
, targetVMAddr
, cacheLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
});
2146 relocLoc
+= pointerSize
;
2147 it
= existingCollectionFixupLocations
.find(relocLoc
);
2150 while ( aslrTracker
.has((void*)relocLoc
) ||
2151 (missingBindLocations
.find(relocLoc
) != missingBindLocations
.end()) ) {
2153 uint16_t diversity
= 0;
2154 bool hasAddrDiv
= false;
2156 bool hasPointerAuth
= false;
2157 uint8_t cacheLevel
= currentLevel
;
2159 if ( aslrTracker
.has((void*)relocLoc
, &cacheLevel
) ) {
2160 hasPointerAuth
= aslrTracker
.hasAuthData((void*)relocLoc
, &diversity
, &hasAddrDiv
, &key
);
2163 uint64_t targetVMAddr
= 0;
2165 uint32_t vmAddr32
= 0;
2166 uint64_t vmAddr64
= 0;
2167 if ( aslrTracker
.hasRebaseTarget32((void*)relocLoc
, &vmAddr32
) ) {
2168 targetVMAddr
= vmAddr32
;
2169 } else if ( aslrTracker
.hasRebaseTarget64((void*)relocLoc
, &vmAddr64
) ) {
2170 targetVMAddr
= vmAddr64
;
2173 targetVMAddr
= *(uint64_t*)relocLoc
;
2175 uint8_t highByte
= 0;
2176 if ( aslrTracker
.hasHigh8((void*)relocLoc
, &highByte
) ) {
2177 uint64_t tbi
= (uint64_t)highByte
<< 56;
2178 targetVMAddr
|= tbi
;
2182 vtable
.entries
.push_back({ relocLoc
, targetVMAddr
, cacheLevel
, diversity
, hasAddrDiv
, key
, hasPointerAuth
});
2183 relocLoc
+= pointerSize
;
2187 logFunc("Found %d vtable items: '%s'\n", vtable
.entries
.size(), vtable
.name
.c_str());
2190 // Map from MachO to diagnostics to emit for that file
2191 std::unordered_map
<const dyld3::MachOAnalyzer
*, Diagnostics
*> diagsMap
;
2192 for (VTableDylib
& dylib
: dylibs
)
2193 diagsMap
[dylib
.ma
] = dylib
.diags
;
2195 uint32_t numPatchedVTables
= 0;
2196 for (auto& vtableEntry
: vtables
) {
2197 if ( vtableEntry
.second
.patched
) {
2198 calculateVTableEntries(vtableEntry
.first
, vtableEntry
.second
);
2199 ++numPatchedVTables
;
2202 while ( numPatchedVTables
!= vtables
.size() ) {
2203 typedef std::pair
<const uint8_t*, VTable
*> VTableEntry
;
2204 std::vector
<VTableEntry
> toBePatched
;
2205 for (auto& vtableEntry
: vtables
) {
2206 if ( vtableEntry
.second
.patched
)
2208 auto superIt
= vtables
.find(vtableEntry
.second
.superVTable
);
2209 assert(superIt
!= vtables
.end());
2210 if ( !superIt
->second
.patched
)
2212 logFunc("Found unpatched vtable: '%s' with patched superclass '%s'\n",
2213 vtableEntry
.second
.name
.c_str(), superIt
->second
.name
.c_str());
2214 toBePatched
.push_back({ vtableEntry
.first
, &vtableEntry
.second
});
2217 if ( toBePatched
.empty() ) {
2218 // If we can't find anything to patch, then print out what we have left
2219 for (const auto& vtableEntry
: vtables
) {
2220 if ( vtableEntry
.second
.patched
)
2222 auto superIt
= vtables
.find(vtableEntry
.second
.superVTable
);
2223 assert(superIt
!= vtables
.end());
2224 diags
.error("Found unpatched vtable: '%s' with unpatched superclass '%s'\n",
2225 vtableEntry
.second
.name
.c_str(), superIt
->second
.name
.c_str());
2230 for (VTableEntry
& vtableEntry
: toBePatched
) {
2231 VTable
& vtable
= *vtableEntry
.second
;
2233 // We can immediately mark this as patched as then calculateVTableEntries can make
2234 // sure we never ask for vtables which aren't ready yet
2235 vtable
.patched
= true;
2236 ++numPatchedVTables
;
2238 auto superIt
= vtables
.find(vtable
.superVTable
);
2239 logFunc("Processing unpatched vtable: '%s' with patched superclass '%s'\n",
2240 vtable
.name
.c_str(), superIt
->second
.name
.c_str());
2242 calculateVTableEntries(vtableEntry
.first
, vtable
);
2244 const VTable
& supervtable
= superIt
->second
;
2245 if ( vtable
.entries
.size() < supervtable
.entries
.size() ) {
2246 // Try emit the error to a per dylib diagnostic object if we can find one
2247 auto diagIt
= diagsMap
.find(vtable
.ma
);
2248 Diagnostics
* diag
= (diagIt
!= diagsMap
.end()) ? diagIt
->second
: &diags
;
2249 diag
->error("Malformed vtable. Super class '%s' has %lu entries vs subclass '%s' with %lu entries",
2250 supervtable
.name
.c_str(), supervtable
.entries
.size(),
2251 vtable
.name
.c_str(), vtable
.entries
.size());
2255 const std::unordered_map
<const uint8_t*, VTableBindSymbol
>& resolvedBindLocations
= vtable
.dylib
->resolvedBindLocations
;
2256 for (uint64_t entryIndex
= 0; entryIndex
!= supervtable
.entries
.size(); ++entryIndex
) {
2257 logFuncVerbose("Processing entry %lld: super[0x%llx] vs subclass[0x%llx]\n", entryIndex
,
2258 *(uint64_t*)supervtable
.entries
[entryIndex
].location
,
2259 *(uint64_t*)vtable
.entries
[entryIndex
].location
);
2261 VTable::Entry
& vtableEntry
= vtable
.entries
[entryIndex
];
2262 const VTable::Entry
& superVTableEntry
= supervtable
.entries
[entryIndex
];
2264 const uint8_t* patchLoc
= vtableEntry
.location
;
2265 uint64_t targetVMAddr
= superVTableEntry
.targetVMAddr
;
2267 // 1) If the symbol is defined locally, do not patch
2268 // This corresponds to a rebase not a bind, so if we have a match in our bind set
2269 // we were bound to another image, and should see if that bind should be overridden by a
2270 // better vtable patch.
2271 auto resolvedBindIt
= resolvedBindLocations
.find(patchLoc
);
2272 auto unresolvedBindIt
= missingBindLocations
.find(patchLoc
);
2273 if ( (resolvedBindIt
== resolvedBindLocations
.end()) && (unresolvedBindIt
== missingBindLocations
.end()) )
2276 // Find the child and parent symbols, if any
2277 const char* childSymbolName
= nullptr;
2278 const char* parentSymbolName
= nullptr;
2280 if ( resolvedBindIt
!= resolvedBindLocations
.end() ) {
2281 childSymbolName
= resolvedBindIt
->second
.symbolName
.c_str();
2283 assert(unresolvedBindIt
!= missingBindLocations
.end());
2284 childSymbolName
= unresolvedBindIt
->second
.symbolName
.c_str();
2287 auto& symbolNames
= collections
[superVTableEntry
.targetCacheLevel
].symbolNames
;
2288 auto parentNameIt
= symbolNames
.find(superVTableEntry
.targetVMAddr
);
2289 if ( parentNameIt
!= symbolNames
.end() )
2290 parentSymbolName
= parentNameIt
->second
;
2292 // The child entry can be NULL when a locally-defined, non-external
2293 // symbol is stripped. We wouldn't patch this entry anyway, so we just skip it.
2294 if ( childSymbolName
== nullptr ) {
2298 // It's possible for the patched parent entry not to have a symbol
2299 // (e.g. when the definition is inlined). We can't patch this entry no
2300 // matter what, so we'll just skip it and die later if it's a problem
2301 // (which is not likely).
2302 if ( parentSymbolName
== nullptr ) {
2306 logFuncVerbose("Processing entry %lld: super[%s] vs subclass[%s]\n", entryIndex
,
2307 parentSymbolName
, childSymbolName
);
2309 // 2) If the child is a pure virtual function, do not patch.
2310 // In general, we want to proceed with patching when the symbol is
2311 // externally defined because pad slots fall into this category.
2312 // The pure virtual function symbol is special case, as the pure
2313 // virtual property itself overrides the parent's implementation.
2314 if ( !strcmp(childSymbolName
, "___cxa_pure_virtual") ) {
2318 // 3) If the symbols are the same, do not patch
2319 // Note that if the symbol was a missing bind, then we'll still patch
2320 // This is the case where the vtable entry itself was a local symbol
2321 // so we had originally failed to bind to it as it wasn't exported, but it
2322 // has the same name as the parent name
2323 if ( !strcmp(childSymbolName
, parentSymbolName
) && (unresolvedBindIt
== missingBindLocations
.end()) ) {
2328 // FIXME: Implement this
2330 // 4) If the parent vtable entry is a pad slot, and the child does not
2331 // match it, then the child was built against a newer version of the
2332 // libraries, so it is binary-incompatible.
2333 require_action(!kxld_sym_name_is_padslot(parent_entry
->patched
.name
),
2334 finish
, rval
= KERN_FAILURE
;
2335 kxld_log(kKxldLogPatching
, kKxldLogErr
,
2336 kKxldLogParentOutOfDate
,
2337 kxld_demangle(super_vtable
->name
, &demangled_name1
,
2338 &demangled_length1
),
2339 kxld_demangle(vtable
->name
, &demangled_name2
,
2340 &demangled_length2
)));
2343 logFunc("Patching entry '%s' in '%s' to point to '%s' in superclass '%s'\n",
2344 childSymbolName
, vtable
.name
.c_str(), parentSymbolName
, supervtable
.name
.c_str());
2347 *((uint64_t*)patchLoc
) = targetVMAddr
;
2349 *((uint32_t*)patchLoc
) = (uint32_t)targetVMAddr
;
2352 // FIXME: When we support a baseKC, pageableKC, and auxKC, the supervtable cache level
2353 // may no longer be correct here as we may be:
2354 // - patching a vtable in auxKC
2355 // - where the supervtable is in pageableKC
2356 // - but the entry slot points to baseKC
2357 aslrTracker
.add((void*)patchLoc
, superVTableEntry
.targetCacheLevel
);
2359 // Add pointer auth if the super vtable had it
2360 if ( superVTableEntry
.hasPointerAuth
)
2361 aslrTracker
.setAuthData((void*)patchLoc
, superVTableEntry
.diversity
,
2362 superVTableEntry
.hasAddrDiv
, superVTableEntry
.key
);
2364 // Update this vtable entry in case there are any subclasses which then need to use it
2365 // to be patched themselves
2366 vtableEntry
.targetVMAddr
= superVTableEntry
.targetVMAddr
;
2367 vtableEntry
.targetCacheLevel
= superVTableEntry
.targetCacheLevel
;
2368 vtableEntry
.diversity
= superVTableEntry
.diversity
;
2369 vtableEntry
.hasAddrDiv
= superVTableEntry
.hasAddrDiv
;
2370 vtableEntry
.key
= superVTableEntry
.key
;
2371 vtableEntry
.hasPointerAuth
= superVTableEntry
.hasPointerAuth
;
2373 missingBindLocations
.erase(patchLoc
);
2379 typedef std::pair
<uint8_t, uint64_t> CacheOffset
;
2381 struct DylibSymbolLocation
{
2382 const DylibSymbols
* dylibSymbols
;
2383 uint64_t symbolVMAddr
;
2387 struct DylibFixups
{
2388 void processFixups(const std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
2389 const std::unordered_map
<std::string_view
, std::vector
<DylibSymbolLocation
>>& symbolMap
,
2390 const std::string
& kernelID
, const CacheBuilder::ASLR_Tracker
& aslrTracker
);
2393 const dyld3::MachOAnalyzer
* ma
= nullptr;
2394 DylibSymbols
& dylibSymbols
;
2395 Diagnostics
& dylibDiag
;
2396 const std::vector
<std::string
>& dependencies
;
2404 struct BranchStubData
{
2405 CacheOffset targetCacheOffset
;
2406 const void* fixupLoc
;
2407 uint64_t fixupVMOffset
;
2409 std::unordered_map
<const uint8_t*, VTableBindSymbol
> missingBindLocations
;
2410 std::unordered_map
<void*, uint8_t> fixupLocs
;
2411 std::unordered_map
<void*, uint8_t> fixupHigh8s
;
2412 std::unordered_map
<void*, AuthData
> fixupAuths
;
2413 std::vector
<BranchStubData
> branchStubs
;
2416 void DylibFixups::processFixups(const std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
,
2417 const std::unordered_map
<std::string_view
, std::vector
<DylibSymbolLocation
>>& symbolMap
,
2418 const std::string
& kernelID
, const CacheBuilder::ASLR_Tracker
& aslrTracker
) {
2419 auto& resolvedBindLocations
= dylibSymbols
.resolvedBindLocations
;
2420 const std::string
& dylibID
= dylibSymbols
.dylibName
;
2422 const bool _is64
= true;
2423 const bool isThirdPartyKext
= (dylibID
.find("com.apple") != 0);
2425 // The magic symbol for missing weak imports
2426 const char* missingWeakImportSymbolName
= "_gOSKextUnresolved";
2428 struct SymbolDefinition
{
2429 uint64_t symbolVMAddr
;
2430 uint32_t kernelCollectionLevel
;
2432 auto findDependencyWithSymbol
= [&symbolMap
, &isThirdPartyKext
](const char* symbolName
,
2433 const std::vector
<std::string
>& dependencies
) {
2434 auto symbolMapIt
= symbolMap
.find(symbolName
);
2435 if ( symbolMapIt
== symbolMap
.end() )
2436 return (SymbolDefinition
){ ~0ULL, 0 };
2437 // Find the first dependency in the list
2438 const std::vector
<DylibSymbolLocation
>& dylibSymbols
= symbolMapIt
->second
;
2439 // The massively common case is 1 or 2 definitions of a given symbol, so a basic searhc should be
2441 for (const std::string
& dependency
: dependencies
) {
2442 for (const DylibSymbolLocation
& dylibSymbol
: dylibSymbols
) {
2443 if ( dependency
== dylibSymbol
.dylibSymbols
->dylibName
) {
2444 // If the Apple kext we are linking has a symbol set, and the user is a third-party kext,
2445 // then only allow the third party kext to see symbols in the kext export list, if it has one
2446 const bool isAppleKext
= (dependency
.find("com.apple") == 0);
2447 if ( isThirdPartyKext
&& isAppleKext
&& !dylibSymbol
.isKPI
)
2449 return (SymbolDefinition
){ dylibSymbol
.symbolVMAddr
, dylibSymbol
.dylibSymbols
->dylibLevel
};
2453 return (SymbolDefinition
){ ~0ULL, 0 };
2456 if (ma
->hasChainedFixups()) {
2457 // build array of targets
2459 const VTableBindSymbol bindSymbol
;
2461 uint32_t dylibLevel
;
2462 bool isMissingWeakImport
;
2463 bool isMissingSymbol
;
2465 __block
std::vector
<BindTarget
> bindTargets
;
2466 __block
bool foundMissingWeakImport
= false;
2467 ma
->forEachChainedFixupTarget(dylibDiag
, ^(int libOrdinal
, const char* symbolName
, uint64_t addend
,
2468 bool weakImport
, bool& stop
) {
2469 if ( (libOrdinal
!= BIND_SPECIAL_DYLIB_FLAT_LOOKUP
) && (libOrdinal
!= BIND_SPECIAL_DYLIB_WEAK_LOOKUP
) ) {
2470 dylibDiag
.error("All chained binds should be flat namespace or weak lookups");
2475 if ( addend
!= 0 ) {
2476 dylibDiag
.error("Chained bind addends are not supported right now");
2481 VTableBindSymbol bindSymbol
= { dylibID
, symbolName
};
2482 bool isMissingSymbol
= false;
2484 for (const std::string
& dependencyID
: dependencies
) {
2485 auto depIt
= dylibsToSymbols
.find(dependencyID
);
2486 if (depIt
== dylibsToSymbols
.end()) {
2487 dylibDiag
.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
2488 symbolName
, dependencyID
.c_str());
2493 const DylibSymbols
& dylibSymbols
= depIt
->second
;
2494 auto exportIt
= dylibSymbols
.globals
.find(symbolName
);
2495 if ( exportIt
== dylibSymbols
.globals
.end() )
2498 isMissingSymbol
= false;
2499 bindTargets
.push_back({ bindSymbol
, exportIt
->second
, dylibSymbols
.dylibLevel
, false, isMissingSymbol
});
2503 // If the symbol is weak, and we didn't find it in our listed
2504 // dependencies, then use our own definition
2505 if ( libOrdinal
== BIND_SPECIAL_DYLIB_WEAK_LOOKUP
) {
2506 auto dylibIt
= dylibsToSymbols
.find(dylibID
);
2507 if (dylibIt
== dylibsToSymbols
.end()) {
2508 dylibDiag
.error("Failed to bind weak '%s' as could not find a define in self",
2513 const DylibSymbols
& dylibSymbols
= dylibIt
->second
;
2514 auto exportIt
= dylibSymbols
.globals
.find(symbolName
);
2515 if ( exportIt
!= dylibSymbols
.globals
.end() ) {
2516 isMissingSymbol
= false;
2517 bindTargets
.push_back({ bindSymbol
, exportIt
->second
, dylibSymbols
.dylibLevel
, false, isMissingSymbol
});
2523 // Find _gOSKextUnresolved in the kernel
2524 // Weak imports are not compared against null, but instead against the address of that symbol
2525 auto kernelSymbolsIt
= dylibsToSymbols
.find(kernelID
);
2526 assert(kernelSymbolsIt
!= dylibsToSymbols
.end());
2527 const DylibSymbols
& kernelSymbols
= kernelSymbolsIt
->second
;
2528 auto exportIt
= kernelSymbols
.globals
.find(missingWeakImportSymbolName
);
2529 if (exportIt
!= kernelSymbols
.globals
.end()) {
2530 foundMissingWeakImport
= true;
2531 isMissingSymbol
= false;
2532 bindTargets
.push_back({ bindSymbol
, exportIt
->second
, kernelSymbols
.dylibLevel
, true, isMissingSymbol
});
2535 dylibDiag
.error("Weak bind symbol '%s' not found in kernel", missingWeakImportSymbolName
);
2539 // Store missing binds for later. They may be fixed by vtable patching
2540 isMissingSymbol
= true;
2541 bindTargets
.push_back({ bindSymbol
, 0, 0, false, isMissingSymbol
});
2543 if ( dylibDiag
.hasError() )
2546 if( foundMissingWeakImport
) {
2547 // If we found a missing weak import, then we need to check that the user did
2548 // something like "if ( &foo == &gOSKextUnresolved )"
2549 // If they didn't use gOSKextUnresolved at all, then there's no way they could be doing that check
2550 auto kernelSymbolsIt
= dylibsToSymbols
.find(kernelID
);
2551 assert(kernelSymbolsIt
!= dylibsToSymbols
.end());
2552 const DylibSymbols
& kernelSymbols
= kernelSymbolsIt
->second
;
2553 auto exportIt
= kernelSymbols
.globals
.find(missingWeakImportSymbolName
);
2554 assert(exportIt
!= kernelSymbols
.globals
.end());
2555 bool foundUseOfMagicSymbol
= false;
2556 for (const BindTarget
& bindTarget
: bindTargets
) {
2557 // Skip the missing weak imports
2558 if ( bindTarget
.isMissingWeakImport
|| bindTarget
.isMissingSymbol
)
2560 // Skip anything which isn't the symbol we are looking for
2561 if ( (bindTarget
.dylibLevel
!= 0) && (bindTarget
.vmAddr
!= exportIt
->second
) )
2563 foundUseOfMagicSymbol
= true;
2567 if ( !foundUseOfMagicSymbol
) {
2568 dylibDiag
.error("Has weak references but does not test for them. "
2569 "Test for weak references with OSKextSymbolIsResolved().");
2574 // uint64_t baseAddress = ma->preferredLoadAddress();
2576 ma
->withChainStarts(dylibDiag
, 0, ^(const dyld_chained_starts_in_image
* starts
) {
2577 ma
->forEachFixupInAllChains(dylibDiag
, starts
, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk
* fixupLoc
, const dyld_chained_starts_in_segment
* segInfo
, bool& stop
) {
2578 switch (segInfo
->pointer_format
) {
2579 case DYLD_CHAINED_PTR_64_OFFSET
:
2580 if ( fixupLoc
->generic64
.bind
.bind
) {
2581 uint64_t bindOrdinal
= fixupLoc
->generic64
.bind
.ordinal
;
2582 if ( bindOrdinal
>= bindTargets
.size() ) {
2583 dylibDiag
.error("Bind ordinal %lld out of range %lu", bindOrdinal
, bindTargets
.size());
2588 const BindTarget
& bindTarget
= bindTargets
[bindOrdinal
];
2589 if ( bindTarget
.isMissingSymbol
) {
2590 // Track this missing bind for later
2591 // For now we bind it to null and don't slide it.
2592 fixupLoc
->raw64
= 0;
2593 missingBindLocations
[(const uint8_t*)fixupLoc
] = bindTarget
.bindSymbol
;
2595 fixupLoc
->raw64
= bindTarget
.vmAddr
;
2596 fixupLocs
[fixupLoc
] = bindTarget
.dylibLevel
;
2597 resolvedBindLocations
[(const uint8_t*)fixupLoc
] = bindTarget
.bindSymbol
;
2601 // convert rebase chain entry to raw pointer to target vmaddr
2602 uint64_t targetVMAddr
= fixupLoc
->generic64
.rebase
.target
;
2603 uint64_t sideTableAddr
= 0;
2604 if ( aslrTracker
.hasRebaseTarget64(fixupLoc
, &sideTableAddr
) )
2605 targetVMAddr
= sideTableAddr
;
2606 // store high8 in side table
2607 if ( fixupLoc
->generic64
.rebase
.high8
)
2608 fixupHigh8s
[fixupLoc
] = fixupLoc
->generic64
.rebase
.high8
;
2609 fixupLoc
->raw64
= targetVMAddr
;
2612 case DYLD_CHAINED_PTR_ARM64E_KERNEL
:
2613 if ( fixupLoc
->arm64e
.bind
.bind
) {
2614 uint64_t bindOrdinal
= fixupLoc
->arm64e
.bind
.ordinal
;
2615 if ( bindOrdinal
>= bindTargets
.size() ) {
2616 dylibDiag
.error("Bind ordinal %lld out of range %lu", bindOrdinal
, bindTargets
.size());
2621 const BindTarget
& bindTarget
= bindTargets
[bindOrdinal
];
2622 uint64_t targetVMAddr
= bindTarget
.vmAddr
;
2624 if ( fixupLoc
->arm64e
.authBind
.auth
) {
2625 // store auth data in side table
2626 fixupAuths
[fixupLoc
] = {
2627 (uint16_t)fixupLoc
->arm64e
.authBind
.diversity
,
2628 (bool)fixupLoc
->arm64e
.authBind
.addrDiv
,
2629 (uint8_t)fixupLoc
->arm64e
.authBind
.key
2633 // plain binds can have addend in chain
2634 targetVMAddr
+= fixupLoc
->arm64e
.bind
.addend
;
2636 // change location from a chain ptr into a raw pointer to the target vmaddr
2637 if ( bindTarget
.isMissingSymbol
) {
2638 // Track this missing bind for later
2639 // For now we bind it to null and don't slide it.
2640 fixupLoc
->raw64
= 0;
2641 missingBindLocations
[(const uint8_t*)fixupLoc
] = bindTarget
.bindSymbol
;
2643 fixupLoc
->raw64
= targetVMAddr
;
2644 fixupLocs
[fixupLoc
] = bindTarget
.dylibLevel
;
2645 resolvedBindLocations
[(const uint8_t*)fixupLoc
] = bindTarget
.bindSymbol
;
2649 // convert rebase chain entry to raw pointer to target vmaddr
2650 if ( fixupLoc
->arm64e
.rebase
.auth
) {
2651 // store auth data in side table
2652 fixupAuths
[fixupLoc
] = {
2653 (uint16_t)fixupLoc
->arm64e
.authRebase
.diversity
,
2654 (bool)fixupLoc
->arm64e
.authRebase
.addrDiv
,
2655 (uint8_t)fixupLoc
->arm64e
.authRebase
.key
2657 uint64_t targetVMAddr
= fixupLoc
->arm64e
.authRebase
.target
;
2658 fixupLoc
->raw64
= targetVMAddr
;
2661 uint64_t targetVMAddr
= fixupLoc
->arm64e
.rebase
.target
;
2662 uint64_t sideTableAddr
;
2663 if ( aslrTracker
.hasRebaseTarget64(fixupLoc
, &sideTableAddr
) )
2664 targetVMAddr
= sideTableAddr
;
2665 // store high8 in side table
2666 if ( fixupLoc
->arm64e
.rebase
.high8
)
2667 fixupHigh8s
[fixupLoc
] = fixupLoc
->arm64e
.rebase
.high8
;
2668 fixupLoc
->raw64
= targetVMAddr
;
2673 fprintf(stderr
, "unknown pointer type %d\n", segInfo
->pointer_format
);
2681 // If we have any missing imports, then they should check for the kernel symbol
2682 // Grab a hold of that now if it exists so we can check it later
2683 __block
bool foundUseOfMagicSymbol
= false;
2684 __block
bool foundMissingWeakImport
= false;
2686 const uint64_t loadAddress
= ma
->preferredLoadAddress();
2687 ma
->forEachBind(dylibDiag
, ^(uint64_t runtimeOffset
, int libOrdinal
, uint8_t bindType
,
2688 const char *symbolName
, bool weakImport
, bool lazyBind
, uint64_t addend
, bool &stop
) {
2689 // printf("Bind at 0x%llx to '%s'\n", runtimeOffset, symbolName);
2690 // Kext binds are a flat namespace so walk until we find the symbol we need
2691 bool foundSymbol
= false;
2692 VTableBindSymbol bindSymbol
= { dylibID
, symbolName
};
2693 if (SymbolDefinition symbolDef
= findDependencyWithSymbol(symbolName
, dependencies
); symbolDef
.symbolVMAddr
!= ~0ULL) {
2694 // Set the bind to the target address since we found it
2695 uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2696 if ( bindType
== BIND_TYPE_POINTER
) {
2698 *((uint64_t*)fixupLoc
) = symbolDef
.symbolVMAddr
;
2700 *((uint32_t*)fixupLoc
) = (uint32_t)symbolDef
.symbolVMAddr
;
2702 // Only track regular fixups for ASLR, not branch fixups
2703 fixupLocs
[fixupLoc
] = symbolDef
.kernelCollectionLevel
;
2704 resolvedBindLocations
[(const uint8_t*)fixupLoc
] = bindSymbol
;
2705 } else if ( bindType
== BIND_TYPE_TEXT_PCREL32
) {
2706 // The value to store is the difference between the bind target
2707 // and the value of the PC after this instruction
2708 uint64_t targetAddress
= 0;
2709 if ( dylibSymbols
.dylibLevel
!= symbolDef
.kernelCollectionLevel
) {
2710 // Record this for later as we want to create stubs serially
2711 CacheOffset targetCacheOffset
= { symbolDef
.kernelCollectionLevel
, symbolDef
.symbolVMAddr
};
2712 branchStubs
.emplace_back((BranchStubData
){
2713 .targetCacheOffset
= targetCacheOffset
,
2714 .fixupLoc
= fixupLoc
,
2715 .fixupVMOffset
= runtimeOffset
2718 targetAddress
= symbolDef
.symbolVMAddr
;
2719 uint64_t diffValue
= targetAddress
- (loadAddress
+ runtimeOffset
+ 4);
2720 *((uint32_t*)fixupLoc
) = (uint32_t)diffValue
;
2723 dylibDiag
.error("Unexpected bind type: %d", bindType
);
2731 if ( foundSymbol
&& !foundUseOfMagicSymbol
) {
2732 foundUseOfMagicSymbol
= (strcmp(symbolName
, missingWeakImportSymbolName
) == 0);
2736 for (const std::string
& dependencyID
: dependencies
) {
2737 auto depIt
= dylibsToSymbols
.find(dependencyID
);
2738 if (depIt
== dylibsToSymbols
.end()) {
2739 dylibDiag
.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
2740 symbolName
, dependencyID
.c_str());
2745 const DylibSymbols
& dylibSymbols
= depIt
->second
;
2746 auto exportIt
= dylibSymbols
.globals
.find(symbolName
);
2747 if ( exportIt
== dylibSymbols
.globals
.end() )
2749 findDependencyWithSymbol(symbolName
, dependencies
);
2754 // If the symbol is weak, and we didn't find it in our listed
2755 // dependencies, then use our own definition
2756 if ( !foundSymbol
&& (libOrdinal
== BIND_SPECIAL_DYLIB_WEAK_LOOKUP
) ) {
2757 auto dylibIt
= dylibsToSymbols
.find(dylibID
);
2758 if (dylibIt
== dylibsToSymbols
.end()) {
2759 dylibDiag
.error("Failed to bind weak '%s' as could not find a define in self",
2765 const DylibSymbols
& dylibSymbols
= dylibIt
->second
;
2766 auto exportIt
= dylibSymbols
.globals
.find(symbolName
);
2767 if ( exportIt
!= dylibSymbols
.globals
.end() ) {
2768 // Set the bind to the target address since we found it
2769 uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2770 if ( bindType
== BIND_TYPE_POINTER
) {
2772 *((uint64_t*)fixupLoc
) = exportIt
->second
;
2774 *((uint32_t*)fixupLoc
) = (uint32_t)exportIt
->second
;
2776 // Only track regular fixups for ASLR, not branch fixups
2777 fixupLocs
[fixupLoc
] = dylibSymbols
.dylibLevel
;
2778 resolvedBindLocations
[(const uint8_t*)fixupLoc
] = bindSymbol
;
2779 } else if ( bindType
== BIND_TYPE_TEXT_PCREL32
) {
2780 // We should never have a branch to a weak bind as we should have had a GOT for these
2781 dylibDiag
.error("Unexpected weak bind type: %d", bindType
);
2785 dylibDiag
.error("Unexpected bind type: %d", bindType
);
2794 if ( !foundSymbol
&& weakImport
) {
2795 if ( bindType
!= BIND_TYPE_POINTER
) {
2796 dylibDiag
.error("Unexpected bind type: %d", bindType
);
2800 // Find _gOSKextUnresolved in the kernel
2801 // Weak imports are not compared against null, but instead against the address of that symbol
2802 auto kernelSymbolsIt
= dylibsToSymbols
.find(kernelID
);
2803 assert(kernelSymbolsIt
!= dylibsToSymbols
.end());
2804 const DylibSymbols
& kernelSymbols
= kernelSymbolsIt
->second
;
2805 auto exportIt
= kernelSymbols
.globals
.find(missingWeakImportSymbolName
);
2806 if (exportIt
!= kernelSymbols
.globals
.end()) {
2807 foundMissingWeakImport
= true;
2809 uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2811 *((uint64_t*)fixupLoc
) = exportIt
->second
;
2813 *((uint32_t*)fixupLoc
) = (uint32_t)exportIt
->second
;
2815 // Only track regular fixups for ASLR, not branch fixups
2816 fixupLocs
[fixupLoc
] = kernelSymbols
.dylibLevel
;
2819 dylibDiag
.error("Weak bind symbol '%s' not found in kernel", missingWeakImportSymbolName
);
2823 if ( !foundSymbol
) {
2824 // Store missing binds for later. They may be fixed by vtable patching
2825 const uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2826 missingBindLocations
[fixupLoc
] = bindSymbol
;
2828 }, ^(const char *symbolName
) {
2829 dylibDiag
.error("Strong binds are not supported right now");
2832 if ( foundMissingWeakImport
&& !foundUseOfMagicSymbol
) {
2833 dylibDiag
.error("Has weak references but does not test for them. "
2834 "Test for weak references with OSKextSymbolIsResolved().");
2838 ma
->forEachRebase(dylibDiag
, false, ^(uint64_t runtimeOffset
, bool &stop
) {
2839 uint8_t* fixupLoc
= (uint8_t*)ma
+runtimeOffset
;
2840 fixupLocs
[fixupLoc
] = (uint8_t)~0U;
2844 // A helper to automatically call CFRelease when we go out of scope
2845 struct AutoReleaseTypeRef
{
2846 AutoReleaseTypeRef() = default;
2847 ~AutoReleaseTypeRef() {
2848 if ( ref
!= nullptr ) {
2852 void setRef(CFTypeRef typeRef
) {
2853 assert(ref
== nullptr);
2857 CFTypeRef ref
= nullptr;
2860 static std::unique_ptr
<std::unordered_set
<std::string
>> getKPI(Diagnostics
& diags
, const dyld3::MachOAnalyzer
* ma
,
2861 std::string_view dylibID
) {
2862 bool isAppleKext
= (dylibID
.find("com.apple") == 0);
2866 __block
std::list
<std::string
> nonASCIIStrings
;
2867 auto getString
= ^(Diagnostics
& diags
, CFStringRef symbolNameRef
) {
2868 const char* symbolName
= CFStringGetCStringPtr(symbolNameRef
, kCFStringEncodingUTF8
);
2869 if ( symbolName
!= nullptr )
2872 CFIndex len
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef
), kCFStringEncodingUTF8
);
2873 char buffer
[len
+ 1];
2874 if ( !CFStringGetCString(symbolNameRef
, buffer
, len
, kCFStringEncodingUTF8
) ) {
2875 diags
.error("Could not convert string to ASCII");
2876 return (const char*)nullptr;
2879 nonASCIIStrings
.push_back(buffer
);
2880 return nonASCIIStrings
.back().c_str();
2883 uint64_t symbolSetsSize
= 0;
2884 const void* symbolSetsContent
= ma
->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize
);
2885 if ( symbolSetsContent
== nullptr )
2888 AutoReleaseTypeRef dataRefReleaser
;
2889 AutoReleaseTypeRef plistRefReleaser
;
2891 std::unordered_set
<std::string
> symbols
;
2892 CFDataRef dataRef
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
, (const uint8_t*)symbolSetsContent
, symbolSetsSize
, kCFAllocatorNull
);
2893 if ( dataRef
== nullptr ) {
2894 diags
.error("Could not create data ref for kpi");
2897 dataRefReleaser
.setRef(dataRef
);
2899 CFErrorRef errorRef
= nullptr;
2900 CFPropertyListRef plistRef
= CFPropertyListCreateWithData(kCFAllocatorDefault
, dataRef
, kCFPropertyListImmutable
, nullptr, &errorRef
);
2901 if (errorRef
!= nullptr) {
2902 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
2903 diags
.error("Could not load plist because :%s", CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
2904 CFRelease(errorRef
);
2907 if ( plistRef
== nullptr ) {
2908 diags
.error("Could not create plist ref for kpi");
2911 plistRefReleaser
.setRef(plistRef
);
2913 if ( CFGetTypeID(plistRef
) != CFDictionaryGetTypeID() ) {
2914 diags
.error("kpi plist should be a dictionary");
2918 CFDictionaryRef symbolSetsDictRef
= (CFDictionaryRef
)plistRef
;
2920 // CFBundleIdentifier
2921 CFStringRef bundleIDRef
= (CFStringRef
)CFDictionaryGetValue(symbolSetsDictRef
, CFSTR("CFBundleIdentifier"));
2922 if ( (bundleIDRef
== nullptr) || (CFGetTypeID(bundleIDRef
) != CFStringGetTypeID()) ) {
2923 diags
.error("kpi bundle ID should be a string");
2927 const char* bundleID
= getString(diags
, bundleIDRef
);
2928 if ( bundleID
== nullptr )
2931 if ( dylibID
!= bundleID
) {
2932 diags
.error("kpi bundle ID doesn't match kext");
2936 CFArrayRef symbolsArrayRef
= (CFArrayRef
)CFDictionaryGetValue(symbolSetsDictRef
, CFSTR("Symbols"));
2937 if ( symbolsArrayRef
!= nullptr ) {
2938 if ( CFGetTypeID(symbolsArrayRef
) != CFArrayGetTypeID() ) {
2939 diags
.error("Symbols value should be an array");
2942 for (CFIndex symbolSetIndex
= 0; symbolSetIndex
!= CFArrayGetCount(symbolsArrayRef
); ++symbolSetIndex
) {
2943 CFStringRef symbolNameRef
= (CFStringRef
)CFArrayGetValueAtIndex(symbolsArrayRef
, symbolSetIndex
);
2944 if ( (symbolNameRef
== nullptr) || (CFGetTypeID(symbolNameRef
) != CFStringGetTypeID()) ) {
2945 diags
.error("Symbol name should be a string");
2949 const char* symbolName
= getString(diags
, symbolNameRef
);
2950 if ( symbolName
== nullptr )
2952 symbols
.insert(symbolName
);
2956 return std::make_unique
<std::unordered_set
<std::string
>>(std::move(symbols
));
2959 void AppCacheBuilder::processFixups()
2961 auto dylibsToSymbolsOwner
= std::make_unique
<std::map
<std::string
, DylibSymbols
>>();
2962 std::map
<std::string
, DylibSymbols
>& dylibsToSymbols
= *dylibsToSymbolsOwner
.get();
2964 auto vtablePatcherOwner
= std::make_unique
<VTablePatcher
>(numFixupLevels
);
2965 VTablePatcher
& vtablePatcher
= *vtablePatcherOwner
.get();
2967 const uint32_t kernelLevel
= 0;
2968 uint8_t currentLevel
= getCurrentFixupLevel();
2970 // Keep track of missing binds until later. They may be "resolved" by vtable patching
2971 std::map
<const uint8_t*, const VTableBindSymbol
> missingBindLocations
;
2973 __block
std::string kernelID
;
2974 __block
const dyld3::MachOAnalyzer
* kernelMA
= nullptr;
2975 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::kernel
) {
2976 kernelMA
= getKernelStaticExecutableFromCache();
2977 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
2978 DylibStripMode stripMode
, const std::vector
<std::string
> &dependencies
,
2979 Diagnostics
& dylibDiag
,
2981 if ( ma
== kernelMA
) {
2986 assert(!kernelID
.empty());
2988 assert(existingKernelCollection
!= nullptr);
2989 existingKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *name
, bool &stop
) {
2990 if ( ma
->isStaticExecutable() ) {
2995 if ( kernelMA
== nullptr ) {
2996 _diagnostics
.error("Could not find kernel in kernel collection");
3001 auto getGlobals
= [](Diagnostics
& diags
, const dyld3::MachOAnalyzer
*ma
) -> std::map
<std::string_view
, uint64_t> {
3002 // Note we don't put __block on the variable directly as then it gets copied in to the return value
3003 std::map
<std::string_view
, uint64_t> exports
;
3004 __block
std::map
<std::string_view
, uint64_t>& exportsRef
= exports
;
3005 ma
->forEachGlobalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
,
3006 uint8_t n_type
, uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
3007 exportsRef
[symbolName
] = n_value
;
3012 auto getLocals
= [](Diagnostics
& diags
, const dyld3::MachOAnalyzer
*ma
) -> std::map
<std::string_view
, uint64_t> {
3013 // Note we don't put __block on the variable directly as then it gets copied in to the return value
3014 std::map
<std::string_view
, uint64_t> exports
;
3015 __block
std::map
<std::string_view
, uint64_t>& exportsRef
= exports
;
3016 ma
->forEachLocalSymbol(diags
, ^(const char *symbolName
, uint64_t n_value
,
3017 uint8_t n_type
, uint8_t n_sect
, uint16_t n_desc
, bool &stop
) {
3018 exportsRef
[symbolName
] = n_value
;
3023 dylibsToSymbols
[kernelID
] = {
3024 getGlobals(_diagnostics
, kernelMA
),
3025 getLocals(_diagnostics
, kernelMA
),
3028 std::string(kernelID
)
3031 // Add all the codeless kext's as kext's can list them as dependencies
3032 // Note we add placeholders here which can be legitimately replaced by symbol sets
3033 for (const InputDylib
& dylib
: codelessKexts
) {
3034 dylibsToSymbols
[dylib
.dylibID
] = { };
3037 // Similarly, add placeholders for codeless kexts in the baseKC
3038 if ( existingKernelCollection
!= nullptr ) {
3039 existingKernelCollection
->forEachPrelinkInfoLibrary(_diagnostics
,
3040 ^(const char *bundleName
, const char* relativePath
,
3041 const std::vector
<const char *> &deps
) {
3042 dylibsToSymbols
[bundleName
] = { };
3046 // And placeholders for codeless kexts in the pageableKC
3047 if ( pageableKernelCollection
!= nullptr ) {
3048 pageableKernelCollection
->forEachPrelinkInfoLibrary(_diagnostics
,
3049 ^(const char *bundleName
, const char* relativePath
,
3050 const std::vector
<const char *> &deps
) {
3051 dylibsToSymbols
[bundleName
] = { };
3055 // Get the symbol sets
3056 AutoReleaseTypeRef dataRefReleaser
;
3057 AutoReleaseTypeRef plistRefReleaser
;
3059 __block
std::list
<std::string
> nonASCIIStrings
;
3060 auto getString
= ^(Diagnostics
& diags
, CFStringRef symbolNameRef
) {
3061 const char* symbolName
= CFStringGetCStringPtr(symbolNameRef
, kCFStringEncodingUTF8
);
3062 if ( symbolName
!= nullptr )
3065 CFIndex len
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef
), kCFStringEncodingUTF8
);
3066 char buffer
[len
+ 1];
3067 if ( !CFStringGetCString(symbolNameRef
, buffer
, len
, kCFStringEncodingUTF8
) ) {
3068 diags
.error("Could not convert string to ASCII");
3069 return (const char*)nullptr;
3072 nonASCIIStrings
.push_back(buffer
);
3073 return nonASCIIStrings
.back().c_str();
3076 uint64_t symbolSetsSize
= 0;
3077 const void* symbolSetsContent
= kernelMA
->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize
);
3078 if ( symbolSetsContent
!= nullptr ) {
3079 const DylibSymbols
& kernelSymbols
= dylibsToSymbols
[kernelID
];
3081 CFDataRef dataRef
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
, (const uint8_t*)symbolSetsContent
, symbolSetsSize
, kCFAllocatorNull
);
3082 if ( dataRef
== nullptr ) {
3083 _diagnostics
.error("Could not create data ref for symbol sets");
3086 dataRefReleaser
.setRef(dataRef
);
3088 CFErrorRef errorRef
= nullptr;
3089 CFPropertyListRef plistRef
= CFPropertyListCreateWithData(kCFAllocatorDefault
, dataRef
, kCFPropertyListImmutable
, nullptr, &errorRef
);
3090 if (errorRef
!= nullptr) {
3091 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
3092 _diagnostics
.error("Could not load plist because :%s",
3093 CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
3094 CFRelease(errorRef
);
3097 if ( plistRef
== nullptr ) {
3098 _diagnostics
.error("Could not create plist ref for symbol sets");
3101 plistRefReleaser
.setRef(plistRef
);
3103 if ( CFGetTypeID(plistRef
) != CFDictionaryGetTypeID() ) {
3104 _diagnostics
.error("Symbol set plist should be a dictionary");
3107 CFDictionaryRef symbolSetsDictRef
= (CFDictionaryRef
)plistRef
;
3108 CFArrayRef symbolSetArrayRef
= (CFArrayRef
)CFDictionaryGetValue(symbolSetsDictRef
, CFSTR("SymbolsSets"));
3109 if ( symbolSetArrayRef
!= nullptr ) {
3110 if ( CFGetTypeID(symbolSetArrayRef
) != CFArrayGetTypeID() ) {
3111 _diagnostics
.error("SymbolsSets value should be an array");
3114 for (CFIndex symbolSetIndex
= 0; symbolSetIndex
!= CFArrayGetCount(symbolSetArrayRef
); ++symbolSetIndex
) {
3115 CFDictionaryRef symbolSetDictRef
= (CFDictionaryRef
)CFArrayGetValueAtIndex(symbolSetArrayRef
, symbolSetIndex
);
3116 if ( CFGetTypeID(symbolSetDictRef
) != CFDictionaryGetTypeID() ) {
3117 _diagnostics
.error("Symbol set element should be a dictionary");
3121 // CFBundleIdentifier
3122 CFStringRef bundleIDRef
= (CFStringRef
)CFDictionaryGetValue(symbolSetDictRef
, CFSTR("CFBundleIdentifier"));
3123 if ( (bundleIDRef
== nullptr) || (CFGetTypeID(bundleIDRef
) != CFStringGetTypeID()) ) {
3124 _diagnostics
.error("Symbol set bundle ID should be a string");
3129 CFArrayRef symbolsArrayRef
= (CFArrayRef
)CFDictionaryGetValue(symbolSetDictRef
, CFSTR("Symbols"));
3130 if ( (symbolsArrayRef
== nullptr) || (CFGetTypeID(symbolsArrayRef
) != CFArrayGetTypeID()) ) {
3131 _diagnostics
.error("Symbol set symbols should be an array");
3135 std::map
<std::string_view
, uint64_t> symbolSetGlobals
;
3136 std::map
<std::string_view
, uint64_t> symbolSetLocals
;
3137 for (CFIndex symbolIndex
= 0; symbolIndex
!= CFArrayGetCount(symbolsArrayRef
); ++symbolIndex
) {
3138 CFDictionaryRef symbolDictRef
= (CFDictionaryRef
)CFArrayGetValueAtIndex(symbolsArrayRef
, symbolIndex
);
3139 if ( CFGetTypeID(symbolDictRef
) != CFDictionaryGetTypeID() ) {
3140 _diagnostics
.error("Symbols array element should be a dictionary");
3145 CFStringRef symbolPrefixRef
= (CFStringRef
)CFDictionaryGetValue(symbolDictRef
, CFSTR("SymbolPrefix"));
3146 if ( symbolPrefixRef
!= nullptr ) {
3147 if ( CFGetTypeID(symbolPrefixRef
) != CFStringGetTypeID() ) {
3148 _diagnostics
.error("Symbol prefix should be a string");
3152 const char* symbolPrefix
= getString(_diagnostics
, symbolPrefixRef
);
3153 if ( symbolPrefix
== nullptr )
3155 size_t symbolPrefixLen
= strlen(symbolPrefix
);
3157 // FIXME: Brute force might not be the best thing here
3158 for (std::pair
<std::string_view
, uint64_t> kernelGlobal
: kernelSymbols
.globals
) {
3159 if ( strncmp(kernelGlobal
.first
.data(), symbolPrefix
, symbolPrefixLen
) == 0 ) {
3160 symbolSetGlobals
[kernelGlobal
.first
] = kernelGlobal
.second
;
3163 for (std::pair
<std::string_view
, uint64_t> kernelLocal
: kernelSymbols
.locals
) {
3164 if ( strncmp(kernelLocal
.first
.data(), symbolPrefix
, symbolPrefixLen
) == 0 ) {
3165 symbolSetLocals
[kernelLocal
.first
] = kernelLocal
.second
;
3172 CFStringRef symbolNameRef
= (CFStringRef
)CFDictionaryGetValue(symbolDictRef
, CFSTR("SymbolName"));
3173 if ( (symbolNameRef
== nullptr) || (CFGetTypeID(symbolNameRef
) != CFStringGetTypeID()) ) {
3174 _diagnostics
.error("Symbol name should be a string");
3178 // AliasTarget [Optional]
3179 CFStringRef aliasTargetRef
= (CFStringRef
)CFDictionaryGetValue(symbolDictRef
, CFSTR("AliasTarget"));
3180 if ( aliasTargetRef
== nullptr ) {
3182 const char* symbolName
= getString(_diagnostics
, symbolNameRef
);
3183 if ( symbolName
== nullptr )
3186 // Find the symbol in xnu
3187 auto globalIt
= kernelSymbols
.globals
.find(symbolName
);
3188 if (globalIt
!= kernelSymbols
.globals
.end()) {
3189 symbolSetGlobals
[symbolName
] = globalIt
->second
;
3192 auto localIt
= kernelSymbols
.locals
.find(symbolName
);
3193 if (localIt
!= kernelSymbols
.locals
.end()) {
3194 symbolSetLocals
[symbolName
] = localIt
->second
;
3198 if ( CFGetTypeID(aliasTargetRef
) != CFStringGetTypeID() ) {
3199 _diagnostics
.error("Alias should be a string");
3203 const char* symbolName
= getString(_diagnostics
, symbolNameRef
);
3204 if ( symbolName
== nullptr )
3206 const char* aliasTargetName
= getString(_diagnostics
, aliasTargetRef
);
3207 if ( aliasTargetName
== nullptr )
3210 // Find the alias symbol in xnu
3211 auto globalIt
= kernelSymbols
.globals
.find(aliasTargetName
);
3212 if (globalIt
!= kernelSymbols
.globals
.end()) {
3213 symbolSetGlobals
[symbolName
] = globalIt
->second
;
3215 _diagnostics
.error("Alias '%s' not found in kernel", aliasTargetName
);
3219 auto localIt
= kernelSymbols
.locals
.find(aliasTargetName
);
3220 if (localIt
!= kernelSymbols
.locals
.end()) {
3221 symbolSetLocals
[symbolName
] = localIt
->second
;
3223 // This is not an error, as aliases from symbol sets from the kernel
3224 // are only for vtable patching, not general binding
3228 const char* dylibID
= getString(_diagnostics
, bundleIDRef
);
3229 if ( dylibID
== nullptr )
3232 // HACK: kxld aliases __ZN15OSMetaClassBase25_RESERVEDOSMetaClassBase3Ev to __ZN15OSMetaClassBase8DispatchE5IORPC
3233 auto metaclassHackIt
= symbolSetGlobals
.find("__ZN15OSMetaClassBase8DispatchE5IORPC");
3234 if ( metaclassHackIt
!= symbolSetGlobals
.end() )
3235 symbolSetGlobals
["__ZN15OSMetaClassBase25_RESERVEDOSMetaClassBase3Ev"] = metaclassHackIt
->second
;
3236 dylibsToSymbols
[dylibID
] = {
3237 std::move(symbolSetGlobals
),
3238 std::move(symbolSetLocals
),
3247 auto processBinary
= ^(Diagnostics
& dylibDiags
, const dyld3::MachOAnalyzer
*ma
,
3248 const std::string
& dylibID
, uint32_t dylibLevel
) {
3249 // We dont support export trie's for now
3250 uint32_t unusedExportTrieOffset
= 0;
3251 uint32_t unusedExportTrieSize
= 0;
3252 if (ma
->hasExportTrie(unusedExportTrieOffset
, unusedExportTrieSize
))
3255 // Already done the kernel before.
3256 if ( ma
== kernelMA
)
3260 dylibsToSymbols
[dylibID
] = {
3261 getGlobals(dylibDiags
, ma
),
3262 getLocals(dylibDiags
, ma
),
3263 getKPI(dylibDiags
, ma
, dylibID
),
3268 // Process binary symbols in parallel
3271 const dyld3::MachOAnalyzer
* ma
= nullptr;
3272 Diagnostics
& dylibDiag
;
3273 const std::string
& dylibID
;
3276 __block
std::vector
<DylibData
> dylibDatas
;
3277 dylibDatas
.reserve(sortedDylibs
.size());
3278 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
, DylibStripMode stripMode
,
3279 const std::vector
<std::string
> &dependencies
, Diagnostics
&dylibDiag
, bool &stop
) {
3280 // Already done the kernel before.
3281 if ( ma
== kernelMA
)
3284 // Make space for all the map entries so that we know they are there when we write their values later
3285 dylibsToSymbols
[dylibID
] = { };
3286 dylibDatas
.emplace_back((DylibData
){ ma
, dylibDiag
, dylibID
});
3289 dispatch_apply(dylibDatas
.size(), DISPATCH_APPLY_AUTO
, ^(size_t index
) {
3290 DylibData
& dylibData
= dylibDatas
[index
];
3291 processBinary(dylibData
.dylibDiag
, dylibData
.ma
, dylibData
.dylibID
, currentLevel
);
3295 // Add exports from the kernel collection if we have it
3296 if ( existingKernelCollection
!= nullptr ) {
3297 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::kernel
);
3298 existingKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *name
, bool &stop
) {
3299 processBinary(_diagnostics
, ma
, name
, fixupLevel
);
3303 // Add exports from the pageable collection if we have it
3304 if ( pageableKernelCollection
!= nullptr ) {
3305 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::pageableKC
);
3306 pageableKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *name
, bool &stop
) {
3307 processBinary(_diagnostics
, ma
, name
, fixupLevel
);
3311 // Map from an offset in to a KC to a synthesized stub which branches to that offset
3312 struct CacheOffsetHash
3314 size_t operator() (const CacheOffset
& cacheOffset
) const
3316 return std::hash
<uint32_t>{}(cacheOffset
.first
) ^ std::hash
<uint64_t>{}(cacheOffset
.second
);
3319 std::unordered_map
<CacheOffset
, uint64_t, CacheOffsetHash
> branchStubs
;
3321 // Clear the branch regions sizes so that we fill them up to their buffer sizes as we go
3322 branchStubsRegion
.sizeInUse
= 0;
3323 branchGOTsRegion
.sizeInUse
= 0;
3326 // Map from each symbol to the list of dylibs which export it
3327 auto symbolMapOwner
= std::make_unique
<std::unordered_map
<std::string_view
, std::vector
<DylibSymbolLocation
>>>();
3328 __block
auto& symbolMap
= *symbolMapOwner
.get();
3329 for (const auto& dylibNameAndSymbols
: dylibsToSymbols
) {
3330 const DylibSymbols
& dylibSymbols
= dylibNameAndSymbols
.second
;
3331 for (const auto& symbolNameAndAddress
: dylibSymbols
.globals
) {
3332 // By default, everything i KPI, ie, can be linked by third parties.
3333 // If a symbol is is provided, even an empty one, then it can override this
3335 if ( dylibSymbols
.dylibName
== "com.apple.kpi.private" ) {
3336 // com.apple.kpi.private is always hidden from third parties. They shouldn't even list it as a dependency
3338 } else if ( dylibSymbols
.kpiSymbols
) {
3339 const std::unordered_set
<std::string
>* kpiSymbols
= dylibSymbols
.kpiSymbols
.get();
3340 if ( kpiSymbols
->count(symbolNameAndAddress
.first
.data()) == 0 )
3343 symbolMap
[symbolNameAndAddress
.first
].push_back({ &dylibSymbols
, symbolNameAndAddress
.second
, isKPI
});
3347 auto dylibFixupsOwner
= std::make_unique
<std::vector
<DylibFixups
>>();
3348 __block
auto& dylibFixups
= *dylibFixupsOwner
.get();
3349 dylibFixups
.reserve(sortedDylibs
.size());
3350 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
, DylibStripMode stripMode
,
3351 const std::vector
<std::string
> &dependencies
, Diagnostics
&dylibDiag
, bool &stop
) {
3353 auto dylibSymbolsIt
= dylibsToSymbols
.find(dylibID
);
3354 assert(dylibSymbolsIt
!= dylibsToSymbols
.end());
3356 dylibFixups
.emplace_back((DylibFixups
){
3358 .dylibSymbols
= dylibSymbolsIt
->second
,
3359 .dylibDiag
= dylibDiag
,
3360 .dependencies
= dependencies
3364 dispatch_apply(dylibFixups
.size(), DISPATCH_APPLY_AUTO
, ^(size_t index
) {
3365 DylibFixups
& dylibFixup
= dylibFixups
[index
];
3366 dylibFixup
.processFixups(dylibsToSymbols
, symbolMap
, kernelID
, _aslrTracker
);
3369 // Merge all the dylib results in serial
3370 for (DylibFixups
& dylibFixup
: dylibFixups
) {
3372 if ( dylibFixup
.dylibDiag
.hasError() ) {
3373 if ( !_diagnostics
.hasError() ) {
3374 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
3379 if ( !dylibFixup
.missingBindLocations
.empty() ) {
3380 missingBindLocations
.insert(dylibFixup
.missingBindLocations
.begin(),
3381 dylibFixup
.missingBindLocations
.end());
3384 if ( !dylibFixup
.fixupLocs
.empty() ) {
3385 for (auto fixupLocAndLevel
: dylibFixup
.fixupLocs
) {
3386 _aslrTracker
.add(fixupLocAndLevel
.first
, fixupLocAndLevel
.second
);
3390 if ( !dylibFixup
.fixupHigh8s
.empty() ) {
3391 for (auto fixupLocAndHigh8
: dylibFixup
.fixupHigh8s
) {
3392 _aslrTracker
.setHigh8(fixupLocAndHigh8
.first
, fixupLocAndHigh8
.second
);
3396 if ( !dylibFixup
.fixupAuths
.empty() ) {
3397 for (auto fixupLocAndAuth
: dylibFixup
.fixupAuths
) {
3398 _aslrTracker
.setAuthData(fixupLocAndAuth
.first
, fixupLocAndAuth
.second
.diversity
,
3399 fixupLocAndAuth
.second
.addrDiv
, fixupLocAndAuth
.second
.key
);
3403 // Emit branch stubs
3404 const uint64_t loadAddress
= dylibFixup
.ma
->preferredLoadAddress();
3405 for (const DylibFixups::BranchStubData
& branchData
: dylibFixup
.branchStubs
) {
3406 // Branching from the auxKC to baseKC. ld64 doesn't emit a stub in x86_64 kexts
3407 // so we need to synthesize one now
3408 uint64_t targetAddress
= 0;
3409 const CacheOffset
& targetCacheOffset
= branchData
.targetCacheOffset
;
3410 auto itAndInserted
= branchStubs
.insert({ targetCacheOffset
, 0 });
3411 if ( itAndInserted
.second
) {
3412 // We inserted the branch location, so we need to create new stubs and GOTs
3413 if ( branchStubsRegion
.sizeInUse
== branchStubsRegion
.bufferSize
) {
3414 _diagnostics
.error("Overflow in branch stubs region");
3417 if ( branchGOTsRegion
.sizeInUse
== branchGOTsRegion
.bufferSize
) {
3418 _diagnostics
.error("Overflow in branch GOTs region");
3421 uint64_t stubAddress
= branchStubsRegion
.unslidLoadAddress
+ branchStubsRegion
.sizeInUse
;
3422 uint8_t* stubBuffer
= branchStubsRegion
.buffer
+ branchStubsRegion
.sizeInUse
;
3423 uint64_t gotAddress
= branchGOTsRegion
.unslidLoadAddress
+ branchGOTsRegion
.sizeInUse
;
3424 uint8_t* gotBuffer
= branchGOTsRegion
.buffer
+ branchGOTsRegion
.sizeInUse
;
3427 // ff 25 aa bb cc dd jmpq *0xddccbbaa(%rip)
3428 uint64_t diffValue
= gotAddress
- (stubAddress
+ 6);
3429 stubBuffer
[0] = 0xFF;
3430 stubBuffer
[1] = 0x25;
3431 memcpy(&stubBuffer
[2], &diffValue
, sizeof(uint32_t));
3433 // And write the GOT
3434 uint8_t symbolCacheLevel
= targetCacheOffset
.first
;
3435 uint64_t symbolVMAddr
= targetCacheOffset
.second
;
3437 *((uint64_t*)gotBuffer
) = symbolVMAddr
;
3439 *((uint32_t*)gotBuffer
) = (uint32_t)symbolVMAddr
;
3440 _aslrTracker
.add(gotBuffer
, symbolCacheLevel
);
3442 branchStubsRegion
.sizeInUse
+= 6;
3443 branchGOTsRegion
.sizeInUse
+= 8;
3444 targetAddress
= stubAddress
;
3445 itAndInserted
.first
->second
= targetAddress
;
3447 // The stub already existed, so use it
3448 targetAddress
= itAndInserted
.first
->second
;
3450 uint64_t diffValue
= targetAddress
- (loadAddress
+ branchData
.fixupVMOffset
+ 4);
3451 *((uint32_t*)branchData
.fixupLoc
) = (uint32_t)diffValue
;
3455 // FIXME: We could move symbolOwner and dylibFixupsOwner to a worker thread to be destroyed
3458 // Now that we've processes all rebases/binds, patch all the vtables
3460 // Add all the collections to the vtable patcher
3461 if ( existingKernelCollection
!= nullptr ) {
3462 // The baseKC for x86_64 has __HIB mapped first , so we need to get either the __DATA or __TEXT depending on what is earliest
3463 // The kernel base address is still __TEXT, even if __DATA or __HIB is mapped prior to that.
3464 // The loader may have loaded something before __TEXT, but the existingKernelCollection pointer still corresponds to __TEXT
3465 __block
uint64_t baseAddress
= ~0ULL;
3466 existingKernelCollection
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
& info
, bool& stop
) {
3467 baseAddress
= std::min(baseAddress
, info
.vmAddr
);
3470 // The existing collection is a pointer to the mach_header for the baseKC, but __HIB and other segments may be before that
3471 // Offset those here
3472 uint64_t basePointerOffset
= existingKernelCollection
->preferredLoadAddress() - baseAddress
;
3473 const uint8_t* basePointer
= (uint8_t*)existingKernelCollection
- basePointerOffset
;
3475 vtablePatcher
.addKernelCollection(existingKernelCollection
, Options::AppCacheKind::kernel
,
3476 basePointer
, baseAddress
);
3479 if ( pageableKernelCollection
!= nullptr ) {
3480 // The baseKC for x86_64 has __HIB mapped first , so we need to get either the __DATA or __TEXT depending on what is earliest
3481 // The kernel base address is still __TEXT, even if __DATA or __HIB is mapped prior to that.
3482 // The loader may have loaded something before __TEXT, but the existingKernelCollection pointer still corresponds to __TEXT
3483 __block
uint64_t baseAddress
= ~0ULL;
3484 pageableKernelCollection
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
& info
, bool& stop
) {
3485 baseAddress
= std::min(baseAddress
, info
.vmAddr
);
3488 // The existing collection is a pointer to the mach_header for the baseKC, but __HIB and other segments may be before that
3489 // Offset those here
3490 uint64_t basePointerOffset
= pageableKernelCollection
->preferredLoadAddress() - baseAddress
;
3491 const uint8_t* basePointer
= (uint8_t*)pageableKernelCollection
- basePointerOffset
;
3493 vtablePatcher
.addKernelCollection(pageableKernelCollection
, Options::AppCacheKind::pageableKC
,
3494 basePointer
, baseAddress
);
3498 vtablePatcher
.addKernelCollection((const dyld3::MachOAppCache
*)cacheHeader
.header
, appCacheOptions
.cacheKind
,
3499 (const uint8_t*)_fullAllocatedBuffer
, cacheBaseAddress
);
3501 // Add all the dylibs to the patcher
3503 if ( existingKernelCollection
!= nullptr ) {
3504 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::kernel
);
3506 __block
std::map
<std::string
, std::vector
<std::string
>> kextDependencies
;
3507 kextDependencies
[kernelID
] = {};
3508 existingKernelCollection
->forEachPrelinkInfoLibrary(_diagnostics
,
3509 ^(const char *bundleName
, const char* relativePath
,
3510 const std::vector
<const char *> &deps
) {
3511 std::vector
<std::string
>& dependencies
= kextDependencies
[bundleName
];
3512 dependencies
.insert(dependencies
.end(), deps
.begin(), deps
.end());
3515 existingKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *dylibID
, bool &stop
) {
3516 auto depsIt
= kextDependencies
.find(dylibID
);
3517 assert(depsIt
!= kextDependencies
.end());
3518 vtablePatcher
.addDylib(_diagnostics
, ma
, dylibID
, depsIt
->second
, fixupLevel
);
3522 if ( pageableKernelCollection
!= nullptr ) {
3523 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::pageableKC
);
3525 __block
std::map
<std::string
, std::vector
<std::string
>> kextDependencies
;
3526 pageableKernelCollection
->forEachPrelinkInfoLibrary(_diagnostics
,
3527 ^(const char *bundleName
, const char* relativePath
,
3528 const std::vector
<const char *> &deps
) {
3529 std::vector
<std::string
>& dependencies
= kextDependencies
[bundleName
];
3530 dependencies
.insert(dependencies
.end(), deps
.begin(), deps
.end());
3533 pageableKernelCollection
->forEachDylib(_diagnostics
, ^(const dyld3::MachOAnalyzer
*ma
, const char *dylibID
, bool &stop
) {
3534 auto depsIt
= kextDependencies
.find(dylibID
);
3535 assert(depsIt
!= kextDependencies
.end());
3536 vtablePatcher
.addDylib(_diagnostics
, ma
, dylibID
, depsIt
->second
, fixupLevel
);
3540 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
, DylibStripMode stripMode
,
3541 const std::vector
<std::string
> &dependencies
, Diagnostics
& dylibDiag
, bool &stop
) {
3542 vtablePatcher
.addDylib(dylibDiag
, ma
, dylibID
, dependencies
, currentLevel
);
3546 vtablePatcher
.findMetaclassDefinitions(dylibsToSymbols
, kernelID
, kernelMA
, appCacheOptions
.cacheKind
);
3547 vtablePatcher
.findExistingFixups(_diagnostics
, existingKernelCollection
, pageableKernelCollection
);
3548 if ( _diagnostics
.hasError() )
3551 // Add vtables from the base KC if we have one
3552 if ( existingKernelCollection
!= nullptr ) {
3553 vtablePatcher
.findBaseKernelVTables(_diagnostics
, existingKernelCollection
, dylibsToSymbols
);
3554 if ( _diagnostics
.hasError() )
3558 // Add vtables from the pageable KC if we have one
3559 if ( pageableKernelCollection
!= nullptr ) {
3560 vtablePatcher
.findPageableKernelVTables(_diagnostics
, pageableKernelCollection
, dylibsToSymbols
);
3561 if ( _diagnostics
.hasError() )
3565 // Add vables from our level
3566 vtablePatcher
.findVTables(currentLevel
, kernelMA
, dylibsToSymbols
, _aslrTracker
, missingBindLocations
);
3568 // Don't run the patcher if we have a failure finding the vtables
3569 if ( vtablePatcher
.hasError() ) {
3570 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
3574 // Now patch all of the vtables.
3575 vtablePatcher
.patchVTables(_diagnostics
, missingBindLocations
, _aslrTracker
, currentLevel
);
3576 if ( _diagnostics
.hasError() )
3579 if ( vtablePatcher
.hasError() ) {
3580 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
3584 // FIXME: We could move vtablePatcherOwner to a worker thread to be destroyed
3585 vtablePatcherOwner
.reset();
3587 // Also error out if we have an error on any of the dylib diagnostic objects
3589 // Log any binds which are still missing
3590 for (const auto& missingLocationAndBind
: missingBindLocations
) {
3591 const uint8_t* missingBindLoc
= missingLocationAndBind
.first
;
3592 const VTableBindSymbol
& missingBind
= missingLocationAndBind
.second
;
3594 // Work out which segment and section this missing bind was in
3595 __block
bool reportedError
= false;
3596 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
, DylibStripMode stripMode
,
3597 const std::vector
<std::string
> &dependencies
, Diagnostics
& dylibDiag
, bool &stopDylib
) {
3598 intptr_t slide
= ma
->getSlide();
3599 ma
->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo
§Info
,
3600 bool malformedSectionRange
, bool &stopSection
) {
3601 const uint8_t* content
= (uint8_t*)(sectInfo
.sectAddr
+ slide
);
3602 const uint8_t* start
= (uint8_t*)content
;
3603 const uint8_t* end
= start
+ sectInfo
.sectSize
;
3604 if ( (missingBindLoc
>= start
) && (missingBindLoc
< end
) ) {
3605 std::string segmentName
= sectInfo
.segInfo
.segName
;
3606 std::string sectionName
= sectInfo
.sectName
;
3607 uint64_t sectionOffset
= (missingBindLoc
- start
);
3609 dylibDiag
.error("Failed to bind '%s' in '%s' (at offset 0x%llx in %s, %s) as "
3610 "could not find a kext which exports this symbol",
3611 missingBind
.symbolName
.c_str(), missingBind
.binaryID
.data(),
3612 sectionOffset
, segmentName
.c_str(), sectionName
.c_str());
3614 reportedError
= true;
3621 if ( !reportedError
) {
3622 _diagnostics
.error("Failed to bind '%s' in '%s' as could not find a kext which exports this symbol",
3623 missingBind
.symbolName
.c_str(), missingBind
.binaryID
.data());
3627 // If we had missing binds and reported no other errors, then generate an error to give the diagnostics something to track
3628 if ( !missingBindLocations
.empty() && _diagnostics
.noError() ) {
3629 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
3632 // FIXME: We could move dylibsToSymbolsOwner to a worker thread to be destroyed
3639 ByteBuffer(uint8_t* storage
, uintptr_t allocCount
) {
3640 buffer
.setInitialStorage(storage
, allocCount
);
3643 uint8_t* makeSpace(size_t bytesNeeded
) {
3644 // Make space in the buffer
3645 for (size_t i
= 0; i
!= bytesNeeded
; ++i
)
3646 buffer
.default_constuct_back();
3648 // Grab a pointer to our position in the buffer
3649 uint8_t* data
= buffer
.begin();
3651 // Move the buffer to start after our data
3652 dyld3::Array
<uint8_t> newBuffer(buffer
.end(), buffer
.freeCount(), 0);
3658 const uint8_t* begin() const {
3659 return buffer
.begin();
3662 const uint8_t* end() const {
3663 return buffer
.end();
3667 dyld3::Array
<uint8_t> buffer
;
3672 void AppCacheBuilder::writeFixups()
3674 if ( fixupsSubRegion
.sizeInUse
== 0 )
3677 __block ByteBuffer
byteBuffer(fixupsSubRegion
.buffer
, fixupsSubRegion
.bufferSize
);
3679 // Keep track of where we put the fixups
3680 const uint8_t* classicRelocsBufferStart
= nullptr;
3681 const uint8_t* classicRelocsBufferEnd
= nullptr;
3683 // If the kernel needs classic relocs, emit those first
3684 CacheHeader64
& header
= cacheHeader
;
3685 if ( header
.dynSymbolTable
!= nullptr ) {
3686 classicRelocsBufferStart
= byteBuffer
.begin();
3688 dyld3::MachOAnalyzer
* cacheMA
= (dyld3::MachOAnalyzer
*)header
.header
;
3689 __block
uint64_t localRelocBaseAddress
= 0;
3690 cacheMA
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
3691 if ( info
.protections
& VM_PROT_WRITE
) {
3692 localRelocBaseAddress
= info
.vmAddr
;
3697 const std::vector
<void*> allRebaseTargets
= _aslrTracker
.getRebaseTargets();
3699 const dyld3::MachOAnalyzer
* kernelMA
= getKernelStaticExecutableFromCache();
3700 kernelMA
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
3701 std::vector
<void*> segmentRebaseTargets
;
3702 uint64_t segmentVMOffset
= info
.vmAddr
- cacheBaseAddress
;
3703 const uint8_t* segmentStartAddr
= (const uint8_t*)(_fullAllocatedBuffer
+ segmentVMOffset
);
3704 const uint8_t* segmentEndAddr
= (const uint8_t*)(segmentStartAddr
+ info
.vmSize
);
3705 for (void* target
: allRebaseTargets
) {
3706 if ( (target
>= segmentStartAddr
) && (target
< segmentEndAddr
) ) {
3707 segmentRebaseTargets
.push_back(target
);
3710 std::sort(segmentRebaseTargets
.begin(), segmentRebaseTargets
.end());
3712 for (void* target
: segmentRebaseTargets
) {
3713 uint64_t targetSegmentOffset
= (uint64_t)target
- (uint64_t)segmentStartAddr
;
3714 //printf("Target: %s + 0x%llx: %p\n", info.segName, targetSegmentOffset, target);
3716 uint64_t offsetFromBaseAddress
= (info
.vmAddr
+ targetSegmentOffset
) - localRelocBaseAddress
;
3717 relocation_info
* reloc
= (relocation_info
*)byteBuffer
.makeSpace(sizeof(relocation_info
));
3718 reloc
->r_address
= (uint32_t)offsetFromBaseAddress
;
3719 reloc
->r_symbolnum
= 0;
3720 reloc
->r_pcrel
= false;
3721 reloc
->r_length
= 0;
3722 reloc
->r_extern
= 0;
3725 uint32_t vmAddr32
= 0;
3726 uint64_t vmAddr64
= 0;
3727 if ( _aslrTracker
.hasRebaseTarget32(target
, &vmAddr32
) ) {
3728 reloc
->r_length
= 2;
3729 *(uint32_t*)target
= vmAddr32
;
3730 } else if ( _aslrTracker
.hasRebaseTarget64(target
, &vmAddr64
) ) {
3731 reloc
->r_length
= 3;
3732 *(uint64_t*)target
= vmAddr64
;
3736 // Remove these fixups so that we don't also emit chained fixups for them
3737 for (void* target
: segmentRebaseTargets
)
3738 _aslrTracker
.remove(target
);
3741 classicRelocsBufferEnd
= byteBuffer
.begin();
3744 // TODO: 32-bit pointer format
3746 const uint8_t currentLevel
= getCurrentFixupLevel();
3748 // We can have up to 4 levels in the fixup format. These are the base addresses from
3749 // which each level starts
3750 BLOCK_ACCCESSIBLE_ARRAY(uint64_t, levelBaseAddresses
, 4);
3751 for (unsigned i
= 0; i
!= numFixupLevels
; ++i
)
3752 levelBaseAddresses
[i
] = 0;
3754 levelBaseAddresses
[currentLevel
] = cacheBaseAddress
;
3755 if ( appCacheOptions
.cacheKind
!= Options::AppCacheKind::kernel
) {
3756 assert(existingKernelCollection
!= nullptr);
3757 // The auxKC is mapped with __DATA first, so we need to get either the __DATA or __TEXT depending on what is earliest
3758 __block
uint64_t baseAddress
= ~0ULL;
3759 existingKernelCollection
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
& info
, bool& stop
) {
3760 baseAddress
= std::min(baseAddress
, info
.vmAddr
);
3762 levelBaseAddresses
[0] = baseAddress
;
3765 if ( pageableKernelCollection
!= nullptr ) {
3766 // We may have __DATA first, so we need to get either the __DATA or __TEXT depending on what is earliest
3767 __block
uint64_t baseAddress
= ~0ULL;
3768 pageableKernelCollection
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
& info
, bool& stop
) {
3769 baseAddress
= std::min(baseAddress
, info
.vmAddr
);
3771 uint8_t fixupLevel
= getFixupLevel(Options::AppCacheKind::pageableKC
);
3772 levelBaseAddresses
[fixupLevel
] = baseAddress
;
3775 // We have a dyld_chained_starts_in_segment plus an offset for each page
3776 struct SegmentFixups
{
3777 //const Region* region = nullptr;
3778 uint8_t* segmentBuffer
= nullptr;
3779 uint64_t segmentIndex
= 0;
3780 uint64_t unslidLoadAddress
= 0;
3781 uint64_t sizeInUse
= 0;
3782 dyld_chained_starts_in_segment
* starts
= nullptr;
3783 uint64_t startsByteSize
= 0;
3784 uint64_t numPagesToFixup
= 0;
3787 auto buildChainedFixups
= ^(uint64_t baseAddress
, uint64_t segmentCount
, std::vector
<SegmentFixups
>& startsInSegments
) {
3789 const uint8_t* chainedFixupsBufferStart
= nullptr;
3790 const uint8_t* chainedFixupsBufferEnd
= nullptr;
3792 chainedFixupsBufferStart
= byteBuffer
.begin();
3794 // Start with dyld_chained_fixups_header which is fixed size
3795 dyld_chained_fixups_header
* fixupsHeader
= (dyld_chained_fixups_header
*)byteBuffer
.makeSpace(sizeof(dyld_chained_fixups_header
));
3797 // We have a dyld_chained_starts_in_image plus an offset for each segment
3798 dyld_chained_starts_in_image
* startsInImage
= (dyld_chained_starts_in_image
*)byteBuffer
.makeSpace(sizeof(dyld_chained_starts_in_image
) + (segmentCount
* sizeof(uint32_t)));
3800 const uint8_t* endOfStarts
= nullptr;
3801 for (SegmentFixups
& segmentFixups
: startsInSegments
) {
3802 uint64_t startsInSegmentByteSize
= sizeof(dyld_chained_starts_in_segment
) + (segmentFixups
.numPagesToFixup
* sizeof(uint16_t));
3803 dyld_chained_starts_in_segment
* startsInSegment
= (dyld_chained_starts_in_segment
*)byteBuffer
.makeSpace(startsInSegmentByteSize
);
3804 endOfStarts
= (const uint8_t*)startsInSegment
+ startsInSegmentByteSize
;
3806 segmentFixups
.starts
= startsInSegment
;
3807 segmentFixups
.startsByteSize
= startsInSegmentByteSize
;
3811 startsInImage
->seg_count
= (uint32_t)segmentCount
;
3812 for (uint32_t segmentIndex
= 0; segmentIndex
!= segmentCount
; ++segmentIndex
) {
3813 startsInImage
->seg_info_offset
[segmentIndex
] = 0;
3815 for (const SegmentFixups
& segmentFixups
: startsInSegments
) {
3816 dyld_chained_starts_in_segment
* startsInSegment
= segmentFixups
.starts
;
3817 uint64_t segmentIndex
= segmentFixups
.segmentIndex
;
3818 assert(segmentIndex
< segmentCount
);
3819 assert(startsInImage
->seg_info_offset
[segmentIndex
] == 0);
3820 startsInImage
->seg_info_offset
[segmentIndex
] = (uint32_t)((uint8_t*)startsInSegment
- (uint8_t*)startsInImage
);
3823 const unsigned chainedPointerStride
= dyld3::MachOAnalyzer::ChainedFixupPointerOnDisk::strideSize(chainedPointerFormat
);
3825 // Starts in segment
3826 for (const SegmentFixups
& segmentFixups
: startsInSegments
) {
3827 dyld_chained_starts_in_segment
* startsInSegment
= segmentFixups
.starts
;
3828 startsInSegment
->size
= (uint32_t)segmentFixups
.startsByteSize
;
3829 startsInSegment
->page_size
= fixupsPageSize();
3830 startsInSegment
->pointer_format
= chainedPointerFormat
;
3831 startsInSegment
->segment_offset
= segmentFixups
.unslidLoadAddress
- baseAddress
;
3832 startsInSegment
->max_valid_pointer
= 0; // FIXME: Needed in 32-bit only
3833 startsInSegment
->page_count
= (segmentFixups
.sizeInUse
+ startsInSegment
->page_size
- 1) / startsInSegment
->page_size
;
3834 for (uint64_t pageIndex
= 0; pageIndex
!= startsInSegment
->page_count
; ++pageIndex
) {
3835 startsInSegment
->page_start
[pageIndex
] = DYLD_CHAINED_PTR_START_NONE
;
3836 uint8_t* lastLoc
= nullptr;
3837 // Note we always walk in 1-byte at a time as x86_64 has unaligned fixups
3838 for (uint64_t pageOffset
= 0; pageOffset
!= startsInSegment
->page_size
; pageOffset
+= 1) {
3839 uint8_t* fixupLoc
= segmentFixups
.segmentBuffer
+ (pageIndex
* startsInSegment
->page_size
) + pageOffset
;
3840 uint8_t fixupLevel
= currentLevel
;
3841 if ( !_aslrTracker
.has(fixupLoc
, &fixupLevel
) )
3843 assert((pageOffset
% chainedPointerStride
) == 0);
3845 // Patch last loc to point here
3847 dyld_chained_ptr_64_kernel_cache_rebase
* lastLocBits
= (dyld_chained_ptr_64_kernel_cache_rebase
*)lastLoc
;
3848 assert(lastLocBits
->next
== 0);
3849 uint64_t next
= (fixupLoc
- lastLoc
) / chainedPointerStride
;
3850 lastLocBits
->next
= next
;
3851 assert(lastLocBits
->next
== next
&& "next location truncated");
3853 // First fixup on this page
3854 startsInSegment
->page_start
[pageIndex
] = pageOffset
;
3858 uint64_t targetVMAddr
= *(uint64_t*)fixupLoc
;
3860 uint8_t highByte
= 0;
3861 if ( _aslrTracker
.hasHigh8(fixupLoc
, &highByte
) ) {
3862 uint64_t tbi
= (uint64_t)highByte
<< 56;
3863 targetVMAddr
|= tbi
;
3866 assert(fixupLevel
< numFixupLevels
);
3867 uint64_t targetVMOffset
= targetVMAddr
- levelBaseAddresses
[fixupLevel
];
3869 // Pack the vmAddr on this location in to the fixup format
3870 dyld_chained_ptr_64_kernel_cache_rebase
* locBits
= (dyld_chained_ptr_64_kernel_cache_rebase
*)fixupLoc
;
3875 if ( _aslrTracker
.hasAuthData(fixupLoc
, &diversity
, &hasAddrDiv
, &key
) ) {
3876 locBits
->target
= targetVMOffset
;
3877 locBits
->cacheLevel
= fixupLevel
;
3878 locBits
->diversity
= diversity
;
3879 locBits
->addrDiv
= hasAddrDiv
;
3882 locBits
->isAuth
= 1;
3883 assert(locBits
->target
== targetVMOffset
&& "target truncated");
3886 locBits
->target
= targetVMOffset
;
3887 locBits
->cacheLevel
= fixupLevel
;
3888 locBits
->diversity
= 0;
3889 locBits
->addrDiv
= 0;
3892 locBits
->isAuth
= 0;
3893 assert(locBits
->target
== targetVMOffset
&& "target truncated");
3899 chainedFixupsBufferEnd
= byteBuffer
.begin();
3902 fixupsHeader
->fixups_version
= 0;
3903 fixupsHeader
->starts_offset
= (uint32_t)((uint8_t*)startsInImage
- (uint8_t*)fixupsHeader
);
3904 fixupsHeader
->imports_offset
= (uint32_t)((uint8_t*)chainedFixupsBufferEnd
- (uint8_t*)fixupsHeader
);
3905 fixupsHeader
->symbols_offset
= fixupsHeader
->imports_offset
;
3906 fixupsHeader
->imports_count
= 0;
3907 fixupsHeader
->imports_format
= DYLD_CHAINED_IMPORT
; // The validate code wants a value here
3908 fixupsHeader
->symbols_format
= 0;
3910 return std::make_pair(chainedFixupsBufferStart
, chainedFixupsBufferEnd
);
3913 if ( fixupsArePerKext() ) {
3914 // The pageableKC (and sometimes auxKC) has one LC_DYLD_CHAINED_FIXUPS per kext, not 1 total
3915 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
3916 DylibStripMode stripMode
, const std::vector
<std::string
> &dependencies
,
3917 Diagnostics
& dylibDiag
, bool &stop
) {
3918 uint64_t loadAddress
= ma
->preferredLoadAddress();
3920 __block
uint64_t numSegments
= 0;
3921 __block
std::vector
<SegmentFixups
> segmentFixups
;
3922 ma
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
3923 // Third party kexts have writable __TEXT, so we need to add starts for all segments
3924 // other than LINKEDIT
3925 bool segmentCanHaveFixups
= false;
3926 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::pageableKC
) {
3927 segmentCanHaveFixups
= (info
.protections
& VM_PROT_WRITE
) != 0;
3930 segmentCanHaveFixups
= (strcmp(info
.segName
, "__LINKEDIT") != 0);
3933 if ( segmentCanHaveFixups
) {
3934 SegmentFixups segmentToFixup
;
3935 segmentToFixup
.segmentBuffer
= (uint8_t*)ma
+ (info
.vmAddr
- loadAddress
);
3936 segmentToFixup
.segmentIndex
= info
.segIndex
;
3937 segmentToFixup
.unslidLoadAddress
= info
.vmAddr
;
3938 segmentToFixup
.sizeInUse
= info
.vmSize
;
3939 segmentToFixup
.starts
= nullptr;
3940 segmentToFixup
.startsByteSize
= 0;
3941 segmentToFixup
.numPagesToFixup
= numWritablePagesToFixup(info
.vmSize
);
3942 segmentFixups
.push_back(segmentToFixup
);
3949 std::pair
<const uint8_t*, const uint8_t*> chainedFixupsRange
= buildChainedFixups(loadAddress
,
3950 numSegments
, segmentFixups
);
3951 const uint8_t* chainedFixupsBufferStart
= chainedFixupsRange
.first
;
3952 const uint8_t* chainedFixupsBufferEnd
= chainedFixupsRange
.second
;
3954 if ( chainedFixupsBufferStart
!= chainedFixupsBufferEnd
) {
3955 // Add the load command to our file
3957 uint64_t fixupsOffset
= (uint64_t)chainedFixupsBufferStart
- (uint64_t)fixupsSubRegion
.buffer
;
3958 uint64_t fixupsSize
= (uint64_t)chainedFixupsBufferEnd
- (uint64_t)chainedFixupsBufferStart
;
3962 typedef Pointer64
<LittleEndian
> P
;
3964 uint32_t freeSpace
= ma
->loadCommandsFreeSpace();
3965 assert(freeSpace
>= sizeof(macho_linkedit_data_command
<P
>));
3966 uint8_t* endOfLoadCommands
= (uint8_t*)ma
+ sizeof(macho_header
<P
>) + ma
->sizeofcmds
;
3968 // update mach_header to account for new load commands
3969 macho_header
<P
>* mh
= (macho_header
<P
>*)ma
;
3970 mh
->set_sizeofcmds(mh
->sizeofcmds() + sizeof(macho_linkedit_data_command
<P
>));
3971 mh
->set_ncmds(mh
->ncmds() + 1);
3973 // Add the new load command
3974 macho_linkedit_data_command
<P
>* cmd
= (macho_linkedit_data_command
<P
>*)endOfLoadCommands
;
3975 cmd
->set_cmd(LC_DYLD_CHAINED_FIXUPS
);
3976 cmd
->set_cmdsize(sizeof(linkedit_data_command
));
3977 cmd
->set_dataoff((uint32_t)(_readOnlyRegion
.cacheFileOffset
+ _readOnlyRegion
.sizeInUse
+ fixupsOffset
));
3978 cmd
->set_datasize((uint32_t)fixupsSize
);
3982 // Also build chained fixups on the top level for the branch stub GOTs
3983 // FIXME: We don't need numRegions() here, but instead just up to an including the RW region
3984 uint64_t segmentCount
= numRegions();
3985 __block
std::vector
<SegmentFixups
> segmentFixups
;
3987 if ( branchGOTsRegion
.sizeInUse
!= 0 ) {
3988 SegmentFixups segmentToFixup
;
3989 segmentToFixup
.segmentBuffer
= branchGOTsRegion
.buffer
;
3990 segmentToFixup
.segmentIndex
= branchGOTsRegion
.index
;
3991 segmentToFixup
.unslidLoadAddress
= branchGOTsRegion
.unslidLoadAddress
;
3992 segmentToFixup
.sizeInUse
= branchGOTsRegion
.sizeInUse
;
3993 segmentToFixup
.starts
= nullptr;
3994 segmentToFixup
.startsByteSize
= 0;
3995 segmentToFixup
.numPagesToFixup
= numWritablePagesToFixup(branchGOTsRegion
.bufferSize
);
3996 segmentFixups
.push_back(segmentToFixup
);
3999 std::pair
<const uint8_t*, const uint8_t*> chainedFixupsRange
= buildChainedFixups(cacheHeaderRegion
.unslidLoadAddress
,
4000 segmentCount
, segmentFixups
);
4001 const uint8_t* chainedFixupsBufferStart
= chainedFixupsRange
.first
;
4002 const uint8_t* chainedFixupsBufferEnd
= chainedFixupsRange
.second
;
4004 if ( chainedFixupsBufferStart
!= chainedFixupsBufferEnd
) {
4005 uint64_t fixupsOffset
= (uint64_t)chainedFixupsBufferStart
- (uint64_t)fixupsSubRegion
.buffer
;
4006 uint64_t fixupsSize
= (uint64_t)chainedFixupsBufferEnd
- (uint64_t)chainedFixupsBufferStart
;
4007 header
.chainedFixups
->dataoff
= (uint32_t)_readOnlyRegion
.cacheFileOffset
+ (uint32_t)_readOnlyRegion
.sizeInUse
+ (uint32_t)fixupsOffset
;;
4008 header
.chainedFixups
->datasize
= (uint32_t)fixupsSize
;
4011 // Build the chained fixups for just the kernel collection itself
4012 // FIXME: We don't need numRegions() here, but instead just up to an including the RW region
4013 uint64_t segmentCount
= numRegions();
4014 __block
std::vector
<SegmentFixups
> segmentFixups
;
4016 auto addSegmentStarts
= ^(const Region
& region
) {
4017 SegmentFixups segmentToFixup
;
4018 segmentToFixup
.segmentBuffer
= region
.buffer
;
4019 segmentToFixup
.segmentIndex
= region
.index
;
4020 segmentToFixup
.unslidLoadAddress
= region
.unslidLoadAddress
;
4021 segmentToFixup
.sizeInUse
= region
.sizeInUse
;
4022 segmentToFixup
.starts
= nullptr;
4023 segmentToFixup
.startsByteSize
= 0;
4024 segmentToFixup
.numPagesToFixup
= numWritablePagesToFixup(region
.bufferSize
);
4025 segmentFixups
.push_back(segmentToFixup
);
4028 if ( dataConstRegion
.sizeInUse
!= 0 )
4029 addSegmentStarts(dataConstRegion
);
4030 if ( branchGOTsRegion
.sizeInUse
!= 0 )
4031 addSegmentStarts(branchGOTsRegion
);
4032 if ( readWriteRegion
.sizeInUse
!= 0 )
4033 addSegmentStarts(readWriteRegion
);
4034 if ( hibernateRegion
.sizeInUse
!= 0 )
4035 addSegmentStarts(hibernateRegion
);
4036 for (const Region
& region
: nonSplitSegRegions
) {
4037 // Assume writable regions have fixups to emit
4038 // Note, third party kext's have __TEXT fixups, so assume all of these have fixups
4039 // LINKEDIT is already elsewhere
4040 addSegmentStarts(region
);
4043 std::pair
<const uint8_t*, const uint8_t*> chainedFixupsRange
= buildChainedFixups(cacheHeaderRegion
.unslidLoadAddress
,
4044 segmentCount
, segmentFixups
);
4045 const uint8_t* chainedFixupsBufferStart
= chainedFixupsRange
.first
;
4046 const uint8_t* chainedFixupsBufferEnd
= chainedFixupsRange
.second
;
4048 if ( chainedFixupsBufferStart
!= chainedFixupsBufferEnd
) {
4049 uint64_t fixupsOffset
= (uint64_t)chainedFixupsBufferStart
- (uint64_t)fixupsSubRegion
.buffer
;
4050 uint64_t fixupsSize
= (uint64_t)chainedFixupsBufferEnd
- (uint64_t)chainedFixupsBufferStart
;
4051 header
.chainedFixups
->dataoff
= (uint32_t)_readOnlyRegion
.cacheFileOffset
+ (uint32_t)_readOnlyRegion
.sizeInUse
+ (uint32_t)fixupsOffset
;;
4052 header
.chainedFixups
->datasize
= (uint32_t)fixupsSize
;
4056 // Move the fixups to the end of __LINKEDIT
4057 if ( classicRelocsBufferStart
!= classicRelocsBufferEnd
) {
4058 uint64_t fixupsOffset
= (uint64_t)classicRelocsBufferStart
- (uint64_t)fixupsSubRegion
.buffer
;
4059 uint64_t fixupsSize
= (uint64_t)classicRelocsBufferEnd
- (uint64_t)classicRelocsBufferStart
;
4060 header
.dynSymbolTable
->locreloff
= (uint32_t)_readOnlyRegion
.cacheFileOffset
+ (uint32_t)_readOnlyRegion
.sizeInUse
+ (uint32_t)fixupsOffset
;
4061 header
.dynSymbolTable
->nlocrel
= (uint32_t)fixupsSize
/ sizeof(fixupsSize
);
4064 uint64_t fixupsSpace
= (uint64_t)byteBuffer
.end() - (uint64_t)fixupsSubRegion
.buffer
;
4066 uint8_t* linkeditEnd
= _readOnlyRegion
.buffer
+ _readOnlyRegion
.sizeInUse
;
4067 memcpy(linkeditEnd
, fixupsSubRegion
.buffer
, fixupsSpace
);
4068 uint8_t* fixupsEnd
= linkeditEnd
+ fixupsSpace
;
4070 _readOnlyRegion
.sizeInUse
+= align(fixupsSpace
, _is64
? 3 : 2);
4071 _readOnlyRegion
.sizeInUse
= align(_readOnlyRegion
.sizeInUse
, 14);
4072 _readOnlyRegion
.bufferSize
= _readOnlyRegion
.sizeInUse
;
4074 // Zero the alignment gap, just in case there's any unoptimized LINKEDIT in there
4075 uint8_t* alignedBufferEnd
= _readOnlyRegion
.buffer
+ _readOnlyRegion
.sizeInUse
;
4076 if ( fixupsEnd
!= alignedBufferEnd
){
4077 memset(fixupsEnd
, 0, alignedBufferEnd
- fixupsEnd
);
4081 dyld3::MachOAnalyzer
* cacheMA
= (dyld3::MachOAnalyzer
*)header
.header
;
4082 uint64_t cachePreferredLoadAddress
= cacheMA
->preferredLoadAddress();
4083 cacheMA
->forEachRebase(_diagnostics
, false, ^(uint64_t runtimeOffset
, bool &stop
) {
4084 printf("Rebase: 0x%llx = 0x%llx\n", runtimeOffset
, runtimeOffset
+ cachePreferredLoadAddress
);
4089 void AppCacheBuilder::allocateBuffer()
4091 // Whether to order the regions __TEXT, __DATA, __LINKEDIT or __DATA, __TEXT, __LINKEDIT in VM address order
4092 bool dataRegionFirstInVMOrder
= false;
4093 bool hibernateRegionFirstInVMOrder
= false;
4094 switch (appCacheOptions
.cacheKind
) {
4095 case Options::AppCacheKind::none
:
4096 assert(0 && "Cache kind should have been set");
4098 case Options::AppCacheKind::kernel
:
4099 if ( hibernateAddress
!= 0 )
4100 hibernateRegionFirstInVMOrder
= true;
4102 case Options::AppCacheKind::pageableKC
:
4103 // There's no interesting ordering for the pageableKC
4105 case Options::AppCacheKind::kernelCollectionLevel2
:
4106 assert(0 && "Unimplemented");
4108 case Options::AppCacheKind::auxKC
:
4109 dataRegionFirstInVMOrder
= true;
4113 // Count how many bytes we need from all our regions
4114 __block
uint64_t numRegionFileBytes
= 0;
4115 __block
uint64_t numRegionVMBytes
= 0;
4117 std::vector
<std::pair
<Region
*, uint64_t>> regions
;
4118 std::vector
<std::pair
<Region
*, uint64_t>> regionsVMOrder
;
4119 std::map
<const Region
*, uint32_t> sectionsToAddToRegions
;
4121 if ( hibernateRegionFirstInVMOrder
) {
4122 regionsVMOrder
.push_back({ &hibernateRegion
, numRegionVMBytes
});
4123 // Pad out the VM offset so that the cache header starts where the base address
4125 uint64_t paddedSize
= cacheBaseAddress
- hibernateAddress
;
4126 if ( hibernateRegion
.bufferSize
> paddedSize
) {
4127 _diagnostics
.error("Could not lay out __HIB segment");
4130 numRegionVMBytes
= paddedSize
;
4131 // Set the base address to the hibernate address so that we actually put the
4132 // hibernate segment there
4133 cacheBaseAddress
= hibernateAddress
;
4135 // Add a section too
4136 sectionsToAddToRegions
[&hibernateRegion
] = 1;
4137 } else if ( dataRegionFirstInVMOrder
) {
4138 if ( prelinkInfoDict
!= nullptr ) {
4139 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4140 regionsVMOrder
.push_back({ &prelinkInfoRegion
, numRegionVMBytes
});
4141 numRegionVMBytes
+= prelinkInfoRegion
.bufferSize
;
4143 if ( readWriteRegion
.sizeInUse
!= 0 ) {
4144 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4145 regionsVMOrder
.push_back({ &readWriteRegion
, numRegionVMBytes
});
4146 numRegionVMBytes
+= readWriteRegion
.bufferSize
;
4151 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4152 regions
.push_back({ &cacheHeaderRegion
, 0 });
4153 regionsVMOrder
.push_back({ &cacheHeaderRegion
, numRegionVMBytes
});
4158 readOnlyTextRegion
.cacheFileOffset
= numRegionFileBytes
;
4159 numRegionFileBytes
+= readOnlyTextRegion
.bufferSize
;
4160 regions
.push_back({ &readOnlyTextRegion
, 0 });
4162 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4163 regionsVMOrder
.push_back({ &readOnlyTextRegion
, numRegionVMBytes
});
4164 numRegionVMBytes
+= readOnlyTextRegion
.bufferSize
;
4166 // Add a section too
4167 sectionsToAddToRegions
[&readOnlyTextRegion
] = 1;
4170 // Split seg __TEXT_EXEC
4171 if ( readExecuteRegion
.sizeInUse
!= 0 ) {
4173 readExecuteRegion
.cacheFileOffset
= numRegionFileBytes
;
4174 numRegionFileBytes
+= readExecuteRegion
.bufferSize
;
4175 regions
.push_back({ &readExecuteRegion
, 0 });
4177 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4178 regionsVMOrder
.push_back({ &readExecuteRegion
, numRegionVMBytes
});
4179 numRegionVMBytes
+= readExecuteRegion
.bufferSize
;
4183 if ( branchStubsRegion
.bufferSize
!= 0 ) {
4185 branchStubsRegion
.cacheFileOffset
= numRegionFileBytes
;
4186 numRegionFileBytes
+= branchStubsRegion
.bufferSize
;
4187 regions
.push_back({ &branchStubsRegion
, 0 });
4189 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4190 regionsVMOrder
.push_back({ &branchStubsRegion
, numRegionVMBytes
});
4191 numRegionVMBytes
+= branchStubsRegion
.bufferSize
;
4195 if ( dataConstRegion
.sizeInUse
!= 0 ) {
4197 dataConstRegion
.cacheFileOffset
= numRegionFileBytes
;
4198 numRegionFileBytes
+= dataConstRegion
.bufferSize
;
4199 regions
.push_back({ &dataConstRegion
, 0 });
4201 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4202 regionsVMOrder
.push_back({ &dataConstRegion
, numRegionVMBytes
});
4203 numRegionVMBytes
+= dataConstRegion
.bufferSize
;
4207 if ( branchGOTsRegion
.bufferSize
!= 0 ) {
4209 branchGOTsRegion
.cacheFileOffset
= numRegionFileBytes
;
4210 numRegionFileBytes
+= branchGOTsRegion
.bufferSize
;
4211 regions
.push_back({ &branchGOTsRegion
, 0 });
4213 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4214 regionsVMOrder
.push_back({ &branchGOTsRegion
, numRegionVMBytes
});
4215 numRegionVMBytes
+= branchGOTsRegion
.bufferSize
;
4219 // Align to 16k before we lay out all contiguous regions
4220 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4221 for (CustomSegment
& customSegment
: customSegments
) {
4222 Region
& region
= *customSegment
.parentRegion
;
4224 region
.cacheFileOffset
= numRegionFileBytes
;
4225 numRegionFileBytes
+= region
.bufferSize
;
4226 regions
.push_back({ ®ion
, 0 });
4228 // Note we can't align the vm offset in here
4229 assert( (numRegionVMBytes
% 4096) == 0);
4230 regionsVMOrder
.push_back({ ®ion
, numRegionVMBytes
});
4231 numRegionVMBytes
+= region
.bufferSize
;
4233 // Maybe add sections too
4234 uint32_t sectionsToAdd
= 0;
4235 if ( customSegment
.sections
.size() > 1 ) {
4236 // More than one section, so they all need names
4237 sectionsToAdd
= (uint32_t)customSegment
.sections
.size();
4238 } else if ( !customSegment
.sections
.front().sectionName
.empty() ) {
4239 // Only one section, but it has a name
4242 sectionsToAddToRegions
[®ion
] = sectionsToAdd
;
4244 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4248 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4249 if ( prelinkInfoDict
!= nullptr )
4252 prelinkInfoRegion
.cacheFileOffset
= numRegionFileBytes
;
4253 numRegionFileBytes
+= prelinkInfoRegion
.bufferSize
;
4254 regions
.push_back({ &prelinkInfoRegion
, 0 });
4256 if ( !dataRegionFirstInVMOrder
) {
4258 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4259 regionsVMOrder
.push_back({ &prelinkInfoRegion
, numRegionVMBytes
});
4260 numRegionVMBytes
+= prelinkInfoRegion
.bufferSize
;
4263 // Add a section too
4264 sectionsToAddToRegions
[&prelinkInfoRegion
] = 1;
4269 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4270 if ( readWriteRegion
.sizeInUse
!= 0 ) {
4272 readWriteRegion
.cacheFileOffset
= numRegionFileBytes
;
4273 numRegionFileBytes
+= readWriteRegion
.bufferSize
;
4274 regions
.push_back({ &readWriteRegion
, 0 });
4276 if ( !dataRegionFirstInVMOrder
) {
4278 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4279 regionsVMOrder
.push_back({ &readWriteRegion
, numRegionVMBytes
});
4280 numRegionVMBytes
+= readWriteRegion
.bufferSize
;
4286 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4287 if ( hibernateRegion
.sizeInUse
!= 0 ) {
4289 hibernateRegion
.cacheFileOffset
= numRegionFileBytes
;
4290 numRegionFileBytes
+= hibernateRegion
.bufferSize
;
4291 regions
.push_back({ &hibernateRegion
, 0 });
4293 // VM offset was already handled earlier
4296 // Non split seg regions
4297 // Align to 16k before we lay out all contiguous regions
4298 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4299 for (Region
& region
: nonSplitSegRegions
) {
4300 region
.cacheFileOffset
= numRegionFileBytes
;
4301 numRegionFileBytes
+= region
.bufferSize
;
4302 regions
.push_back({ ®ion
, 0 });
4304 // Note we can't align the vm offset in here
4305 assert( (numRegionVMBytes
% 4096) == 0);
4306 regionsVMOrder
.push_back({ ®ion
, numRegionVMBytes
});
4307 numRegionVMBytes
+= region
.bufferSize
;
4309 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4314 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4315 _readOnlyRegion
.cacheFileOffset
= numRegionFileBytes
;
4316 numRegionFileBytes
+= _readOnlyRegion
.bufferSize
;
4317 regions
.push_back({ &_readOnlyRegion
, 0 });
4319 numRegionVMBytes
= align(numRegionVMBytes
, 14);
4320 regionsVMOrder
.push_back({ &_readOnlyRegion
, numRegionVMBytes
});
4321 numRegionVMBytes
+= _readOnlyRegion
.bufferSize
;
4323 // __LINKEDIT fixups sub region
4325 numRegionFileBytes
= align(numRegionFileBytes
, 14);
4326 if ( fixupsSubRegion
.sizeInUse
!= 0 ) {
4327 fixupsSubRegion
.cacheFileOffset
= numRegionFileBytes
;
4328 numRegionFileBytes
+= fixupsSubRegion
.bufferSize
;
4329 //regions.push_back({ &fixupsSubRegion, 0 });
4332 regionsVMOrder
.push_back({ &fixupsSubRegion
, numRegionVMBytes
});
4333 numRegionVMBytes
+= fixupsSubRegion
.bufferSize
;
4336 const thread_command
* unixThread
= nullptr;
4337 if (const DylibInfo
* dylib
= getKernelStaticExecutableInputFile()) {
4338 unixThread
= dylib
->input
->mappedFile
.mh
->unixThreadLoadCommand();
4343 const uint64_t cacheHeaderSize
= sizeof(mach_header_64
);
4344 uint64_t cacheLoadCommandsSize
= 0;
4345 uint64_t cacheNumLoadCommands
= 0;
4348 ++cacheNumLoadCommands
;
4349 uint64_t uuidOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4350 cacheLoadCommandsSize
+= sizeof(uuid_command
);
4353 ++cacheNumLoadCommands
;
4354 uint64_t buildVersionOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4355 cacheLoadCommandsSize
+= sizeof(build_version_command
);
4358 uint64_t unixThreadOffset
= 0;
4359 if ( unixThread
!= nullptr ) {
4360 ++cacheNumLoadCommands
;
4361 unixThreadOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4362 cacheLoadCommandsSize
+= unixThread
->cmdsize
;
4365 // SYMTAB and DYSYMTAB
4366 uint64_t symbolTableOffset
= 0;
4367 uint64_t dynSymbolTableOffset
= 0;
4368 if (const DylibInfo
* dylib
= getKernelStaticExecutableInputFile()) {
4369 if ( dylib
->input
->mappedFile
.mh
->usesClassicRelocationsInKernelCollection() ) {
4371 ++cacheNumLoadCommands
;
4372 symbolTableOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4373 cacheLoadCommandsSize
+= sizeof(symtab_command
);
4376 ++cacheNumLoadCommands
;
4377 dynSymbolTableOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4378 cacheLoadCommandsSize
+= sizeof(dysymtab_command
);
4382 // LC_DYLD_CHAINED_FIXUPS
4383 // The pageableKC has one LC_DYLD_CHAINED_FIXUPS per kext, and 1 more on the top-level
4384 // for the branch GOTs
4385 uint64_t chainedFixupsOffset
= 0;
4386 if ( fixupsSubRegion
.bufferSize
!= 0 ) {
4387 ++cacheNumLoadCommands
;
4388 chainedFixupsOffset
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4389 cacheLoadCommandsSize
+= sizeof(linkedit_data_command
);
4392 // Add an LC_SEGMENT_64 for each region
4393 for (auto& regionAndOffset
: regions
) {
4394 ++cacheNumLoadCommands
;
4395 regionAndOffset
.second
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4396 cacheLoadCommandsSize
+= sizeof(segment_command_64
);
4398 // Add space for any sections too
4399 auto sectionIt
= sectionsToAddToRegions
.find(regionAndOffset
.first
);
4400 if ( sectionIt
!= sectionsToAddToRegions
.end() ) {
4401 uint32_t numSections
= sectionIt
->second
;
4402 cacheLoadCommandsSize
+= sizeof(section_64
) * numSections
;
4406 // Add an LC_FILESET_ENTRY for each dylib
4407 std::vector
<std::pair
<const DylibInfo
*, uint64_t>> dylibs
;
4408 for (const auto& dylib
: sortedDylibs
) {
4409 ++cacheNumLoadCommands
;
4410 const char* dylibID
= dylib
.dylibID
.c_str();
4411 dylibs
.push_back({ &dylib
, cacheHeaderSize
+ cacheLoadCommandsSize
});
4412 uint64_t size
= align(sizeof(fileset_entry_command
) + strlen(dylibID
) + 1, 3);
4413 cacheLoadCommandsSize
+= size
;
4416 uint64_t cacheHeaderRegionSize
= cacheHeaderSize
+ cacheLoadCommandsSize
;
4418 // Align the app cache header before the rest of the bytes
4419 cacheHeaderRegionSize
= align(cacheHeaderRegionSize
, 14);
4421 assert(numRegionFileBytes
<= numRegionVMBytes
);
4423 _allocatedBufferSize
= cacheHeaderRegionSize
+ numRegionVMBytes
;
4425 // The fixup format cannot handle a KC over 1GB (64MB for arm64e auxKC). Error out if we exceed that
4426 uint64_t cacheLimit
= 1 << 30;
4427 if ( (appCacheOptions
.cacheKind
== Options::AppCacheKind::auxKC
) && (_options
.archs
== &dyld3::GradedArchs::arm64e
) )
4428 cacheLimit
= 64 * (1 << 20);
4429 if ( _allocatedBufferSize
>= cacheLimit
) {
4430 _diagnostics
.error("kernel collection size exceeds maximum size of %lld vs actual size of %lld",
4431 cacheLimit
, _allocatedBufferSize
);
4435 if ( vm_allocate(mach_task_self(), &_fullAllocatedBuffer
, _allocatedBufferSize
, VM_FLAGS_ANYWHERE
) != 0 ) {
4436 _diagnostics
.error("could not allocate buffer");
4440 // Assign region vm and buffer addresses now that we know the size of
4443 // All vm offsets prior to the cache header are already correct
4444 // All those after the cache header need to be shifted by the cache
4446 bool seenCacheHeader
= false;
4447 for (const auto& regionAndVMOffset
: regionsVMOrder
) {
4448 Region
* region
= regionAndVMOffset
.first
;
4449 uint64_t vmOffset
= regionAndVMOffset
.second
;
4450 region
->unslidLoadAddress
= cacheBaseAddress
+ vmOffset
;
4451 if ( seenCacheHeader
) {
4452 // Shift by the cache header size
4453 region
->unslidLoadAddress
+= cacheHeaderRegionSize
;
4455 // The offset is correct but add in the base address
4456 seenCacheHeader
= (region
== &cacheHeaderRegion
);
4458 region
->buffer
= (uint8_t*)_fullAllocatedBuffer
+ (region
->unslidLoadAddress
- cacheBaseAddress
);
4463 cacheHeaderRegion
.bufferSize
= cacheHeaderRegionSize
;
4464 cacheHeaderRegion
.sizeInUse
= cacheHeaderRegion
.bufferSize
;
4465 cacheHeaderRegion
.cacheFileOffset
= 0;
4466 cacheHeaderRegion
.initProt
= VM_PROT_READ
;
4467 cacheHeaderRegion
.maxProt
= VM_PROT_READ
;
4468 cacheHeaderRegion
.name
= "__TEXT";
4471 for (const auto& regionAndVMOffset
: regionsVMOrder
) {
4472 printf("0x%llx : %s\n", regionAndVMOffset
.first
->unslidLoadAddress
, regionAndVMOffset
.first
->name
.c_str());
4476 CacheHeader64
& header
= cacheHeader
;
4477 header
.header
= (mach_header_64
*)cacheHeaderRegion
.buffer
;
4478 header
.numLoadCommands
= cacheNumLoadCommands
;
4479 header
.loadCommandsSize
= cacheLoadCommandsSize
;
4480 header
.uuid
= (uuid_command
*)(cacheHeaderRegion
.buffer
+ uuidOffset
);
4481 header
.buildVersion
= (build_version_command
*)(cacheHeaderRegion
.buffer
+ buildVersionOffset
);
4482 if ( unixThread
!= nullptr ) {
4483 header
.unixThread
= (thread_command
*)(cacheHeaderRegion
.buffer
+ unixThreadOffset
);
4484 // Copy the contents here while we have the source pointer available
4485 memcpy(header
.unixThread
, unixThread
, unixThread
->cmdsize
);
4488 if ( symbolTableOffset
!= 0 ) {
4489 header
.symbolTable
= (symtab_command
*)(cacheHeaderRegion
.buffer
+ symbolTableOffset
);
4492 if ( dynSymbolTableOffset
!= 0 ) {
4493 header
.dynSymbolTable
= (dysymtab_command
*)(cacheHeaderRegion
.buffer
+ dynSymbolTableOffset
);
4496 if ( chainedFixupsOffset
!= 0 ) {
4497 header
.chainedFixups
= (linkedit_data_command
*)(cacheHeaderRegion
.buffer
+ chainedFixupsOffset
);
4500 for (auto& regionAndOffset
: regions
) {
4501 assert(regionAndOffset
.first
->initProt
!= 0);
4502 assert(regionAndOffset
.first
->maxProt
!= 0);
4503 segment_command_64
* loadCommand
= (segment_command_64
*)(cacheHeaderRegion
.buffer
+ regionAndOffset
.second
);
4504 header
.segments
.push_back({ loadCommand
, regionAndOffset
.first
});
4506 for (const auto& dylibAndOffset
: dylibs
) {
4507 fileset_entry_command
* loadCommand
= (fileset_entry_command
*)(cacheHeaderRegion
.buffer
+ dylibAndOffset
.second
);
4508 header
.dylibs
.push_back({ loadCommand
, dylibAndOffset
.first
});
4511 // Move the offsets of all the other regions
4513 readOnlyTextRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4515 // Split seg __TEXT_EXEC
4516 readExecuteRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4519 branchStubsRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4521 // Split seg __DATA_CONST
4522 dataConstRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4525 branchGOTsRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4528 readWriteRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4531 hibernateRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4534 for (Region
& region
: customDataRegions
) {
4535 region
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4538 // Non split seg regions
4539 for (Region
& region
: nonSplitSegRegions
) {
4540 region
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4544 prelinkInfoRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4547 _readOnlyRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4549 // __LINKEDIT fixups sub region
4550 fixupsSubRegion
.cacheFileOffset
+= cacheHeaderRegion
.sizeInUse
;
4556 void AppCacheBuilder::generateCacheHeader() {
4558 assert(0 && "Unimplemented");
4562 typedef Pointer64
<LittleEndian
> P
;
4563 CacheHeader64
& header
= cacheHeader
;
4566 macho_header
<P
>* mh
= (macho_header
<P
>*)header
.header
;
4567 mh
->set_magic(MH_MAGIC_64
);
4568 mh
->set_cputype(_options
.archs
->_orderedCpuTypes
[0].type
);
4569 mh
->set_cpusubtype(_options
.archs
->_orderedCpuTypes
[0].subtype
);
4570 mh
->set_filetype(MH_FILESET
);
4571 mh
->set_ncmds((uint32_t)header
.numLoadCommands
);
4572 mh
->set_sizeofcmds((uint32_t)header
.loadCommandsSize
);
4574 mh
->set_reserved(0);
4576 // FIXME: Move this to writeAppCacheHeader
4578 macho_uuid_command
<P
>* cmd
= (macho_uuid_command
<P
>*)header
.uuid
;
4579 cmd
->set_cmd(LC_UUID
);
4580 cmd
->set_cmdsize(sizeof(uuid_command
));
4581 cmd
->set_uuid((uuid_t
){});
4584 // FIXME: Move this to writeAppCacheHeader
4586 macho_build_version_command
<P
>* cmd
= (macho_build_version_command
<P
>*)header
.buildVersion
;
4587 cmd
->set_cmd(LC_BUILD_VERSION
);
4588 cmd
->set_cmdsize(sizeof(build_version_command
));
4589 cmd
->set_platform((uint32_t)_options
.platform
);
4595 // FIXME: Move this to writeAppCacheHeader
4596 // LC_UNIXTHREAD was already memcpy()'ed from the source dylib when we allocated space for it
4597 // We still need to slide its PC value here before we lose the information about the slide
4598 if ( header
.unixThread
!= nullptr ) {
4599 const DylibInfo
* dylib
= getKernelStaticExecutableInputFile();
4600 const dyld3::MachOAnalyzer
* ma
= dylib
->input
->mappedFile
.mh
;
4601 ma
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
4602 uint64_t startAddress
= dylib
->input
->mappedFile
.mh
->entryAddrFromThreadCmd(header
.unixThread
);
4603 if ( (startAddress
< info
.vmAddr
) || (startAddress
>= (info
.vmAddr
+ info
.vmSize
)) )
4606 uint64_t segSlide
= dylib
->cacheLocation
[info
.segIndex
].dstCacheUnslidAddress
- info
.vmAddr
;
4607 startAddress
+= segSlide
;
4609 macho_thread_command
<P
>* cmd
= (macho_thread_command
<P
>*)header
.unixThread
;
4610 cmd
->set_thread_register(ma
->entryAddrRegisterIndexForThreadCmd(), startAddress
);
4616 if ( header
.symbolTable
!= nullptr ) {
4617 macho_symtab_command
<P
>* cmd
= (macho_symtab_command
<P
>*)header
.symbolTable
;
4618 cmd
->set_cmd(LC_SYMTAB
);
4619 cmd
->set_cmdsize(sizeof(symtab_command
));
4623 cmd
->set_strsize(0);
4626 if ( header
.dynSymbolTable
!= nullptr ) {
4627 macho_dysymtab_command
<P
>* cmd
= (macho_dysymtab_command
<P
>*)header
.dynSymbolTable
;
4628 cmd
->set_cmd(LC_DYSYMTAB
);
4629 cmd
->set_cmdsize(sizeof(dysymtab_command
));
4630 cmd
->set_ilocalsym(0);
4631 cmd
->set_nlocalsym(0);
4632 cmd
->set_iextdefsym(0);
4633 cmd
->set_nextdefsym(0);
4634 cmd
->set_iundefsym(0);
4635 cmd
->set_nundefsym(0);
4638 cmd
->set_modtaboff(0);
4639 cmd
->set_nmodtab(0);
4640 cmd
->set_extrefsymoff(0);
4641 cmd
->set_nextrefsyms(0);
4642 cmd
->set_indirectsymoff(0);
4643 cmd
->set_nindirectsyms(0);
4644 cmd
->set_extreloff(0);
4645 cmd
->set_nextrel(0);
4646 cmd
->set_locreloff(0);
4647 cmd
->set_nlocrel(0);
4650 if ( header
.chainedFixups
!= nullptr ) {
4651 macho_linkedit_data_command
<P
>* cmd
= (macho_linkedit_data_command
<P
>*)header
.chainedFixups
;
4652 cmd
->set_cmd(LC_DYLD_CHAINED_FIXUPS
);
4653 cmd
->set_cmdsize(sizeof(linkedit_data_command
));
4654 cmd
->set_dataoff(0);
4655 cmd
->set_datasize(0);
4658 // FIXME: Move this to writeAppCacheHeader
4659 uint64_t segmentIndex
= 0;
4660 for (CacheHeader64::SegmentCommandAndRegion
& cmdAndInfo
: header
.segments
) {
4661 macho_segment_command
<P
>* cmd
= (macho_segment_command
<P
>*)cmdAndInfo
.first
;
4662 Region
* region
= cmdAndInfo
.second
;
4663 region
->index
= segmentIndex
;
4666 assert(region
->initProt
!= 0);
4667 assert(region
->maxProt
!= 0);
4669 const char* name
= region
->name
.c_str();
4671 cmd
->set_cmd(LC_SEGMENT_64
);
4672 cmd
->set_cmdsize(sizeof(segment_command_64
));
4673 cmd
->set_segname(name
);
4674 cmd
->set_vmaddr(region
->unslidLoadAddress
);
4675 cmd
->set_vmsize(region
->sizeInUse
);
4676 cmd
->set_fileoff(region
->cacheFileOffset
);
4677 cmd
->set_filesize(region
->sizeInUse
);
4678 cmd
->set_maxprot(region
->maxProt
);
4679 cmd
->set_initprot(region
->initProt
);
4683 if ( region
== &readOnlyTextRegion
) {
4684 // __PRELINK_TEXT should also get a section
4685 cmd
->set_cmdsize(cmd
->cmdsize() + sizeof(section_64
));
4688 macho_section
<P
>* section
= (macho_section
<P
>*)((uint64_t)cmd
+ sizeof(*cmd
));
4689 section
->set_sectname("__text");
4690 section
->set_segname(name
);
4691 section
->set_addr(region
->unslidLoadAddress
);
4692 section
->set_size(region
->sizeInUse
);
4693 section
->set_offset((uint32_t)region
->cacheFileOffset
);
4694 section
->set_align(0);
4695 section
->set_reloff(0);
4696 section
->set_nreloc(0);
4697 section
->set_flags(S_REGULAR
| S_ATTR_SOME_INSTRUCTIONS
| S_ATTR_PURE_INSTRUCTIONS
);
4698 section
->set_reserved1(0);
4699 section
->set_reserved2(0);
4700 } else if ( region
== &prelinkInfoRegion
) {
4701 // __PRELINK_INFO should also get a section
4702 cmd
->set_cmdsize(cmd
->cmdsize() + sizeof(section_64
));
4705 macho_section
<P
>* section
= (macho_section
<P
>*)((uint64_t)cmd
+ sizeof(*cmd
));
4706 section
->set_sectname("__info");
4707 section
->set_segname(name
);
4708 section
->set_addr(region
->unslidLoadAddress
);
4709 section
->set_size(region
->sizeInUse
);
4710 section
->set_offset((uint32_t)region
->cacheFileOffset
);
4711 section
->set_align(0);
4712 section
->set_reloff(0);
4713 section
->set_nreloc(0);
4714 section
->set_flags(S_REGULAR
);
4715 section
->set_reserved1(0);
4716 section
->set_reserved2(0);
4717 } else if ( region
== &hibernateRegion
) {
4718 // __HIB should also get a section
4719 cmd
->set_cmdsize(cmd
->cmdsize() + sizeof(section_64
));
4722 macho_section
<P
>* section
= (macho_section
<P
>*)((uint64_t)cmd
+ sizeof(*cmd
));
4723 section
->set_sectname("__text");
4724 section
->set_segname(name
);
4725 section
->set_addr(region
->unslidLoadAddress
);
4726 section
->set_size(region
->sizeInUse
);
4727 section
->set_offset((uint32_t)region
->cacheFileOffset
);
4728 section
->set_align(0);
4729 section
->set_reloff(0);
4730 section
->set_nreloc(0);
4731 section
->set_flags(S_REGULAR
| S_ATTR_SOME_INSTRUCTIONS
);
4732 section
->set_reserved1(0);
4733 section
->set_reserved2(0);
4735 // Custom segments may have sections
4736 for (CustomSegment
&customSegment
: customSegments
) {
4737 if ( region
!= customSegment
.parentRegion
)
4740 // Found a segment for this region. Now work out how many sections to emit
4741 // Maybe add sections too
4742 uint32_t sectionsToAdd
= 0;
4743 if ( customSegment
.sections
.size() > 1 ) {
4744 // More than one section, so they all need names
4745 sectionsToAdd
= (uint32_t)customSegment
.sections
.size();
4746 } else if ( !customSegment
.sections
.front().sectionName
.empty() ) {
4747 // Only one section, but it has a name
4750 // Only 1 section, and it has no name, so don't add a section
4754 cmd
->set_cmdsize(cmd
->cmdsize() + (sizeof(section_64
) * sectionsToAdd
));
4755 cmd
->set_nsects(sectionsToAdd
);
4756 uint8_t* bufferPos
= (uint8_t*)cmd
+ sizeof(*cmd
);
4757 for (const CustomSegment::CustomSection
& customSection
: customSegment
.sections
) {
4758 macho_section
<P
>* section
= (macho_section
<P
>*)bufferPos
;
4759 section
->set_sectname(customSection
.sectionName
.c_str());
4760 section
->set_segname(name
);
4761 section
->set_addr(region
->unslidLoadAddress
+ customSection
.offsetInRegion
);
4762 section
->set_size(customSection
.data
.size());
4763 section
->set_offset((uint32_t)(region
->cacheFileOffset
+ customSection
.offsetInRegion
));
4764 section
->set_align(0);
4765 section
->set_reloff(0);
4766 section
->set_nreloc(0);
4767 section
->set_flags(S_REGULAR
);
4768 section
->set_reserved1(0);
4769 section
->set_reserved2(0);
4771 bufferPos
+= sizeof(section_64
);
4777 // Write the dylibs. These are all we need for now to be able to walk the
4779 for (CacheHeader64::DylibCommandAndInfo
& cmdAndInfo
: header
.dylibs
) {
4780 macho_fileset_entry_command
<P
>* cmd
= (macho_fileset_entry_command
<P
>*)cmdAndInfo
.first
;
4781 const DylibInfo
* dylib
= cmdAndInfo
.second
;
4783 const char* dylibID
= dylib
->dylibID
.c_str();
4784 uint64_t size
= align(sizeof(fileset_entry_command
) + strlen(dylibID
) + 1, 3);
4786 cmd
->set_cmd(LC_FILESET_ENTRY
);
4787 cmd
->set_cmdsize((uint32_t)size
);
4788 cmd
->set_vmaddr(dylib
->cacheLocation
[0].dstCacheUnslidAddress
);
4789 cmd
->set_fileoff(dylib
->cacheLocation
[0].dstCacheFileOffset
);
4790 cmd
->set_entry_id(dylibID
);
4795 void AppCacheBuilder::generatePrelinkInfo() {
4796 if ( prelinkInfoDict
== nullptr ) {
4797 // The kernel doesn't need a prelink dictionary just for itself
4798 bool needsPrelink
= true;
4799 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::kernel
) {
4800 if ( sortedDylibs
.size() == 1 )
4801 needsPrelink
= false;
4803 if ( needsPrelink
) {
4804 _diagnostics
.error("Expected prelink info dictionary");
4809 CFMutableArrayRef arrayRef
= (CFMutableArrayRef
)CFDictionaryGetValue(prelinkInfoDict
,
4810 CFSTR("_PrelinkInfoDictionary"));
4811 if ( arrayRef
== nullptr ) {
4812 _diagnostics
.error("Expected prelink info dictionary array");
4816 typedef std::pair
<const dyld3::MachOAnalyzer
*, Diagnostics
*> DylibAndDiag
;
4817 __block
std::unordered_map
<std::string_view
, DylibAndDiag
> dylibs
;
4818 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
4819 DylibStripMode stripMode
, const std::vector
<std::string
>& dependencies
,
4820 Diagnostics
& dylibDiag
, bool& stop
) {
4821 dylibs
[dylibID
] = { ma
, &dylibDiag
};
4823 for (const InputDylib
& dylib
: codelessKexts
) {
4824 dylibs
[dylib
.dylibID
] = { nullptr, nullptr };
4827 __block
std::list
<std::string
> nonASCIIStrings
;
4828 auto getString
= ^(Diagnostics
& diags
, CFStringRef symbolNameRef
) {
4829 const char* symbolName
= CFStringGetCStringPtr(symbolNameRef
, kCFStringEncodingUTF8
);
4830 if ( symbolName
!= nullptr )
4833 CFIndex len
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef
), kCFStringEncodingUTF8
);
4834 char buffer
[len
+ 1];
4835 if ( !CFStringGetCString(symbolNameRef
, buffer
, len
, kCFStringEncodingUTF8
) ) {
4836 diags
.error("Could not convert string to ASCII");
4837 return (const char*)nullptr;
4840 nonASCIIStrings
.push_back(buffer
);
4841 return nonASCIIStrings
.back().c_str();
4844 bool badKext
= false;
4845 CFIndex arrayCount
= CFArrayGetCount(arrayRef
);
4846 for (CFIndex i
= 0; i
!= arrayCount
; ++i
) {
4847 CFMutableDictionaryRef dictRef
= (CFMutableDictionaryRef
)CFArrayGetValueAtIndex(arrayRef
, i
);
4849 CFStringRef bundleIDRef
= (CFStringRef
)CFDictionaryGetValue(dictRef
, CFSTR("CFBundleIdentifier"));
4850 if ( bundleIDRef
== nullptr ) {
4851 _diagnostics
.error("Cannot get bundle ID for dylib");
4855 const char* bundleIDStr
= getString(_diagnostics
, bundleIDRef
);
4856 if ( _diagnostics
.hasError() )
4859 auto dylibIt
= dylibs
.find(bundleIDStr
);
4860 if ( dylibIt
== dylibs
.end() ) {
4861 _diagnostics
.error("Cannot get dylib for bundle ID %s", bundleIDStr
);
4864 const dyld3::MachOAnalyzer
*ma
= dylibIt
->second
.first
;
4865 Diagnostics
* dylibDiag
= dylibIt
->second
.second
;
4866 // Skip codeless kext's
4867 if ( ma
== nullptr )
4869 uint64_t loadAddress
= ma
->preferredLoadAddress();
4871 // _PrelinkExecutableLoadAddr
4872 CFNumberRef loadAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &loadAddress
);
4873 CFDictionarySetValue(dictRef
, CFSTR("_PrelinkExecutableLoadAddr"), loadAddrRef
);
4874 CFRelease(loadAddrRef
);
4876 // _PrelinkExecutableSourceAddr
4877 CFNumberRef sourceAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &loadAddress
);
4878 CFDictionarySetValue(dictRef
, CFSTR("_PrelinkExecutableSourceAddr"), sourceAddrRef
);
4879 CFRelease(sourceAddrRef
);
4882 __block
uint64_t kmodInfoAddress
= 0;
4884 // Check for a global first
4885 __block
bool found
= false;
4887 dyld3::MachOAnalyzer::FoundSymbol foundInfo
;
4888 found
= ma
->findExportedSymbol(_diagnostics
, "_kmod_info", true, foundInfo
, nullptr);
4890 kmodInfoAddress
= loadAddress
+ foundInfo
.value
;
4893 // And fall back to a local if we need to
4895 ma
->forEachLocalSymbol(_diagnostics
, ^(const char* aSymbolName
, uint64_t n_value
, uint8_t n_type
,
4896 uint8_t n_sect
, uint16_t n_desc
, bool& stop
) {
4897 if ( strcmp(aSymbolName
, "_kmod_info") == 0 ) {
4898 kmodInfoAddress
= n_value
;
4906 CFNumberRef kmodInfoAddrRef
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberLongLongType
, &kmodInfoAddress
);
4907 CFDictionarySetValue(dictRef
, CFSTR("_PrelinkKmodInfo"), kmodInfoAddrRef
);
4908 CFRelease(kmodInfoAddrRef
);
4910 // Since we have a reference to the kmod info anyway, set its address field to the correct value
4912 uint64_t kmodInfoVMOffset
= kmodInfoAddress
- loadAddress
;
4913 dyld3::MachOAppCache::KModInfo64_v1
* kmodInfo
= (dyld3::MachOAppCache::KModInfo64_v1
*)((uint8_t*)ma
+ kmodInfoVMOffset
);
4914 if ( kmodInfo
->info_version
!= 1 ) {
4915 dylibDiag
->error("unsupported kmod_info version of %d", kmodInfo
->info_version
);
4919 __block
uint64_t textSegmnentVMAddr
= 0;
4920 __block
uint64_t textSegmnentVMSize
= 0;
4921 ma
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
&info
, bool &stop
) {
4922 if ( !strcmp(info
.segName
, "__TEXT") ) {
4923 textSegmnentVMAddr
= info
.vmAddr
;
4924 textSegmnentVMSize
= info
.vmSize
;
4928 kmodInfo
->address
= textSegmnentVMAddr
;
4929 kmodInfo
->size
= textSegmnentVMSize
;
4933 CFErrorRef errorRef
= nullptr;
4934 CFDataRef xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, prelinkInfoDict
,
4935 kCFPropertyListXMLFormat_v1_0
, 0, &errorRef
);
4936 if (errorRef
!= nullptr) {
4937 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
4938 _diagnostics
.error("Could not serialise plist because :%s",
4939 CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
4941 CFRelease(errorRef
);
4944 CFIndex xmlDataLength
= CFDataGetLength(xmlData
);
4945 if ( xmlDataLength
> prelinkInfoRegion
.bufferSize
) {
4946 _diagnostics
.error("Overflow in prelink info segment. 0x%llx vs 0x%llx",
4947 (uint64_t)xmlDataLength
, prelinkInfoRegion
.bufferSize
);
4952 // Write the prelink info in to the buffer
4953 memcpy(prelinkInfoRegion
.buffer
, CFDataGetBytePtr(xmlData
), xmlDataLength
);
4957 if ( badKext
&& _diagnostics
.noError() ) {
4958 _diagnostics
.error("One or more binaries has an error which prevented linking. See other errors.");
4962 bool AppCacheBuilder::addCustomSection(const std::string
& segmentName
,
4963 CustomSegment::CustomSection section
) {
4964 for (CustomSegment
& segment
: customSegments
) {
4965 if ( segment
.segmentName
!= segmentName
)
4968 // Found a matching segment
4969 // Make sure we don't have a section with this name already
4970 if ( section
.sectionName
.empty() ) {
4971 // We can't add a segment only section if other sections exist
4972 _diagnostics
.error("Cannot add empty section name with segment '%s' as other sections exist on that segment",
4973 segmentName
.c_str());
4977 for (const CustomSegment::CustomSection
& existingSection
: segment
.sections
) {
4978 if ( existingSection
.sectionName
.empty() ) {
4979 // We can't add a section with a name if an existing section exists with no name
4980 _diagnostics
.error("Cannot add section named '%s' with segment '%s' as segment has existing nameless section",
4981 segmentName
.c_str(), section
.sectionName
.c_str());
4984 if ( existingSection
.sectionName
== section
.sectionName
) {
4985 // We can't add a section with the same name as an existing one
4986 _diagnostics
.error("Cannot add section named '%s' with segment '%s' as section already exists",
4987 segmentName
.c_str(), section
.sectionName
.c_str());
4991 segment
.sections
.push_back(section
);
4995 // Didn't find a segment, so add a new one
4996 CustomSegment segment
;
4997 segment
.segmentName
= segmentName
;
4998 segment
.sections
.push_back(section
);
4999 customSegments
.push_back(segment
);
5003 void AppCacheBuilder::setExistingKernelCollection(const dyld3::MachOAppCache
* appCacheMA
) {
5004 existingKernelCollection
= appCacheMA
;
5007 void AppCacheBuilder::setExistingPageableKernelCollection(const dyld3::MachOAppCache
* appCacheMA
) {
5008 pageableKernelCollection
= appCacheMA
;
5011 void AppCacheBuilder::setExtraPrelinkInfo(CFDictionaryRef dictionary
) {
5012 extraPrelinkInfo
= dictionary
;
5016 inline uint32_t absolutetime_to_milliseconds(uint64_t abstime
)
5018 return (uint32_t)(abstime
/1000/1000);
5021 void AppCacheBuilder::buildAppCache(const std::vector
<InputDylib
>& dylibs
)
5023 uint64_t t1
= mach_absolute_time();
5025 // make copy of dylib list and sort
5026 makeSortedDylibs(dylibs
);
5028 // Set the chained pointer format
5029 // x86_64 uses unaligned fixups
5030 if ( (_options
.archs
== &dyld3::GradedArchs::x86_64
) || (_options
.archs
== &dyld3::GradedArchs::x86_64h
) ) {
5031 chainedPointerFormat
= DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE
;
5033 chainedPointerFormat
= DYLD_CHAINED_PTR_64_KERNEL_CACHE
;
5036 // If we have only codeless kexts, then error out
5037 if ( sortedDylibs
.empty() ) {
5038 if ( codelessKexts
.empty() ) {
5039 _diagnostics
.error("No binaries or codeless kexts were provided");
5041 _diagnostics
.error("Cannot build collection without binaries as only %lx codeless kexts provided",
5042 codelessKexts
.size());
5047 // assign addresses for each segment of each dylib in new cache
5048 assignSegmentRegionsAndOffsets();
5049 if ( _diagnostics
.hasError() )
5052 // allocate space used by largest possible cache plus room for LINKEDITS before optimization
5054 if ( _diagnostics
.hasError() )
5057 assignSegmentAddresses();
5059 generateCacheHeader();
5061 // copy all segments into cache
5062 uint64_t t2
= mach_absolute_time();
5065 // rebase all dylibs for new location in cache
5066 uint64_t t3
= mach_absolute_time();
5067 if ( appCacheOptions
.cacheKind
== Options::AppCacheKind::auxKC
) {
5068 // We can have text fixups in the auxKC so ASLR should just track the whole buffer
5069 __block
const Region
* firstDataRegion
= nullptr;
5070 __block
const Region
* lastDataRegion
= nullptr;
5071 forEachRegion(^(const Region
®ion
) {
5072 if ( (firstDataRegion
== nullptr) || (region
.buffer
< firstDataRegion
->buffer
) )
5073 firstDataRegion
= ®ion
;
5074 if ( (lastDataRegion
== nullptr) || (region
.buffer
> lastDataRegion
->buffer
) )
5075 lastDataRegion
= ®ion
;
5078 if ( firstDataRegion
!= nullptr ) {
5079 uint64_t size
= (lastDataRegion
->buffer
- firstDataRegion
->buffer
) + lastDataRegion
->bufferSize
;
5080 _aslrTracker
.setDataRegion(firstDataRegion
->buffer
, size
);
5083 const Region
* firstDataRegion
= nullptr;
5084 const Region
* lastDataRegion
= nullptr;
5085 if ( hibernateRegion
.sizeInUse
!= 0 ) {
5086 firstDataRegion
= &hibernateRegion
;
5087 lastDataRegion
= &hibernateRegion
;
5090 if ( dataConstRegion
.sizeInUse
!= 0 ) {
5091 if ( firstDataRegion
== nullptr )
5092 firstDataRegion
= &dataConstRegion
;
5093 if ( (lastDataRegion
== nullptr) || (dataConstRegion
.buffer
> lastDataRegion
->buffer
) )
5094 lastDataRegion
= &dataConstRegion
;
5097 if ( branchGOTsRegion
.bufferSize
!= 0 ) {
5098 if ( firstDataRegion
== nullptr )
5099 firstDataRegion
= &branchGOTsRegion
;
5100 if ( (lastDataRegion
== nullptr) || (branchGOTsRegion
.buffer
> lastDataRegion
->buffer
) )
5101 lastDataRegion
= &branchGOTsRegion
;
5104 if ( readWriteRegion
.sizeInUse
!= 0 ) {
5105 // __DATA might be before __DATA_CONST in an auxKC
5106 if ( (firstDataRegion
== nullptr) || (readWriteRegion
.buffer
< firstDataRegion
->buffer
) )
5107 firstDataRegion
= &readWriteRegion
;
5108 if ( (lastDataRegion
== nullptr) || (readWriteRegion
.buffer
> lastDataRegion
->buffer
) )
5109 lastDataRegion
= &readWriteRegion
;
5112 for (const Region
& region
: nonSplitSegRegions
) {
5113 // Assume writable regions have fixups to emit
5114 // Note, third party kext's have __TEXT fixups, so assume all of these have fixups
5115 // LINKEDIT is already elsewhere
5116 if ( readWriteRegion
.sizeInUse
!= 0 ) {
5117 assert(region
.buffer
>= readWriteRegion
.buffer
);
5119 if ( firstDataRegion
== nullptr )
5120 firstDataRegion
= ®ion
;
5121 if ( (lastDataRegion
== nullptr) || (region
.buffer
> lastDataRegion
->buffer
) )
5122 lastDataRegion
= ®ion
;
5125 if ( firstDataRegion
!= nullptr ) {
5126 uint64_t size
= (lastDataRegion
->buffer
- firstDataRegion
->buffer
) + lastDataRegion
->bufferSize
;
5127 _aslrTracker
.setDataRegion(firstDataRegion
->buffer
, size
);
5130 adjustAllImagesForNewSegmentLocations(cacheBaseAddress
, _aslrTracker
, nullptr, nullptr);
5131 if ( _diagnostics
.hasError() )
5134 // Once we have the final addresses, we can emit the prelink info segment
5135 generatePrelinkInfo();
5136 if ( _diagnostics
.hasError() )
5139 // build ImageArray for dyld3, which has side effect of binding all cached dylibs
5140 uint64_t t4
= mach_absolute_time();
5142 if ( _diagnostics
.hasError() )
5145 uint64_t t5
= mach_absolute_time();
5147 // optimize away stubs
5148 uint64_t t6
= mach_absolute_time();
5150 __block
std::vector
<std::pair
<const mach_header
*, const char*>> images
;
5151 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
5152 DylibStripMode stripMode
, const std::vector
<std::string
>& dependencies
,
5153 Diagnostics
& dylibDiag
, bool& stop
) {
5154 images
.push_back({ ma
, dylibID
.c_str() });
5156 // FIXME: Should we keep the same never stub eliminate symbols? Eg, for gmalloc.
5157 const char* const neverStubEliminateSymbols
[] = {
5161 uint64_t cacheUnslidAddr
= cacheBaseAddress
;
5162 int64_t cacheSlide
= (long)_fullAllocatedBuffer
- cacheUnslidAddr
;
5163 optimizeAwayStubs(images
, cacheSlide
, cacheUnslidAddr
,
5164 nullptr, neverStubEliminateSymbols
);
5167 // FIPS seal corecrypto, This must be done after stub elimination (so that __TEXT,__text is not changed after sealing)
5170 // merge and compact LINKEDIT segments
5171 uint64_t t7
= mach_absolute_time();
5173 __block
std::vector
<std::tuple
<const mach_header
*, const char*, DylibStripMode
>> images
;
5174 __block
std::set
<const mach_header
*> imagesToStrip
;
5175 __block
const dyld3::MachOAnalyzer
* kernelMA
= nullptr;
5176 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
5177 DylibStripMode stripMode
, const std::vector
<std::string
>& dependencies
,
5178 Diagnostics
& dylibDiag
, bool& stop
) {
5179 if ( stripMode
== DylibStripMode::stripNone
) {
5180 // If the binary didn't have a strip mode, then use the global mode
5181 switch (appCacheOptions
.cacheKind
) {
5182 case Options::AppCacheKind::none
:
5183 assert("Unhandled kind");
5185 case Options::AppCacheKind::kernel
:
5186 switch (appCacheOptions
.stripMode
) {
5187 case Options::StripMode::none
:
5189 case Options::StripMode::all
:
5190 stripMode
= CacheBuilder::DylibStripMode::stripAll
;
5192 case Options::StripMode::allExceptKernel
:
5193 // Strip all binaries which are not the kernel
5194 if ( kernelMA
== nullptr ) {
5195 kernelMA
= getKernelStaticExecutableFromCache();
5197 if ( ma
!= kernelMA
)
5198 stripMode
= CacheBuilder::DylibStripMode::stripAll
;
5202 case Options::AppCacheKind::pageableKC
:
5203 assert("Unhandled kind");
5205 case Options::AppCacheKind::kernelCollectionLevel2
:
5206 assert("Unhandled kind");
5208 case Options::AppCacheKind::auxKC
:
5209 assert("Unhandled kind");
5213 images
.push_back({ ma
, dylibID
.c_str(), stripMode
});
5215 optimizeLinkedit(nullptr, images
);
5217 // update final readOnly region size
5219 assert(0 && "Unimplemented");
5223 typedef Pointer64
<LittleEndian
> P
;
5224 CacheHeader64
& header
= cacheHeader
;
5226 for (CacheHeader64::SegmentCommandAndRegion
& cmdAndRegion
: header
.segments
) {
5227 if (cmdAndRegion
.second
!= &_readOnlyRegion
)
5229 cmdAndRegion
.first
->vmsize
= _readOnlyRegion
.sizeInUse
;
5230 cmdAndRegion
.first
->filesize
= _readOnlyRegion
.sizeInUse
;
5236 uint64_t t8
= mach_absolute_time();
5238 uint64_t t9
= mach_absolute_time();
5240 // Add fixups to rebase/bind the app cache
5244 assert(0 && "Unimplemented");
5246 // update final readOnly region size
5249 typedef Pointer64
<LittleEndian
> P
;
5250 CacheHeader64
& header
= cacheHeader
;
5252 for (CacheHeader64::SegmentCommandAndRegion
& cmdAndRegion
: header
.segments
) {
5253 if (cmdAndRegion
.second
!= &_readOnlyRegion
)
5255 cmdAndRegion
.first
->vmsize
= _readOnlyRegion
.sizeInUse
;
5256 cmdAndRegion
.first
->filesize
= _readOnlyRegion
.sizeInUse
;
5262 // FIXME: We could move _aslrTracker to a worker thread to be destroyed as we don't need it
5265 uint64_t t10
= mach_absolute_time();
5268 if ( _diagnostics
.hasError() )
5271 uint64_t t11
= mach_absolute_time();
5273 if ( _options
.verbose
) {
5274 fprintf(stderr
, "time to layout cache: %ums\n", absolutetime_to_milliseconds(t2
-t1
));
5275 fprintf(stderr
, "time to copy cached dylibs into buffer: %ums\n", absolutetime_to_milliseconds(t3
-t2
));
5276 fprintf(stderr
, "time to adjust segments for new split locations: %ums\n", absolutetime_to_milliseconds(t4
-t3
));
5277 fprintf(stderr
, "time to bind all images: %ums\n", absolutetime_to_milliseconds(t5
-t4
));
5278 fprintf(stderr
, "time to optimize Objective-C: %ums\n", absolutetime_to_milliseconds(t6
-t5
));
5279 fprintf(stderr
, "time to do stub elimination: %ums\n", absolutetime_to_milliseconds(t7
-t6
));
5280 fprintf(stderr
, "time to optimize LINKEDITs: %ums\n", absolutetime_to_milliseconds(t8
-t7
));
5281 fprintf(stderr
, "time to compute slide info: %ums\n", absolutetime_to_milliseconds(t10
-t9
));
5282 fprintf(stderr
, "time to compute UUID and codesign cache file: %ums\n", absolutetime_to_milliseconds(t11
-t10
));
5286 void AppCacheBuilder::fipsSign()
5288 if ( appCacheOptions
.cacheKind
!= Options::AppCacheKind::kernel
)
5291 // find com.apple.kec.corecrypto in collection being built
5292 __block
const dyld3::MachOAnalyzer
* kextMA
= nullptr;
5293 forEachCacheDylib(^(const dyld3::MachOAnalyzer
*ma
, const std::string
&dylibID
,
5294 DylibStripMode stripMode
, const std::vector
<std::string
>& dependencies
,
5295 Diagnostics
& dylibDiag
, bool& stop
) {
5296 if ( dylibID
== "com.apple.kec.corecrypto" ) {
5302 if ( kextMA
== nullptr ) {
5303 _diagnostics
.warning("Could not find com.apple.kec.corecrypto, skipping FIPS sealing");
5307 // find location in com.apple.kec.corecrypto to store hash of __text section
5308 uint64_t hashStoreSize
;
5309 const void* hashStoreLocation
= kextMA
->findSectionContent("__TEXT", "__fips_hmacs", hashStoreSize
);
5310 if ( hashStoreLocation
== nullptr ) {
5311 _diagnostics
.warning("Could not find __TEXT/__fips_hmacs section in com.apple.kec.corecrypto, skipping FIPS sealing");
5314 if ( hashStoreSize
!= 32 ) {
5315 _diagnostics
.warning("__TEXT/__fips_hmacs section in com.apple.kec.corecrypto is not 32 bytes in size, skipping FIPS sealing");
5319 // compute hmac hash of __text section. It may be in __TEXT_EXEC or __TEXT
5321 const void* textLocation
= kextMA
->findSectionContent("__TEXT", "__text", textSize
);
5322 if ( textLocation
== nullptr ) {
5323 textLocation
= kextMA
->findSectionContent("__TEXT_EXEC", "__text", textSize
);
5325 if ( textLocation
== nullptr ) {
5326 _diagnostics
.warning("Could not find __TEXT/__text section in com.apple.kec.corecrypto, skipping FIPS sealing");
5329 unsigned char hmac_key
= 0;
5330 CCHmac(kCCHmacAlgSHA256
, &hmac_key
, 1, textLocation
, textSize
, (void*)hashStoreLocation
); // store hash directly into hashStoreLocation
5333 void AppCacheBuilder::generateUUID() {
5334 uint8_t* uuidLoc
= cacheHeader
.uuid
->uuid
;
5335 assert(uuid_is_null(uuidLoc
));
5337 CCDigestRef digestRef
= CCDigestCreate(kCCDigestSHA256
);
5338 forEachRegion(^(const Region
®ion
) {
5339 if ( _diagnostics
.hasError() )
5341 if ( region
.sizeInUse
== 0 )
5343 int result
= CCDigestUpdate(digestRef
, region
.buffer
, region
.sizeInUse
);
5344 if ( result
!= 0 ) {
5345 _diagnostics
.error("Could not generate UUID: %d", result
);
5349 if ( !_diagnostics
.hasError() ) {
5350 uint8_t buffer
[CCDigestGetOutputSize(kCCDigestSHA256
)];
5351 int result
= CCDigestFinal(digestRef
, buffer
);
5352 memcpy(cacheHeader
.uuid
->uuid
, buffer
, sizeof(cacheHeader
.uuid
->uuid
));
5353 if ( result
!= 0 ) {
5354 _diagnostics
.error("Could not finalize UUID: %d", result
);
5357 CCDigestDestroy(digestRef
);
5358 if ( _diagnostics
.hasError() )
5361 // Update the prelink info dictionary too
5362 if ( prelinkInfoDict
!= nullptr ) {
5363 CFDataRef dataRef
= CFDataCreate(kCFAllocatorDefault
, &cacheHeader
.uuid
->uuid
[0], sizeof(cacheHeader
.uuid
->uuid
));
5364 CFDictionarySetValue(prelinkInfoDict
, CFSTR("_PrelinkKCID"), dataRef
);
5367 CFErrorRef errorRef
= nullptr;
5368 CFDataRef xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, prelinkInfoDict
,
5369 kCFPropertyListXMLFormat_v1_0
, 0, &errorRef
);
5370 if (errorRef
!= nullptr) {
5371 CFStringRef errorString
= CFErrorCopyDescription(errorRef
);
5372 _diagnostics
.error("Could not serialise plist because :%s",
5373 CFStringGetCStringPtr(errorString
, kCFStringEncodingASCII
));
5375 CFRelease(errorRef
);
5378 CFIndex xmlDataLength
= CFDataGetLength(xmlData
);
5379 if ( xmlDataLength
> prelinkInfoRegion
.bufferSize
) {
5380 _diagnostics
.error("Overflow in prelink info segment. 0x%llx vs 0x%llx",
5381 (uint64_t)xmlDataLength
, prelinkInfoRegion
.bufferSize
);
5386 // Write the prelink info in to the buffer
5387 memcpy(prelinkInfoRegion
.buffer
, CFDataGetBytePtr(xmlData
), xmlDataLength
);
5394 void AppCacheBuilder::writeFile(const std::string
& path
)
5396 std::string pathTemplate
= path
+ "-XXXXXX";
5397 size_t templateLen
= strlen(pathTemplate
.c_str())+2;
5398 BLOCK_ACCCESSIBLE_ARRAY(char, pathTemplateSpace
, templateLen
);
5399 strlcpy(pathTemplateSpace
, pathTemplate
.c_str(), templateLen
);
5400 int fd
= mkstemp(pathTemplateSpace
);
5402 _diagnostics
.error("could not open file %s", pathTemplateSpace
);
5405 uint64_t cacheFileSize
= 0;
5406 // FIXME: Do we ever need to avoid allocating space for zero fill?
5407 cacheFileSize
= _readOnlyRegion
.cacheFileOffset
+ _readOnlyRegion
.sizeInUse
;
5409 // set final cache file size (may help defragment file)
5410 ::ftruncate(fd
, cacheFileSize
);
5412 // Write the whole buffer
5413 uint64_t writtenSize
= pwrite(fd
, (const uint8_t*)_fullAllocatedBuffer
, cacheFileSize
, 0);
5414 if (writtenSize
== cacheFileSize
) {
5415 ::fchmod(fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
5416 if ( ::rename(pathTemplateSpace
, path
.c_str()) == 0) {
5421 _diagnostics
.error("could not write whole file. %lld bytes out of %lld were written",
5422 writtenSize
, cacheFileSize
);
5426 ::unlink(pathTemplateSpace
);
5429 void AppCacheBuilder::writeBuffer(uint8_t*& buffer
, uint64_t& bufferSize
) const {
5430 bufferSize
= _readOnlyRegion
.cacheFileOffset
+ _readOnlyRegion
.sizeInUse
;
5431 buffer
= (uint8_t*)malloc(bufferSize
);
5433 forEachRegion(^(const Region
®ion
) {
5434 if ( region
.sizeInUse
== 0 )
5436 memcpy(buffer
+ region
.cacheFileOffset
, (const uint8_t*)region
.buffer
, region
.sizeInUse
);