dyld-832.7.1.tar.gz
[apple/dyld.git] / dyld3 / shared-cache / AppCacheBuilder.cpp
1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
2 *
3 * Copyright (c) 2014 Apple Inc. All rights reserved.
4 *
5 * @APPLE_LICENSE_HEADER_START@
6 *
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
12 * file.
13 *
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.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24
25 #include "AppCacheBuilder.h"
26
27 #include <mach/mach_time.h>
28 #include <sys/stat.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>
34
35 #include <CommonCrypto/CommonHMAC.h>
36 #include <CommonCrypto/CommonDigest.h>
37 #include <CommonCrypto/CommonDigestSPI.h>
38
39 AppCacheBuilder::AppCacheBuilder(const DyldSharedCache::CreateOptions& options,
40 const Options& appCacheOptions,
41 const dyld3::closure::FileSystem& fileSystem)
42 : CacheBuilder(options, fileSystem), appCacheOptions(appCacheOptions)
43 {
44 // FIXME: 32-bit support
45 _is64 = true;
46 }
47
48 AppCacheBuilder::~AppCacheBuilder() {
49 if (prelinkInfoDict) {
50 CFRelease(prelinkInfoDict);
51 }
52 if (_fullAllocatedBuffer) {
53 vm_deallocate(mach_task_self(), _fullAllocatedBuffer, _allocatedBufferSize);
54 }
55 }
56
57
58 void AppCacheBuilder::makeSortedDylibs(const std::vector<InputDylib>& dylibs)
59 {
60 for (const InputDylib& file : dylibs) {
61 if ( file.dylib.loadedFileInfo.fileContent == nullptr ) {
62 codelessKexts.push_back(file);
63 } else {
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;
72 }
73 }
74
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;
81
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)
86 return splitSegA;
87
88 // Finally sort by path
89 return a.input->mappedFile.runtimePath < b.input->mappedFile.runtimePath;
90 });
91
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;
95 });
96 }
97
98
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,
104 bool& stop)) const {
105 bool stop = false;
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);
112 break;
113 }
114 }
115 if (stop)
116 break;
117 }
118 }
119
120 void AppCacheBuilder::forEachDylibInfo(void (^callback)(const DylibInfo& dylib, Diagnostics& dylibDiag)) {
121 for (const AppCacheDylibInfo& dylibInfo : sortedDylibs)
122 callback(dylibInfo, *dylibInfo.errors);
123 }
124
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() )
129 return &dylib;
130 }
131 return nullptr;
132 }
133
134 const dyld3::MachOAnalyzer* AppCacheBuilder::getKernelStaticExecutableFromCache() const {
135 // FIXME: Support reading this from a prebuilt KC
136 assert(appCacheOptions.cacheKind == Options::AppCacheKind::kernel);
137
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,
142 bool& stop) {
143 if ( ma->isStaticExecutable() ) {
144 kernelMA = ma;
145 stop = true;
146 }
147 });
148
149 assert(kernelMA != nullptr);
150 return kernelMA;
151 }
152
153 void AppCacheBuilder::forEachRegion(void (^callback)(const Region& region)) const {
154 // cacheHeaderRegion
155 callback(cacheHeaderRegion);
156
157 // readOnlyTextRegion
158 callback(readOnlyTextRegion);
159
160 // readExecuteRegion
161 if ( readExecuteRegion.sizeInUse != 0 )
162 callback(readExecuteRegion);
163
164 // branchStubsRegion
165 if ( branchStubsRegion.bufferSize != 0 )
166 callback(branchStubsRegion);
167
168 // dataConstRegion
169 if ( dataConstRegion.sizeInUse != 0 )
170 callback(dataConstRegion);
171
172 // branchGOTsRegion
173 if ( branchGOTsRegion.bufferSize != 0 )
174 callback(branchGOTsRegion);
175
176 // readWriteRegion
177 if ( readWriteRegion.sizeInUse != 0 )
178 callback(readWriteRegion);
179
180 // hibernateRegion
181 if ( hibernateRegion.sizeInUse != 0 )
182 callback(hibernateRegion);
183
184 // -sectcreate
185 for (const Region& region : customDataRegions)
186 callback(region);
187
188 // prelinkInfoRegion
189 if ( prelinkInfoDict != nullptr )
190 callback(prelinkInfoRegion);
191
192 // nonSplitSegRegions
193 for (const Region& region : nonSplitSegRegions)
194 callback(region);
195
196 // _readOnlyRegion
197 callback(_readOnlyRegion);
198
199 // fixupsRegion
200 // We don't count this as its not a real region
201 }
202
203 uint64_t AppCacheBuilder::numRegions() const {
204 __block uint64_t count = 0;
205
206 forEachRegion(^(const Region &region) {
207 ++count;
208 });
209
210 return count;
211 }
212
213 uint64_t AppCacheBuilder::fixupsPageSize() const {
214 bool use4K = false;
215 use4K |= (_options.archs == &dyld3::GradedArchs::x86_64);
216 use4K |= (_options.archs == &dyld3::GradedArchs::x86_64h);
217 return use4K ? 4096 : 16384;
218 }
219
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;
225 }
226
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 )
232 return true;
233 bool isX86 = (_options.archs == &dyld3::GradedArchs::x86_64) || (_options.archs == &dyld3::GradedArchs::x86_64h);
234 return isX86 && (appCacheOptions.cacheKind == Options::AppCacheKind::auxKC);
235 }
236
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 )
244 return 0;
245
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
250 return 0;
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
255 break;
256 }
257
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;
262 struct SymbolHash
263 {
264 size_t operator() (const Symbol& symbol) const
265 {
266 return std::hash<std::string_view>{}(symbol.first) ^ std::hash<int>{}(symbol.second);
267 }
268 };
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 )
275 return;
276 seenSymbols.insert({ symbolName, libOrdinal });
277 }, ^(const char *symbolName) {
278 });
279 totalTargets += seenSymbols.size();
280 }
281 return totalTargets;
282 }
283
284 void AppCacheBuilder::assignSegmentRegionsAndOffsets()
285 {
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({});
292 });
293 }
294
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;
301 break;
302 }
303 }
304 if ( kernelMA == nullptr ) {
305 _diagnostics.error("Could not find kernel image");
306 return;
307 }
308 cacheBaseAddress = kernelMA->preferredLoadAddress();
309 }
310
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();
314
315 uint32_t minimumSegmentAlignmentP2 = 14;
316 if ( (_options.archs == &dyld3::GradedArchs::x86_64) || (_options.archs == &dyld3::GradedArchs::x86_64h) ) {
317 minimumSegmentAlignmentP2 = 12;
318 }
319
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;
329 return 0U;
330 };
331
332 {
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();
337 if (!canBePacked)
338 continue;
339
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) )
345 return;
346 if ( (strcmp(segInfo.segName, "__DATA_CONST") == 0)
347 || (strcmp(segInfo.segName, "__PPLDATA_CONST") == 0)
348 || (strcmp(segInfo.segName, "__LASTDATA_CONST") == 0) )
349 return;
350 if ( strcmp(segInfo.segName, "__LINKEDIT") == 0 )
351 return;
352 if ( strcmp(segInfo.segName, "__LINKINFO") == 0 )
353 return;
354
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);
358
359 // __CTF is not mapped in to the kernel, so remove it from the final binary.
360 if ( strcmp(segInfo.segName, "__CTF") == 0 ) {
361 copySize = 0;
362 dstCacheSegmentSize = 0;
363 }
364
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;
383 });
384 }
385
386 // kclist needs this segment, even if its empty, so leave it in there
387 readOnlyTextRegion.bufferSize = align(offsetInRegion, 14);
388 readOnlyTextRegion.sizeInUse = readOnlyTextRegion.bufferSize;
389 readOnlyTextRegion.permissions = VM_PROT_READ;
390 readOnlyTextRegion.name = "__PRELINK_TEXT";
391 }
392
393 // __TEXT segments with r/x permissions
394 {
395 // __TEXT segments with r/x permissions
396 __block uint64_t offsetInRegion = 0;
397 for (DylibInfo& dylib : sortedDylibs) {
398 bool canBePacked = dylib.input->mappedFile.mh->hasSplitSeg();
399 if (!canBePacked)
400 continue;
401
402 __block uint64_t textSegVmAddr = 0;
403 dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
404 if ( strcmp(segInfo.segName, "__TEXT") == 0 )
405 textSegVmAddr = segInfo.vmAddr;
406 if ( strcmp(segInfo.segName, "__HIB") == 0 )
407 return;
408 if ( segInfo.protections != (VM_PROT_READ | VM_PROT_EXECUTE) )
409 return;
410 // kxld packs __TEXT_EXEC so we will do
411 // Note we align to at least 16-bytes as LDR's can scale up to 16 from their address
412 // and aligning them less than 16 would break that
413 uint32_t minAlignmentP2 = getMinAlignment(dylib.input->mappedFile.mh);
414 offsetInRegion = align(offsetInRegion, std::max(segInfo.p2align, 4U));
415 offsetInRegion = align(offsetInRegion, minAlignmentP2);
416 size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
417 uint64_t dstCacheSegmentSize = align(segInfo.sizeOfSections, minAlignmentP2);
418 SegmentMappingInfo loc;
419 loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
420 loc.segName = segInfo.segName;
421 loc.dstSegment = nullptr;
422 loc.dstCacheUnslidAddress = offsetInRegion; // This will be updated later once we've assigned addresses
423 loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
424 loc.dstCacheSegmentSize = (uint32_t)dstCacheSegmentSize;
425 loc.dstCacheFileSize = (uint32_t)copySize;
426 loc.copySegmentSize = (uint32_t)copySize;
427 loc.srcSegmentIndex = segInfo.segIndex;
428 loc.parentRegion = &readExecuteRegion;
429 dylib.cacheLocation[segInfo.segIndex] = loc;
430 offsetInRegion += loc.dstCacheSegmentSize;
431 });
432 }
433
434 // align r/x region end
435 readExecuteRegion.bufferSize = align(offsetInRegion, 14);
436 readExecuteRegion.sizeInUse = readExecuteRegion.bufferSize;
437 readExecuteRegion.permissions = VM_PROT_READ | VM_PROT_EXECUTE;
438 readExecuteRegion.name = "__TEXT_EXEC";
439 }
440
441 if ( branchTargetsFromKexts != 0 ) {
442 // 6-bytes per jmpq
443 branchStubsRegion.bufferSize = align(branchTargetsFromKexts * 6, 14);
444 branchStubsRegion.sizeInUse = branchStubsRegion.bufferSize;
445 branchStubsRegion.permissions = VM_PROT_READ | VM_PROT_EXECUTE;
446 branchStubsRegion.name = "__BRANCH_STUBS";
447 }
448
449 // __DATA_CONST segments
450 {
451 __block uint64_t offsetInRegion = 0;
452 for (DylibInfo& dylib : sortedDylibs) {
453 if (!dylib.input->mappedFile.mh->hasSplitSeg())
454 continue;
455
456 __block uint64_t textSegVmAddr = 0;
457 dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
458 if ( strcmp(segInfo.segName, "__TEXT") == 0 )
459 textSegVmAddr = segInfo.vmAddr;
460 if ( (segInfo.protections & VM_PROT_EXECUTE) != 0 )
461 return;
462 if ( (strcmp(segInfo.segName, "__DATA_CONST") != 0)
463 && (strcmp(segInfo.segName, "__PPLDATA_CONST") != 0)
464 && (strcmp(segInfo.segName, "__LASTDATA_CONST") != 0) )
465 return;
466 // kxld packs __DATA_CONST so we will do
467 uint32_t minAlignmentP2 = getMinAlignment(dylib.input->mappedFile.mh);
468 offsetInRegion = align(offsetInRegion, segInfo.p2align);
469 offsetInRegion = align(offsetInRegion, minAlignmentP2);
470 size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
471 uint64_t dstCacheSegmentSize = align(segInfo.sizeOfSections, minAlignmentP2);
472 SegmentMappingInfo loc;
473 loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
474 loc.segName = segInfo.segName;
475 loc.dstSegment = nullptr;
476 loc.dstCacheUnslidAddress = offsetInRegion; // This will be updated later once we've assigned addresses
477 loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
478 loc.dstCacheSegmentSize = (uint32_t)dstCacheSegmentSize;
479 loc.dstCacheFileSize = (uint32_t)copySize;
480 loc.copySegmentSize = (uint32_t)copySize;
481 loc.srcSegmentIndex = segInfo.segIndex;
482 loc.parentRegion = &dataConstRegion;
483 dylib.cacheLocation[segInfo.segIndex] = loc;
484 offsetInRegion += loc.dstCacheSegmentSize;
485 });
486 }
487
488 // align r/o region end
489 dataConstRegion.bufferSize = align(offsetInRegion, 14);
490 dataConstRegion.sizeInUse = dataConstRegion.bufferSize;
491 dataConstRegion.permissions = VM_PROT_READ;
492 dataConstRegion.name = "__DATA_CONST";
493 }
494
495 // Branch GOTs
496 if ( branchTargetsFromKexts != 0 ) {
497 // 8-bytes per GOT
498 branchGOTsRegion.bufferSize = align(branchTargetsFromKexts * 8, 14);
499 branchGOTsRegion.sizeInUse = branchGOTsRegion.bufferSize;
500 branchGOTsRegion.permissions = VM_PROT_READ | VM_PROT_WRITE;
501 branchGOTsRegion.name = "__BRANCH_GOTS";
502 }
503
504 // __DATA* segments
505 {
506 __block uint64_t offsetInRegion = 0;
507 for (DylibInfo& dylib : sortedDylibs) {
508 if (!dylib.input->mappedFile.mh->hasSplitSeg())
509 continue;
510
511 __block uint64_t textSegVmAddr = 0;
512 dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
513 if ( strcmp(segInfo.segName, "__TEXT") == 0 )
514 textSegVmAddr = segInfo.vmAddr;
515 if ( strcmp(segInfo.segName, "__HIB") == 0 )
516 return;
517 if ( (strcmp(segInfo.segName, "__DATA_CONST") == 0)
518 || (strcmp(segInfo.segName, "__PPLDATA_CONST") == 0)
519 || (strcmp(segInfo.segName, "__LASTDATA_CONST") == 0) )
520 return;
521 if ( segInfo.protections != (VM_PROT_READ | VM_PROT_WRITE) )
522 return;
523 // kxld packs __DATA so we will do
524 uint32_t minAlignmentP2 = getMinAlignment(dylib.input->mappedFile.mh);
525 offsetInRegion = align(offsetInRegion, segInfo.p2align);
526 offsetInRegion = align(offsetInRegion, minAlignmentP2);
527 size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
528 uint64_t dstCacheSegmentSize = align(segInfo.sizeOfSections, minAlignmentP2);
529 SegmentMappingInfo loc;
530 loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
531 loc.segName = segInfo.segName;
532 loc.dstSegment = nullptr;
533 loc.dstCacheUnslidAddress = offsetInRegion; // This will be updated later once we've assigned addresses
534 loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
535 loc.dstCacheSegmentSize = (uint32_t)dstCacheSegmentSize;
536 loc.dstCacheFileSize = (uint32_t)copySize;
537 loc.copySegmentSize = (uint32_t)copySize;
538 loc.srcSegmentIndex = segInfo.segIndex;
539 loc.parentRegion = &readWriteRegion;
540 dylib.cacheLocation[segInfo.segIndex] = loc;
541 offsetInRegion += loc.dstCacheSegmentSize;
542 });
543 }
544
545 // align r/w region end
546 readWriteRegion.bufferSize = align(offsetInRegion, 14);
547 readWriteRegion.sizeInUse = readWriteRegion.bufferSize;
548 readWriteRegion.permissions = VM_PROT_READ | VM_PROT_WRITE;
549 readWriteRegion.name = "__DATA";
550 }
551
552 {
553 // Hibernate region
554 __block uint64_t offsetInRegion = 0;
555 for (DylibInfo& dylib : sortedDylibs) {
556 if ( !dylib.input->mappedFile.mh->isStaticExecutable() )
557 continue;
558
559 __block uint64_t textSegVmAddr = 0;
560 dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
561 if ( strcmp(segInfo.segName, "__TEXT") == 0 )
562 textSegVmAddr = segInfo.vmAddr;
563 if ( strcmp(segInfo.segName, "__HIB") != 0 )
564 return;
565 size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
566 SegmentMappingInfo loc;
567 loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
568 loc.segName = segInfo.segName;
569 loc.dstSegment = nullptr;
570 loc.dstCacheUnslidAddress = offsetInRegion; // This will be updated later once we've assigned addresses
571 loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
572 loc.dstCacheSegmentSize = (uint32_t)segInfo.vmSize;
573 loc.dstCacheFileSize = (uint32_t)copySize;
574 loc.copySegmentSize = (uint32_t)copySize;
575 loc.srcSegmentIndex = segInfo.segIndex;
576 loc.parentRegion = &hibernateRegion;
577 dylib.cacheLocation[segInfo.segIndex] = loc;
578 offsetInRegion += loc.dstCacheSegmentSize;
579
580 hibernateAddress = segInfo.vmAddr;
581 });
582
583 // Only xnu has __HIB, so no need to continue once we've found it.
584 break;
585 }
586
587 hibernateRegion.bufferSize = align(offsetInRegion, 14);
588 hibernateRegion.sizeInUse = hibernateRegion.bufferSize;
589 hibernateRegion.permissions = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE;
590 hibernateRegion.name = "__HIB";
591 }
592
593 // __TEXT and __DATA from non-split seg dylibs, if we have any
594 {
595 for (DylibInfo& dylib : sortedDylibs) {
596 bool canBePacked = dylib.input->mappedFile.mh->hasSplitSeg();
597 if (canBePacked)
598 continue;
599
600 __block uint64_t textSegVmAddr = 0;
601 dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
602 if ( strcmp(segInfo.segName, "__TEXT") == 0 )
603 textSegVmAddr = segInfo.vmAddr;
604 if ( strcmp(segInfo.segName, "__LINKEDIT") == 0 )
605 return;
606
607 nonSplitSegRegions.emplace_back();
608 nonSplitSegRegions.back().permissions = segInfo.protections;
609 nonSplitSegRegions.back().name = "__REGION" + std::to_string(nonSplitSegRegions.size() - 1);
610
611 // Note we don't align the region offset as we have no split seg
612 uint64_t offsetInRegion = 0;
613 SegmentMappingInfo loc;
614 loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
615 loc.segName = segInfo.segName;
616 loc.dstSegment = nullptr;
617 loc.dstCacheUnslidAddress = offsetInRegion; // This will be updated later once we've assigned addresses
618 loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
619 loc.dstCacheSegmentSize = (uint32_t)segInfo.vmSize;
620 loc.dstCacheFileSize = (uint32_t)segInfo.fileSize;
621 loc.copySegmentSize = (uint32_t)segInfo.fileSize;
622 loc.srcSegmentIndex = segInfo.segIndex;
623 loc.parentRegion = &nonSplitSegRegions.back();
624 dylib.cacheLocation[segInfo.segIndex] = loc;
625 offsetInRegion += loc.dstCacheSegmentSize;
626
627 // record non-split seg region end
628 nonSplitSegRegions.back().bufferSize = offsetInRegion;
629 nonSplitSegRegions.back().sizeInUse = nonSplitSegRegions.back().bufferSize;
630 });
631 }
632 }
633
634 // -sectcreate
635 if ( !customSegments.empty() ) {
636 for (CustomSegment& segment: customSegments) {
637 uint64_t offsetInRegion = 0;
638 for (CustomSegment::CustomSection& section : segment.sections) {
639 section.offsetInRegion = offsetInRegion;
640 offsetInRegion += section.data.size();
641 }
642
643 Region& customRegion = customDataRegions.emplace_back();
644 segment.parentRegion = &customRegion;
645
646 // align region end
647 customRegion.bufferSize = align(offsetInRegion, 14);
648 customRegion.sizeInUse = customRegion.bufferSize;
649 customRegion.permissions = VM_PROT_READ;
650 customRegion.name = segment.segmentName;
651 }
652 }
653
654 // __PRELINK_INFO
655 {
656 // This is populated with regular kexts and codeless kexts
657 struct PrelinkInfo {
658 CFDictionaryRef infoPlist = nullptr;
659 const dyld3::MachOAnalyzer* ma = nullptr;
660 std::string_view bundlePath;
661 std::string_view executablePath;
662 };
663 std::vector<PrelinkInfo> infos;
664 for (AppCacheDylibInfo& dylib : sortedDylibs) {
665 if (dylib.infoPlist == nullptr)
666 continue;
667 infos.push_back({ dylib.infoPlist, dylib.input->mappedFile.mh, dylib.bundlePath, dylib.input->loadedFileInfo.path });
668 }
669 for (InputDylib& dylib : codelessKexts) {
670 infos.push_back({ dylib.infoPlist, nullptr, dylib.bundlePath, "" });
671 }
672
673 CFMutableArrayRef bundlesArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0,
674 &kCFTypeArrayCallBacks);
675 for (PrelinkInfo& info : infos) {
676 CFDictionaryRef infoPlist = info.infoPlist;
677 // Create a copy of the dictionary so that we can add more fields
678 CFMutableDictionaryRef dictCopyRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, infoPlist);
679
680 // _PrelinkBundlePath
681 CFStringRef bundlePath = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, info.bundlePath.data(),
682 kCFStringEncodingASCII, kCFAllocatorNull);
683 CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkBundlePath"), bundlePath);
684 CFRelease(bundlePath);
685
686 // Note we want this address to be a large enough integer in the xml format that we have enough space
687 // to replace it with its real address later
688 const uint64_t largeAddress = 0x7FFFFFFFFFFFFFFF;
689
690 // _PrelinkExecutableLoadAddr
691 // Leave a placeholder for this for now just so that we have enough space for it later
692 CFNumberRef loadAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &largeAddress);
693 CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkExecutableLoadAddr"), loadAddrRef);
694 CFRelease(loadAddrRef);
695
696 // _PrelinkExecutableRelativePath
697 if ( info.executablePath != "" ) {
698 const char* relativePath = info.executablePath.data();
699 if ( strncmp(relativePath, info.bundlePath.data(), info.bundlePath.size()) == 0 ) {
700 relativePath = relativePath + info.bundlePath.size();
701 if ( relativePath[0] == '/' )
702 ++relativePath;
703 } else if ( const char* lastSlash = strrchr(relativePath, '/') )
704 relativePath = lastSlash+1;
705 CFStringRef executablePath = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, relativePath,
706 kCFStringEncodingASCII, kCFAllocatorNull);
707 CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkExecutableRelativePath"), executablePath);
708 CFRelease(executablePath);
709 }
710
711 // _PrelinkExecutableSize
712 // This seems to be the file size of __TEXT
713 __block uint64_t textSegFileSize = 0;
714 if ( info.ma != nullptr ) {
715 info.ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
716 if ( strcmp(segInfo.segName, "__TEXT") == 0 )
717 textSegFileSize = segInfo.fileSize;
718 });
719 }
720 if (textSegFileSize != 0) {
721 CFNumberRef fileSizeRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &textSegFileSize);
722 CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkExecutableSize"), fileSizeRef);
723 CFRelease(fileSizeRef);
724 }
725
726 // _PrelinkExecutableSourceAddr
727 // Leave a placeholder for this for now just so that we have enough space for it later
728 CFNumberRef sourceAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &largeAddress);
729 CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkExecutableSourceAddr"), sourceAddrRef);
730 CFRelease(sourceAddrRef);
731
732 // _PrelinkKmodInfo
733 // Leave a placeholder for this for now just so that we have enough space for it later
734 dyld3::MachOAnalyzer::FoundSymbol foundInfo;
735 if ( (info.ma != nullptr) ) {
736 // Check for a global first
737 __block bool found = false;
738 found = info.ma->findExportedSymbol(_diagnostics, "_kmod_info", true, foundInfo, nullptr);
739 if ( !found ) {
740 // And fall back to a local if we need to
741 info.ma->forEachLocalSymbol(_diagnostics, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type,
742 uint8_t n_sect, uint16_t n_desc, bool& stop) {
743 if ( strcmp(aSymbolName, "_kmod_info") == 0 ) {
744 found = true;
745 stop = true;
746 }
747 });
748 }
749
750 if ( found ) {
751 CFNumberRef kmodInfoAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &largeAddress);
752 CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkKmodInfo"), kmodInfoAddrRef);
753 CFRelease(kmodInfoAddrRef);
754 }
755 }
756
757 CFArrayAppendValue(bundlesArrayRef, dictCopyRef);
758 // Release the temporary dictionary now that its in the array
759 CFRelease(dictCopyRef);
760 }
761
762 prelinkInfoDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
763 &kCFTypeDictionaryKeyCallBacks,
764 &kCFTypeDictionaryValueCallBacks);
765
766 // First add any data from addPrelinkInfo()
767 if ( extraPrelinkInfo != nullptr ) {
768 CFDictionaryApplierFunction applier = [](const void *key, const void *value, void *context) {
769 CFMutableDictionaryRef parentDict = (CFMutableDictionaryRef)context;
770 CFDictionaryAddValue(parentDict, key, value);
771 };
772 CFDictionaryApplyFunction(extraPrelinkInfo, applier, (void*)prelinkInfoDict);
773 }
774
775 if ( bundlesArrayRef != nullptr ) {
776 CFDictionaryAddValue(prelinkInfoDict, CFSTR("_PrelinkInfoDictionary"), bundlesArrayRef);
777 CFRelease(bundlesArrayRef);
778 }
779
780 // Add a placeholder for the collection UUID
781 {
782 uuid_t uuid = {};
783 CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (const uint8_t*)&uuid, sizeof(uuid));
784 CFDictionaryAddValue(prelinkInfoDict, CFSTR("_PrelinkKCID"), dataRef);
785 CFRelease(dataRef);
786 }
787
788 // The pageable/aux KCs should embed the UUID of the base kernel collection
789 if ( existingKernelCollection != nullptr ) {
790 uuid_t uuid = {};
791 bool foundUUID = existingKernelCollection->getUuid(uuid);
792 if ( !foundUUID ) {
793 _diagnostics.error("Could not find UUID in base kernel collection");
794 return;
795 }
796 CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (const uint8_t*)&uuid, sizeof(uuid));
797 CFDictionaryAddValue(prelinkInfoDict, CFSTR("_BootKCID"), dataRef);
798 CFRelease(dataRef);
799 }
800
801 // The aux KC should embed the UUID of the pageable kernel collection if we have one
802 if ( pageableKernelCollection != nullptr ) {
803 uuid_t uuid = {};
804 bool foundUUID = pageableKernelCollection->getUuid(uuid);
805 if ( !foundUUID ) {
806 _diagnostics.error("Could not find UUID in pageable kernel collection");
807 return;
808 }
809 CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (const uint8_t*)&uuid, sizeof(uuid));
810 CFDictionaryAddValue(prelinkInfoDict, CFSTR("_PageableKCID"), dataRef);
811 CFRelease(dataRef);
812 }
813
814 CFErrorRef errorRef = nullptr;
815 CFDataRef xmlData = CFPropertyListCreateData(kCFAllocatorDefault, prelinkInfoDict,
816 kCFPropertyListXMLFormat_v1_0, 0, &errorRef);
817 if (errorRef != nullptr) {
818 CFStringRef errorString = CFErrorCopyDescription(errorRef);
819 _diagnostics.error("Could not serialise plist because :%s",
820 CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
821 CFRelease(xmlData);
822 CFRelease(errorRef);
823 return;
824 } else {
825 CFIndex xmlDataLength = CFDataGetLength(xmlData);
826 CFRelease(xmlData);
827
828 // align region end
829 prelinkInfoRegion.bufferSize = align(xmlDataLength, 14);
830 prelinkInfoRegion.sizeInUse = prelinkInfoRegion.bufferSize;
831 prelinkInfoRegion.permissions = VM_PROT_READ | VM_PROT_WRITE;
832 prelinkInfoRegion.name = "__PRELINK_INFO";
833 }
834 }
835
836 // Do all __LINKINFO regardless of split seg
837 _nonLinkEditReadOnlySize = 0;
838 __block uint64_t offsetInRegion = 0;
839 for (DylibInfo& dylib : sortedDylibs) {
840 __block uint64_t textSegVmAddr = 0;
841 dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
842 if ( strcmp(segInfo.segName, "__TEXT") == 0 )
843 textSegVmAddr = segInfo.vmAddr;
844 if ( segInfo.protections != VM_PROT_READ )
845 return;
846 if ( strcmp(segInfo.segName, "__LINKINFO") != 0 )
847 return;
848 // Keep segments 4K or more aligned
849 offsetInRegion = align(offsetInRegion, std::max((int)segInfo.p2align, (int)12));
850 size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
851 SegmentMappingInfo loc;
852 loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
853 loc.segName = segInfo.segName;
854 loc.dstSegment = nullptr;
855 loc.dstCacheUnslidAddress = offsetInRegion; // This will be updated later once we've assigned addresses
856 loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
857 loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12);
858 loc.dstCacheFileSize = (uint32_t)copySize;
859 loc.copySegmentSize = (uint32_t)copySize;
860 loc.srcSegmentIndex = segInfo.segIndex;
861 loc.parentRegion = &_readOnlyRegion;
862 dylib.cacheLocation[segInfo.segIndex] = loc;
863 offsetInRegion += loc.dstCacheSegmentSize;
864 });
865 }
866
867 // Align the end of the __LINKINFO
868 offsetInRegion = align(offsetInRegion, 14);
869 _nonLinkEditReadOnlySize = offsetInRegion;
870
871 // Do all __LINKEDIT, regardless of split seg
872 for (DylibInfo& dylib : sortedDylibs) {
873 __block uint64_t textSegVmAddr = 0;
874 dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
875 if ( strcmp(segInfo.segName, "__TEXT") == 0 )
876 textSegVmAddr = segInfo.vmAddr;
877 if ( segInfo.protections != VM_PROT_READ )
878 return;
879 if ( strcmp(segInfo.segName, "__LINKEDIT") != 0 )
880 return;
881 // Keep segments 4K or more aligned
882 offsetInRegion = align(offsetInRegion, std::max((int)segInfo.p2align, (int)12));
883 size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
884 SegmentMappingInfo loc;
885 loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
886 loc.segName = segInfo.segName;
887 loc.dstSegment = nullptr;
888 loc.dstCacheUnslidAddress = offsetInRegion; // This will be updated later once we've assigned addresses
889 loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
890 loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12);
891 loc.dstCacheFileSize = (uint32_t)copySize;
892 loc.copySegmentSize = (uint32_t)copySize;
893 loc.srcSegmentIndex = segInfo.segIndex;
894 loc.parentRegion = &_readOnlyRegion;
895 dylib.cacheLocation[segInfo.segIndex] = loc;
896 offsetInRegion += loc.dstCacheSegmentSize;
897 });
898 }
899
900 // align r/o region end
901 _readOnlyRegion.bufferSize = align(offsetInRegion, 14);
902 _readOnlyRegion.sizeInUse = _readOnlyRegion.bufferSize;
903 _readOnlyRegion.permissions = VM_PROT_READ;
904 _readOnlyRegion.name = "__LINKEDIT";
905
906 // Add space in __LINKEDIT for chained fixups and classic relocs
907 {
908
909 // The pageableKC (and sometimes auxKC) has 1 LC_DYLD_CHAINED_FIXUPS per kext
910 // while other KCs have 1 for the whole KC.
911 // It also tracks each segment in each kext for chained fixups, not the segments on the KC itself
912 __block uint64_t numSegmentsForChainedFixups = 0;
913 uint64_t numChainedFixupHeaders = 0;
914 if ( fixupsArePerKext() ) {
915 for (DylibInfo& dylib : sortedDylibs) {
916 dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
917 ++numSegmentsForChainedFixups;
918 });
919 }
920 numChainedFixupHeaders = sortedDylibs.size();
921
922 // Branch stubs need fixups on the GOTs region. So add in a top-level chained fixup entry
923 // and for now all the regions as we don't know what segment index the branch GOTs will be
924 numSegmentsForChainedFixups += numRegions();
925 numChainedFixupHeaders++;
926 } else {
927 numSegmentsForChainedFixups = numRegions();
928 numChainedFixupHeaders = 1;
929 }
930
931 uint64_t numBytesForPageStarts = 0;
932 if ( dataConstRegion.sizeInUse != 0 )
933 numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(dataConstRegion.bufferSize));
934 if ( branchGOTsRegion.bufferSize != 0 )
935 numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(branchGOTsRegion.bufferSize));
936 if ( readWriteRegion.sizeInUse != 0 )
937 numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(readWriteRegion.bufferSize));
938 if ( hibernateRegion.sizeInUse != 0 )
939 numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(hibernateRegion.bufferSize));
940 for (const Region& region : nonSplitSegRegions) {
941 // Assume writable regions have fixups to emit
942 // Note, third party kext's have __TEXT fixups, so assume all of these have fixups
943 // LINKEDIT is already elsewhere
944 numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(region.bufferSize));
945 }
946
947 uint64_t numBytesForChainedFixups = 0;
948 if ( numBytesForPageStarts != 0 ) {
949 numBytesForChainedFixups = numBytesForPageStarts;
950 numBytesForChainedFixups += sizeof(dyld_chained_fixups_header) * numChainedFixupHeaders;
951 numBytesForChainedFixups += sizeof(dyld_chained_starts_in_image) * numChainedFixupHeaders;
952 numBytesForChainedFixups += sizeof(uint32_t) * numSegmentsForChainedFixups;
953 }
954
955 __block uint64_t numBytesForClassicRelocs = 0;
956 if ( appCacheOptions.cacheKind == Options::AppCacheKind::kernel ) {
957 if ( const DylibInfo* dylib = getKernelStaticExecutableInputFile() ) {
958 if ( dylib->input->mappedFile.mh->usesClassicRelocationsInKernelCollection() ) {
959 dylib->input->mappedFile.mh->forEachRebase(_diagnostics, false, ^(uint64_t runtimeOffset, bool &stop) {
960 numBytesForClassicRelocs += sizeof(relocation_info);
961 });
962 }
963 }
964 }
965
966 // align fixups region end
967 if ( (numBytesForChainedFixups != 0) || (numBytesForClassicRelocs != 0) ) {
968 uint64_t numBytes = align(numBytesForChainedFixups, 3) + align(numBytesForClassicRelocs, 3);
969 fixupsSubRegion.bufferSize = align(numBytes, 14);
970 fixupsSubRegion.sizeInUse = fixupsSubRegion.bufferSize;
971 fixupsSubRegion.permissions = VM_PROT_READ;
972 fixupsSubRegion.name = "__FIXUPS";
973 }
974 }
975 }
976
977 void AppCacheBuilder::assignSegmentAddresses() {
978 // Segments already have offsets in to their regions. Now assign the regions their addresses
979 // in the full allocated buffer, and then assign all segments in those regions
980 for (DylibInfo& dylib : sortedDylibs) {
981 for (SegmentMappingInfo& loc : dylib.cacheLocation) {
982 loc.dstSegment = loc.parentRegion->buffer + loc.dstCacheFileOffset;
983 loc.dstCacheUnslidAddress = loc.parentRegion->unslidLoadAddress + loc.dstCacheFileOffset;
984 loc.dstCacheFileOffset = (uint32_t)loc.parentRegion->cacheFileOffset + loc.dstCacheFileOffset;
985 }
986 }
987 }
988
989 void AppCacheBuilder::copyRawSegments() {
990 const bool log = false;
991
992 // Call the base class to copy segment data
993 CacheBuilder::copyRawSegments();
994
995 // The copy any custom sections
996 for (const CustomSegment& segment : customSegments) {
997 for (const CustomSegment::CustomSection& section : segment.sections) {
998 uint8_t* dstBuffer = segment.parentRegion->buffer + section.offsetInRegion;
999 uint64_t dstVMAddr = segment.parentRegion->unslidLoadAddress + section.offsetInRegion;
1000 if (log) fprintf(stderr, "copy %s segment %s %s (0x%08lX bytes) from %p to %p (logical addr 0x%llX)\n",
1001 _options.archs->name(), segment.segmentName.c_str(), section.sectionName.c_str(),
1002 section.data.size(), section.data.data(), dstBuffer, dstVMAddr);
1003 ::memcpy(dstBuffer, section.data.data(), section.data.size());
1004 }
1005 }
1006 }
1007
1008 static uint8_t getFixupLevel(AppCacheBuilder::Options::AppCacheKind kind) {
1009 uint8_t currentLevel = (uint8_t)~0U;
1010 switch (kind) {
1011 case AppCacheBuilder::Options::AppCacheKind::none:
1012 assert(0 && "Cache kind should have been set");
1013 break;
1014 case AppCacheBuilder::Options::AppCacheKind::kernel:
1015 currentLevel = 0;
1016 break;
1017 case AppCacheBuilder::Options::AppCacheKind::pageableKC:
1018 // The pageableKC sits right above the baseKC which is level 0
1019 currentLevel = 1;
1020 break;
1021 case AppCacheBuilder::Options::AppCacheKind::kernelCollectionLevel2:
1022 assert(0 && "Unimplemented");
1023 break;
1024 case AppCacheBuilder::Options::AppCacheKind::auxKC:
1025 currentLevel = 3;
1026 break;
1027 }
1028 return currentLevel;
1029 }
1030
1031 uint32_t AppCacheBuilder::getCurrentFixupLevel() const {
1032 return getFixupLevel(appCacheOptions.cacheKind);
1033 }
1034
1035 struct VTableBindSymbol {
1036 std::string_view binaryID;
1037 std::string symbolName;
1038 };
1039
1040 // For every dylib, lets make a map from its exports to its defs
1041 struct DylibSymbols {
1042 // Define a bunch of constructors so that we know we are getting move constructors not copies
1043 DylibSymbols() = default;
1044 DylibSymbols(const DylibSymbols&) = delete;
1045 DylibSymbols(DylibSymbols&&) = default;
1046 DylibSymbols(std::map<std::string_view, uint64_t>&& globals,
1047 std::map<std::string_view, uint64_t>&& locals,
1048 std::unique_ptr<std::unordered_set<std::string>> kpiSymbols,
1049 uint32_t dylibLevel, const std::string& dylibName)
1050 : globals(std::move(globals)), locals(std::move(locals)), kpiSymbols(std::move(kpiSymbols)),
1051 dylibLevel(dylibLevel), dylibName(dylibName) { }
1052
1053 DylibSymbols& operator=(const DylibSymbols& other) = delete;
1054 DylibSymbols& operator=(DylibSymbols&& other) = default;
1055
1056 std::map<std::string_view, uint64_t> globals;
1057
1058 // We also need to track locals as vtable patching supports patching with these too
1059 std::map<std::string_view, uint64_t> locals;
1060
1061 // KPI (ie, a symbol set embedded in this binary)
1062 std::unique_ptr<std::unordered_set<std::string>> kpiSymbols;
1063
1064 // Kernel collections can reference each other in levels. This is the level
1065 // of the exported dylib. Eg, the base KC is 0, and the aux KC is 3
1066 uint32_t dylibLevel = 0;
1067
1068 // Store the name of the dylib for fast lookups
1069 std::string dylibName;
1070
1071 // Keep track of the binds in this dylib as these tell us if a vtable slot is to a local
1072 // or external definition of a function
1073 std::unordered_map<const uint8_t*, VTableBindSymbol> resolvedBindLocations;
1074 };
1075
1076 class VTablePatcher {
1077 public:
1078
1079 VTablePatcher(uint32_t numFixupLevels);
1080
1081 bool hasError() const;
1082
1083 void addKernelCollection(const dyld3::MachOAppCache* cacheMA, AppCacheBuilder::Options::AppCacheKind kind,
1084 const uint8_t* basePointer, uint64_t baseAddress);
1085 void addDylib(Diagnostics& diags, const dyld3::MachOAnalyzer* ma, const std::string& dylibID,
1086 const std::vector<std::string>& dependencies, uint8_t cacheLevel);
1087
1088 void findMetaclassDefinitions(std::map<std::string, DylibSymbols>& dylibsToSymbols,
1089 const std::string& kernelID, const dyld3::MachOAnalyzer* kernelMA,
1090 AppCacheBuilder::Options::AppCacheKind cacheKind);
1091 void findExistingFixups(Diagnostics& diags,
1092 const dyld3::MachOAppCache* existingKernelCollection,
1093 const dyld3::MachOAppCache* pageableKernelCollection);
1094 void findBaseKernelVTables(Diagnostics& diags, const dyld3::MachOAppCache* existingKernelCollection,
1095 std::map<std::string, DylibSymbols>& dylibsToSymbols);
1096 void findPageableKernelVTables(Diagnostics& diags, const dyld3::MachOAppCache* existingKernelCollection,
1097 std::map<std::string, DylibSymbols>& dylibsToSymbols);
1098 void findVTables(uint8_t currentLevel, const dyld3::MachOAnalyzer* kernelMA,
1099 std::map<std::string, DylibSymbols>& dylibsToSymbols,
1100 const AppCacheBuilder::ASLR_Tracker& aslrTracker,
1101 const std::map<const uint8_t*, const VTableBindSymbol>& missingBindLocations);
1102 void calculateSymbols();
1103 void patchVTables(Diagnostics& diags,
1104 std::map<const uint8_t*, const VTableBindSymbol>& missingBindLocations,
1105 AppCacheBuilder::ASLR_Tracker& aslrTracker,
1106 uint8_t currentLevel);
1107
1108 private:
1109
1110 void logFunc(const char* format, ...) {
1111 if ( logPatching ) {
1112 va_list list;
1113 va_start(list, format);
1114 vfprintf(stderr, format, list);
1115 va_end(list);
1116 }
1117 };
1118
1119 void logFuncVerbose(const char* format, ...) {
1120 if ( logPatchingVerbose ) {
1121 va_list list;
1122 va_start(list, format);
1123 vfprintf(stderr, format, list);
1124 va_end(list);
1125 }
1126 };
1127
1128 // Extract a substring by dropping optional prefix/suffix
1129 std::string_view extractString(std::string_view str, std::string_view prefix, std::string_view suffix) {
1130 if ( !prefix.empty() ) {
1131 // Make sure we have the prefix we are looking for
1132 if ( str.find(prefix) != 0 ) {
1133 return std::string_view();
1134 }
1135 str.remove_prefix(prefix.size());
1136 }
1137 if ( !suffix.empty() ) {
1138 // Make sure we have the prefix we are looking for
1139 size_t pos = str.rfind(suffix);
1140 if ( pos != (str.size() - suffix.size()) ) {
1141 return std::string_view();
1142 }
1143 str.remove_suffix(suffix.size());
1144 }
1145 return str;
1146 };
1147
1148 struct VTable {
1149 struct Entry {
1150 const uint8_t* location = nullptr;
1151 uint64_t targetVMAddr = ~0ULL;
1152 uint32_t targetCacheLevel = ~0;
1153 // Pointer auth
1154 uint16_t diversity = 0;
1155 bool hasAddrDiv = false;
1156 uint8_t key = 0;
1157 bool hasPointerAuth = false;
1158 };
1159
1160 const dyld3::MachOAnalyzer* ma = nullptr;
1161 const uint8_t* superVTable = nullptr;
1162 const DylibSymbols* dylib = nullptr;
1163 bool fromParentCollection = false;
1164 bool patched = false;
1165 std::string name = "";
1166 std::vector<Entry> entries;
1167 };
1168
1169 struct SymbolLocation {
1170 uint64_t vmAddr = 0;
1171 bool foundSymbol = 0;
1172
1173 bool found() const {
1174 return foundSymbol;
1175 }
1176 };
1177
1178 struct Fixup {
1179 uint64_t targetVMAddr = 0;
1180 uint8_t cacheLevel = 0;
1181 // Pointer auth
1182 uint16_t diversity = 0;
1183 bool hasAddrDiv = false;
1184 uint8_t key = 0;
1185 bool hasPointerAuth = false;
1186 };
1187
1188 struct VTableDylib {
1189 Diagnostics* diags = nullptr;
1190 const dyld3::MachOAnalyzer* ma = nullptr;
1191 std::string dylibID = "";
1192 std::vector<std::string> dependencies;
1193 uint32_t cacheLevel = ~0U;
1194 };
1195
1196 struct KernelCollection {
1197 const dyld3::MachOAppCache* ma = nullptr;
1198
1199 // We need the base pointers to the buffers for every level
1200 // These are the base of the allocated memory, which corresponds to pointing to the lowest
1201 // vmAddr for the buffer. These do *not* necessarily point to a mach_header
1202 const uint8_t* basePointer = nullptr;
1203
1204 // We also need the base vm addresses to the buffers for every level
1205 uint64_t baseAddress = ~0ULL;
1206
1207 std::unordered_map<uint64_t, const char*> symbolNames;
1208 std::map<uint64_t, std::string_view> metaclassDefinitions;
1209 };
1210
1211 SymbolLocation findVTablePatchingSymbol(std::string_view symbolName, const DylibSymbols& dylibSymbols);
1212
1213 std::vector<VTableDylib> dylibs;
1214 std::map<const uint8_t*, VTable> vtables;
1215 std::vector<KernelCollection> collections;
1216 const uint8_t* baseMetaClassVTableLoc = nullptr;
1217
1218 // Record all the fixup locations in the base/pageable KCs as we need to use them instead of the ASLR tracker
1219 std::map<const uint8_t*, Fixup> existingCollectionFixupLocations;
1220
1221 const uint32_t pointerSize = 8;
1222 const bool logPatching = false;
1223 const bool logPatchingVerbose = false;
1224
1225 // Magic constants for vtable patching
1226 //const char* cxxPrefix = "__Z";
1227 const char* vtablePrefix = "__ZTV";
1228 const char* osObjPrefix = "__ZN";
1229 // const char* vtableReservedToken = "_RESERVED";
1230 const char* metaclassToken = "10gMetaClassE";
1231 const char* superMetaclassPointerToken = "10superClassE";
1232 const char* metaclassVTablePrefix = "__ZTVN";
1233 const char* metaclassVTableSuffix = "9MetaClassE";
1234 };
1235
1236 VTablePatcher::VTablePatcher(uint32_t numFixupLevels) {
1237 collections.resize(numFixupLevels);
1238 }
1239
1240 bool VTablePatcher::hasError() const {
1241 for (const VTableDylib& dylib : dylibs) {
1242 if ( dylib.diags->hasError() )
1243 return true;
1244 }
1245 return false;
1246 }
1247
1248 void VTablePatcher::addKernelCollection(const dyld3::MachOAppCache* cacheMA, AppCacheBuilder::Options::AppCacheKind kind,
1249 const uint8_t* basePointer, uint64_t baseAddress) {
1250 uint8_t cacheLevel = getFixupLevel(kind);
1251
1252 assert(cacheLevel < collections.size());
1253 assert(collections[cacheLevel].ma == nullptr);
1254
1255 collections[cacheLevel].ma = cacheMA;
1256 collections[cacheLevel].basePointer = basePointer;
1257 collections[cacheLevel].baseAddress = baseAddress;
1258 }
1259
1260 void VTablePatcher::addDylib(Diagnostics &diags, const dyld3::MachOAnalyzer *ma,
1261 const std::string& dylibID, const std::vector<std::string>& dependencies,
1262 uint8_t cacheLevel) {
1263 dylibs.push_back((VTableDylib){ &diags, ma, dylibID, dependencies, cacheLevel });
1264 }
1265
1266 VTablePatcher::SymbolLocation VTablePatcher::findVTablePatchingSymbol(std::string_view symbolName,
1267 const DylibSymbols& dylibSymbols) {
1268 // First look in the globals
1269 auto globalsIt = dylibSymbols.globals.find(symbolName);
1270 if ( globalsIt != dylibSymbols.globals.end() ) {
1271 return { globalsIt->second, true };
1272 }
1273
1274 // Then again in the locals
1275 auto localsIt = dylibSymbols.locals.find(symbolName);
1276 if ( localsIt != dylibSymbols.locals.end() ) {
1277 return { localsIt->second, true };
1278 }
1279
1280 return { ~0ULL, false };
1281 };
1282
1283 void VTablePatcher::findMetaclassDefinitions(std::map<std::string, DylibSymbols>& dylibsToSymbols,
1284 const std::string& kernelID, const dyld3::MachOAnalyzer* kernelMA,
1285 AppCacheBuilder::Options::AppCacheKind cacheKind) {
1286 for (VTableDylib& dylib : dylibs) {
1287 auto& metaclassDefinitions = collections[dylib.cacheLevel].metaclassDefinitions;
1288 dylib.ma->forEachGlobalSymbol(*dylib.diags, ^(const char *symbolName, uint64_t n_value,
1289 uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
1290 if ( strstr(symbolName, metaclassToken) != nullptr )
1291 metaclassDefinitions[n_value] = symbolName;
1292 });
1293 dylib.ma->forEachLocalSymbol(*dylib.diags, ^(const char *symbolName, uint64_t n_value,
1294 uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
1295 if ( strstr(symbolName, metaclassToken) != nullptr )
1296 metaclassDefinitions[n_value] = symbolName;
1297 });
1298 }
1299
1300 // Keep track of the root OSMetaClass from which all other metaclasses inherit
1301 DylibSymbols& kernelDylibSymbols = dylibsToSymbols[kernelID];
1302 SymbolLocation symbolLocation = findVTablePatchingSymbol("__ZTV11OSMetaClass", kernelDylibSymbols);
1303 if ( symbolLocation.found() ) {
1304 baseMetaClassVTableLoc = (uint8_t*)kernelMA + (symbolLocation.vmAddr - kernelMA->preferredLoadAddress());
1305
1306 VTable& vtable = vtables[baseMetaClassVTableLoc];
1307 vtable.ma = kernelMA;
1308 vtable.dylib = &kernelDylibSymbols;
1309 vtable.fromParentCollection = (cacheKind != AppCacheBuilder::Options::AppCacheKind::kernel);
1310 vtable.patched = true;
1311 vtable.name = "__ZTV11OSMetaClass";
1312 }
1313 }
1314
1315 void VTablePatcher::findExistingFixups(Diagnostics& diags,
1316 const dyld3::MachOAppCache* existingKernelCollection,
1317 const dyld3::MachOAppCache* pageableKernelCollection) {
1318
1319 const bool is64 = pointerSize == 8;
1320
1321 if ( existingKernelCollection != nullptr ) {
1322 uint8_t kernelLevel = getFixupLevel(AppCacheBuilder::Options::AppCacheKind::kernel);
1323 uint64_t kernelBaseAddress = collections[kernelLevel].baseAddress;
1324 const uint8_t* kernelBasePointer = collections[kernelLevel].basePointer;
1325
1326 // We may have both chained and classic fixups. First add chained
1327 if ( existingKernelCollection->hasChainedFixupsLoadCommand() ) {
1328 existingKernelCollection->withChainStarts(diags, 0, ^(const dyld_chained_starts_in_image* starts) {
1329 existingKernelCollection->forEachFixupInAllChains(diags, starts, false,
1330 ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
1331 uint64_t vmOffset = 0;
1332 bool isRebase = fixupLoc->isRebase(segInfo->pointer_format, kernelBaseAddress, vmOffset);
1333 assert(isRebase);
1334 uint64_t targetVMAddr = kernelBaseAddress + vmOffset;
1335 uint16_t diversity = fixupLoc->kernel64.diversity;
1336 bool hasAddrDiv = fixupLoc->kernel64.addrDiv;
1337 uint8_t key = fixupLoc->kernel64.key;
1338 bool hasPointerAuth = fixupLoc->kernel64.isAuth;
1339 existingCollectionFixupLocations[(const uint8_t*)fixupLoc] = { targetVMAddr, kernelLevel, diversity, hasAddrDiv, key, hasPointerAuth };
1340 });
1341 });
1342 }
1343
1344 // And add classic if we have them
1345 existingKernelCollection->forEachRebase(diags, ^(const char *opcodeName, const dyld3::MachOAnalyzer::LinkEditInfo &leInfo,
1346 const dyld3::MachOAnalyzer::SegmentInfo *segments,
1347 bool segIndexSet, uint32_t pointerSize, uint8_t segmentIndex,
1348 uint64_t segmentOffset, dyld3::MachOAnalyzer::Rebase kind, bool &stop) {
1349 uint64_t rebaseVmAddr = segments[segmentIndex].vmAddr + segmentOffset;
1350 uint64_t runtimeOffset = rebaseVmAddr - kernelBaseAddress;
1351 const uint8_t* fixupLoc = kernelBasePointer + runtimeOffset;
1352 uint64_t targetVMAddr = 0;
1353 if ( is64 ) {
1354 targetVMAddr = *(uint64_t*)fixupLoc;
1355 } else {
1356 targetVMAddr = *(uint32_t*)fixupLoc;
1357 }
1358 // Classic relocs have no pointer auth
1359 uint16_t diversity = 0;
1360 bool hasAddrDiv = false;
1361 uint8_t key = 0;
1362 bool hasPointerAuth = false;
1363 existingCollectionFixupLocations[(const uint8_t*)fixupLoc] = { targetVMAddr, kernelLevel, diversity, hasAddrDiv, key, hasPointerAuth };
1364 });
1365 }
1366
1367 // Add pageable fixup locations if we have it
1368 if ( pageableKernelCollection != nullptr ) {
1369 // We only have chained fixups here to add, but they are on each kext, not on the KC itself
1370 pageableKernelCollection->forEachDylib(diags, ^(const dyld3::MachOAnalyzer *ma, const char *name, bool &stop) {
1371 // Skip kexts without fixups
1372 if ( !ma->hasChainedFixupsLoadCommand() )
1373 return;
1374 ma->withChainStarts(diags, 0, ^(const dyld_chained_starts_in_image* starts) {
1375 ma->forEachFixupInAllChains(diags, starts, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
1376 uint64_t vmOffset = 0;
1377 bool isRebase = fixupLoc->isRebase(DYLD_CHAINED_PTR_64_KERNEL_CACHE, 0, vmOffset);
1378 assert(isRebase);
1379 uint8_t targetFixupLevel = fixupLoc->kernel64.cacheLevel;
1380 uint64_t targetVMAddr = collections[targetFixupLevel].baseAddress + vmOffset;
1381 uint16_t diversity = fixupLoc->kernel64.diversity;
1382 bool hasAddrDiv = fixupLoc->kernel64.addrDiv;
1383 uint8_t key = fixupLoc->kernel64.key;
1384 bool hasPointerAuth = fixupLoc->kernel64.isAuth;
1385 existingCollectionFixupLocations[(const uint8_t*)fixupLoc] = { targetVMAddr, targetFixupLevel, diversity, hasAddrDiv, key, hasPointerAuth };
1386 });
1387 });
1388 });
1389 }
1390 }
1391
1392 void VTablePatcher::findBaseKernelVTables(Diagnostics& diags, const dyld3::MachOAppCache* existingKernelCollection,
1393 std::map<std::string, DylibSymbols>& dylibsToSymbols)
1394 {
1395 const bool is64 = pointerSize == 8;
1396
1397 uint8_t kernelLevel = getFixupLevel(AppCacheBuilder::Options::AppCacheKind::kernel);
1398 uint64_t kernelBaseAddress = collections[kernelLevel].baseAddress;
1399 const uint8_t* kernelBasePointer = collections[kernelLevel].basePointer;
1400 uint16_t chainedPointerFormat = 0;
1401
1402 if ( existingKernelCollection->hasChainedFixupsLoadCommand() )
1403 chainedPointerFormat = existingKernelCollection->chainedPointerFormat();
1404
1405 // Map from dylibID to list of dependencies
1406 std::map<std::string, const std::vector<std::string>*> kextDependencies;
1407 for (VTableDylib& dylib : dylibs) {
1408 if ( dylib.cacheLevel != kernelLevel )
1409 continue;
1410 kextDependencies[dylib.dylibID] = &dylib.dependencies;
1411 }
1412
1413 bool kernelUsesClassicRelocs = existingKernelCollection->usesClassicRelocationsInKernelCollection();
1414 existingKernelCollection->forEachDylib(diags, ^(const dyld3::MachOAnalyzer *ma, const char *dylibID, bool &stop) {
1415 uint64_t loadAddress = ma->preferredLoadAddress();
1416
1417 auto visitBaseKernelCollectionSymbols = ^(const char *symbolName, uint64_t n_value) {
1418 if ( strstr(symbolName, superMetaclassPointerToken) == nullptr )
1419 return;
1420 uint8_t* fixupLoc = (uint8_t*)ma + (n_value - loadAddress);
1421 logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName, dylibID, fixupLoc);
1422
1423 // 2 - Derive the name of the class from the super MetaClass pointer.
1424 std::string_view className = extractString(symbolName, osObjPrefix, superMetaclassPointerToken);
1425 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1426 if ( className.empty() ) {
1427 logFunc("Unsupported vtable superclass name\n");
1428 return;
1429 }
1430 logFunc("Class name: '%s'\n", std::string(className).c_str());
1431
1432 // 3 - Derive the name of the class's vtable from the name of the class
1433 // We support namespaces too which means adding an N before the class name and E after
1434 std::string classVTableName = std::string(vtablePrefix) + std::string(className);
1435 logFunc("Class vtable name: '%s'\n", classVTableName.c_str());
1436
1437 uint64_t classVTableVMAddr = 0;
1438 const DylibSymbols& dylibSymbols = dylibsToSymbols[dylibID];
1439 {
1440 std::string namespacedVTableName;
1441 SymbolLocation symbolLocation = findVTablePatchingSymbol(classVTableName, dylibSymbols);
1442 if ( !symbolLocation.found() ) {
1443 // If we didn't find a name then try again with namespaces
1444 namespacedVTableName = std::string(vtablePrefix) + "N" + std::string(className) + "E";
1445 logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName.c_str());
1446 symbolLocation = findVTablePatchingSymbol(namespacedVTableName, dylibSymbols);
1447 }
1448 if ( symbolLocation.found() ) {
1449 classVTableVMAddr = symbolLocation.vmAddr;
1450 } else {
1451 diags.error("Class vtables '%s' or '%s' is not exported from '%s'",
1452 classVTableName.c_str(), namespacedVTableName.c_str(), dylibID);
1453 stop = true;
1454 return;
1455 }
1456 }
1457
1458 logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr);
1459 const uint8_t* classVTableLoc = kernelBasePointer + (classVTableVMAddr - kernelBaseAddress);
1460
1461 // 4 - Follow the super MetaClass pointer to get the address of the super MetaClass's symbol
1462 uint64_t superMetaclassSymbolAddress = 0;
1463 auto existingKernelCollectionFixupLocIt = existingCollectionFixupLocations.find(fixupLoc);
1464 if ( existingKernelCollectionFixupLocIt != existingCollectionFixupLocations.end() ) {
1465 if ( ma->isKextBundle() || !kernelUsesClassicRelocs ) {
1466 auto* chainedFixupLoc = (dyld3::MachOLoaded::ChainedFixupPointerOnDisk*)fixupLoc;
1467 uint64_t vmOffset = 0;
1468 bool isRebase = chainedFixupLoc->isRebase(chainedPointerFormat, kernelBaseAddress, vmOffset);
1469 assert(isRebase);
1470 superMetaclassSymbolAddress = kernelBaseAddress + vmOffset;
1471 } else {
1472 // The classic reloc is already the vmAddr so nothing special to do here.
1473 assert(is64);
1474 superMetaclassSymbolAddress = *(uint64_t*)fixupLoc;
1475 }
1476 }
1477
1478 logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress);
1479
1480 if ( superMetaclassSymbolAddress == 0 ) {
1481 if ( classVTableName == "__ZTV8OSObject" ) {
1482 // This is the base class of all objects, so it doesn't have a super class
1483 // We add it as a placeholder and set it to 'true' to show its already been processed
1484 VTable& vtable = vtables[classVTableLoc];
1485 vtable.ma = ma;
1486 vtable.dylib = &dylibSymbols;
1487 vtable.fromParentCollection = true;
1488 vtable.patched = true;
1489 vtable.name = classVTableName;
1490 return;
1491 }
1492 }
1493
1494 // 5 - Look up the super MetaClass symbol by address
1495 // FIXME: VTable patching the auxKC with the superclass in the baseKC
1496 uint8_t superclassFixupLevel = kernelLevel;
1497
1498 auto& metaclassDefinitions = collections[superclassFixupLevel].metaclassDefinitions;
1499 auto metaclassIt = metaclassDefinitions.find(superMetaclassSymbolAddress);
1500 if ( metaclassIt == metaclassDefinitions.end() ) {
1501 diags.error("Cannot find symbol for metaclass pointed to by '%s' in '%s'",
1502 symbolName, dylibID);
1503 stop = true;
1504 return;
1505 }
1506
1507 // 6 - Derive the super class's name from the super MetaClass name
1508 std::string_view superClassName = extractString(metaclassIt->second, osObjPrefix, metaclassToken);
1509 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1510 if ( superClassName.empty() ) {
1511 logFunc("Unsupported vtable superclass name\n");
1512 return;
1513 }
1514 logFunc("Superclass name: '%s'\n", std::string(superClassName).c_str());
1515
1516 // 7 - Derive the super class's vtable from the super class's name
1517 std::string superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
1518
1519 // We support namespaces, so first try the superclass without the namespace, then again with it
1520 const uint8_t* superclassVTableLoc = nullptr;
1521 for (unsigned i = 0; i != 2; ++i) {
1522 if ( i == 1 ) {
1523 superclassVTableName = std::string(vtablePrefix) + + "N" + std::string(superClassName) + "E";
1524 }
1525 logFunc("Superclass vtable name: '%s'\n", superclassVTableName.c_str());
1526
1527 if ( ma->isKextBundle() ) {
1528 // First check if the superclass vtable comes from a dependent kext
1529 auto it = kextDependencies.find(dylibID);
1530 assert(it != kextDependencies.end());
1531 const std::vector<std::string>& dependencies = *it->second;
1532 for (const std::string& dependencyID : dependencies) {
1533 auto depIt = dylibsToSymbols.find(dependencyID);
1534 if (depIt == dylibsToSymbols.end()) {
1535 diags.error("Failed to bind '%s' in '%s' as could not find a kext with '%s' bundle-id",
1536 symbolName, dylibID, dependencyID.c_str());
1537 stop = true;
1538 return;
1539 }
1540
1541 const DylibSymbols& dylibSymbols = depIt->second;
1542 SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
1543 if ( !symbolLocation.found() )
1544 continue;
1545
1546 uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
1547 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
1548 superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
1549 break;
1550 }
1551 }
1552 if ( superclassVTableLoc == nullptr ) {
1553 auto depIt = dylibsToSymbols.find(dylibID);
1554 if (depIt == dylibsToSymbols.end()) {
1555 diags.error("Failed to bind '%s' in '%s' as could not find a binary with '%s' bundle-id",
1556 symbolName, dylibID, dylibID);
1557 stop = true;
1558 return;
1559 }
1560
1561 const DylibSymbols& dylibSymbols = depIt->second;
1562 SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
1563 if ( symbolLocation.found() ) {
1564 uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
1565 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
1566 superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
1567 }
1568 }
1569
1570 if ( superclassVTableLoc != nullptr )
1571 break;
1572 }
1573
1574 if ( superclassVTableLoc == nullptr ) {
1575 superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
1576 diags.error("Superclass vtable '%s' is not exported from '%s' or its dependencies",
1577 superclassVTableName.c_str(), dylibID);
1578 stop = true;
1579 return;
1580 }
1581
1582 // Add an entry for this vtable
1583 VTable& vtable = vtables[classVTableLoc];
1584 vtable.superVTable = superclassVTableLoc;
1585 vtable.ma = ma;
1586 vtable.dylib = &dylibSymbols;
1587 vtable.fromParentCollection = true;
1588 vtable.patched = true;
1589 vtable.name = classVTableName;
1590
1591 // And an entry for the superclass vtable
1592 VTable& supervtable = vtables[superclassVTableLoc];
1593 supervtable.fromParentCollection = true;
1594 supervtable.patched = true;
1595 supervtable.name = superclassVTableName;
1596 };
1597
1598 ma->forEachGlobalSymbol(diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
1599 uint8_t n_sect, uint16_t n_desc, bool &stop) {
1600 visitBaseKernelCollectionSymbols(symbolName, n_value);
1601 });
1602
1603 if ( diags.hasError() ) {
1604 stop = true;
1605 return;
1606 }
1607
1608 ma->forEachLocalSymbol(diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
1609 uint8_t n_sect, uint16_t n_desc, bool &stop) {
1610 visitBaseKernelCollectionSymbols(symbolName, n_value);
1611 });
1612
1613 if ( diags.hasError() ) {
1614 stop = true;
1615 return;
1616 }
1617 });
1618 }
1619
1620 void VTablePatcher::findPageableKernelVTables(Diagnostics& diags, const dyld3::MachOAppCache* pageableKernelCollection,
1621 std::map<std::string, DylibSymbols>& dylibsToSymbols)
1622 {
1623 uint8_t collectionLevel = getFixupLevel(AppCacheBuilder::Options::AppCacheKind::pageableKC);
1624 uint64_t collectionBaseAddress = collections[collectionLevel].baseAddress;
1625 const uint8_t* collectionBasePointer = collections[collectionLevel].basePointer;
1626
1627 // Map from dylibID to list of dependencies
1628 std::map<std::string, const std::vector<std::string>*> kextDependencies;
1629 for (VTableDylib& dylib : dylibs) {
1630 if ( dylib.cacheLevel != collectionLevel )
1631 continue;
1632 kextDependencies[dylib.dylibID] = &dylib.dependencies;
1633 }
1634
1635 pageableKernelCollection->forEachDylib(diags, ^(const dyld3::MachOAnalyzer *ma, const char *dylibID, bool &stop) {
1636 uint64_t loadAddress = ma->preferredLoadAddress();
1637 auto visitPageableKernelCollectionSymbols = ^(const char *symbolName, uint64_t n_value) {
1638 if ( strstr(symbolName, superMetaclassPointerToken) == nullptr )
1639 return;
1640 uint8_t* fixupLoc = (uint8_t*)ma + (n_value - loadAddress);
1641 logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName, dylibID, fixupLoc);
1642
1643 // 2 - Derive the name of the class from the super MetaClass pointer.
1644 std::string_view className = extractString(symbolName, osObjPrefix, superMetaclassPointerToken);
1645 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1646 if ( className.empty() ) {
1647 logFunc("Unsupported vtable superclass name\n");
1648 return;
1649 }
1650 logFunc("Class name: '%s'\n", std::string(className).c_str());
1651
1652 // 3 - Derive the name of the class's vtable from the name of the class
1653 // We support namespaces too which means adding an N before the class name and E after
1654 std::string classVTableName = std::string(vtablePrefix) + std::string(className);
1655 logFunc("Class vtable name: '%s'\n", classVTableName.c_str());
1656
1657 uint64_t classVTableVMAddr = 0;
1658 const DylibSymbols& dylibSymbols = dylibsToSymbols[dylibID];
1659 {
1660 std::string namespacedVTableName;
1661 SymbolLocation symbolLocation = findVTablePatchingSymbol(classVTableName, dylibSymbols);
1662 if ( !symbolLocation.found() ) {
1663 // If we didn't find a name then try again with namespaces
1664 namespacedVTableName = std::string(vtablePrefix) + "N" + std::string(className) + "E";
1665 logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName.c_str());
1666 symbolLocation = findVTablePatchingSymbol(namespacedVTableName, dylibSymbols);
1667 }
1668 if ( symbolLocation.found() ) {
1669 classVTableVMAddr = symbolLocation.vmAddr;
1670 } else {
1671 diags.error("Class vtables '%s' or '%s' is not exported from '%s'",
1672 classVTableName.c_str(), namespacedVTableName.c_str(), dylibID);
1673 stop = true;
1674 return;
1675 }
1676 }
1677
1678 logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr);
1679 const uint8_t* classVTableLoc = collectionBasePointer + (classVTableVMAddr - collectionBaseAddress);
1680
1681 // 4 - Follow the super MetaClass pointer to get the address of the super MetaClass's symbol
1682 uint8_t superclassFixupLevel = (uint8_t)~0U;
1683 uint64_t superMetaclassSymbolAddress = 0;
1684 auto existingKernelCollectionFixupLocIt = existingCollectionFixupLocations.find(fixupLoc);
1685 if ( existingKernelCollectionFixupLocIt != existingCollectionFixupLocations.end() ) {
1686 auto* chainedFixupLoc = (dyld3::MachOLoaded::ChainedFixupPointerOnDisk*)fixupLoc;
1687 uint64_t vmOffset = 0;
1688 bool isRebase = chainedFixupLoc->isRebase(DYLD_CHAINED_PTR_64_KERNEL_CACHE, 0, vmOffset);
1689 assert(isRebase);
1690 // The superclass could be in the baseKC, while we are analysing the pageableKC, so we need to get the correct level
1691 // from the fixup
1692 superclassFixupLevel = chainedFixupLoc->kernel64.cacheLevel;
1693 superMetaclassSymbolAddress = collections[superclassFixupLevel].baseAddress + vmOffset;
1694 }
1695
1696 logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress);
1697
1698 if ( superMetaclassSymbolAddress == 0 ) {
1699 if ( classVTableName == "__ZTV8OSObject" ) {
1700 // This is the base class of all objects, so it doesn't have a super class
1701 // We add it as a placeholder and set it to 'true' to show its already been processed
1702 VTable& vtable = vtables[classVTableLoc];
1703 vtable.ma = ma;
1704 vtable.dylib = &dylibSymbols;
1705 vtable.fromParentCollection = true;
1706 vtable.patched = true;
1707 vtable.name = classVTableName;
1708 return;
1709 }
1710 }
1711
1712 // 5 - Look up the super MetaClass symbol by address
1713 auto& metaclassDefinitions = collections[superclassFixupLevel].metaclassDefinitions;
1714 auto metaclassIt = metaclassDefinitions.find(superMetaclassSymbolAddress);
1715 if ( metaclassIt == metaclassDefinitions.end() ) {
1716 diags.error("Cannot find symbol for metaclass pointed to by '%s' in '%s'",
1717 symbolName, dylibID);
1718 stop = true;
1719 return;
1720 }
1721
1722 // 6 - Derive the super class's name from the super MetaClass name
1723 std::string_view superClassName = extractString(metaclassIt->second, osObjPrefix, metaclassToken);
1724 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1725 if ( superClassName.empty() ) {
1726 logFunc("Unsupported vtable superclass name\n");
1727 return;
1728 }
1729 logFunc("Superclass name: '%s'\n", std::string(superClassName).c_str());
1730
1731 // 7 - Derive the super class's vtable from the super class's name
1732 std::string superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
1733
1734 // We support namespaces, so first try the superclass without the namespace, then again with it
1735 const uint8_t* superclassVTableLoc = nullptr;
1736 for (unsigned i = 0; i != 2; ++i) {
1737 if ( i == 1 ) {
1738 superclassVTableName = std::string(vtablePrefix) + + "N" + std::string(superClassName) + "E";
1739 }
1740 logFunc("Superclass vtable name: '%s'\n", superclassVTableName.c_str());
1741
1742 if ( ma->isKextBundle() ) {
1743 // First check if the superclass vtable comes from a dependent kext
1744 auto it = kextDependencies.find(dylibID);
1745 assert(it != kextDependencies.end());
1746 const std::vector<std::string>& dependencies = *it->second;
1747 for (const std::string& dependencyID : dependencies) {
1748 auto depIt = dylibsToSymbols.find(dependencyID);
1749 if (depIt == dylibsToSymbols.end()) {
1750 diags.error("Failed to bind '%s' in '%s' as could not find a kext with '%s' bundle-id",
1751 symbolName, dylibID, dependencyID.c_str());
1752 stop = true;
1753 return;
1754 }
1755
1756 const DylibSymbols& dylibSymbols = depIt->second;
1757 SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
1758 if ( !symbolLocation.found() )
1759 continue;
1760
1761 uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
1762 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
1763 superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
1764 break;
1765 }
1766 }
1767 if ( superclassVTableLoc == nullptr ) {
1768 auto depIt = dylibsToSymbols.find(dylibID);
1769 if (depIt == dylibsToSymbols.end()) {
1770 diags.error("Failed to bind '%s' in '%s' as could not find a binary with '%s' bundle-id",
1771 symbolName, dylibID, dylibID);
1772 stop = true;
1773 return;
1774 }
1775
1776 const DylibSymbols& dylibSymbols = depIt->second;
1777 SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
1778 if ( symbolLocation.found() ) {
1779 uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
1780 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
1781 superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
1782 }
1783 }
1784
1785 if ( superclassVTableLoc != nullptr )
1786 break;
1787 }
1788
1789 if ( superclassVTableLoc == nullptr ) {
1790 superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
1791 diags.error("Superclass vtable '%s' is not exported from '%s' or its dependencies",
1792 superclassVTableName.c_str(), dylibID);
1793 stop = true;
1794 return;
1795 }
1796
1797 // Add an entry for this vtable
1798 VTable& vtable = vtables[classVTableLoc];
1799 vtable.superVTable = superclassVTableLoc;
1800 vtable.ma = ma;
1801 vtable.dylib = &dylibSymbols;
1802 vtable.fromParentCollection = true;
1803 vtable.patched = true;
1804 vtable.name = classVTableName;
1805
1806 // And an entry for the superclass vtable
1807 VTable& supervtable = vtables[superclassVTableLoc];
1808 supervtable.fromParentCollection = true;
1809 supervtable.patched = true;
1810 supervtable.name = superclassVTableName;
1811 };
1812
1813 ma->forEachGlobalSymbol(diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
1814 uint8_t n_sect, uint16_t n_desc, bool &stop) {
1815 visitPageableKernelCollectionSymbols(symbolName, n_value);
1816 });
1817
1818 if ( diags.hasError() ) {
1819 stop = true;
1820 return;
1821 }
1822
1823 ma->forEachLocalSymbol(diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
1824 uint8_t n_sect, uint16_t n_desc, bool &stop) {
1825 visitPageableKernelCollectionSymbols(symbolName, n_value);
1826 });
1827
1828 if ( diags.hasError() ) {
1829 stop = true;
1830 return;
1831 }
1832 });
1833 }
1834
1835 void VTablePatcher::findVTables(uint8_t currentLevel, const dyld3::MachOAnalyzer* kernelMA,
1836 std::map<std::string, DylibSymbols>& dylibsToSymbols,
1837 const AppCacheBuilder::ASLR_Tracker& aslrTracker,
1838 const std::map<const uint8_t*, const VTableBindSymbol>& missingBindLocations)
1839 {
1840 const bool is64 = pointerSize == 8;
1841
1842 uint64_t collectionBaseAddress = collections[currentLevel].baseAddress;
1843 const uint8_t* collectionBasePointer = collections[currentLevel].basePointer;
1844
1845 // VTable patching algorithm (for each symbol...):
1846 // - To find the address of a class vtable:
1847 // - Take symbols with '10superClassE' in their name, eg, __ZN10IOMachPort10superClassE
1848 // - Work out the name of the class from that symbol name, eg, 10IOMachPort
1849 // - Work out the name of the VTable from that class name, eg, __ZTV10IOMachPort
1850 // - Find the address for the export with that vtable name
1851 // - To find the superclass for a given class
1852 // - Take the symbol with '10superClassE' in their name, eg, __ZN10IOMachPort10superClassE
1853 // - Take its address and dereference it as "__ZN10IOMachPort10superClassE = &__ZN8OSObject10gMetaClassE"
1854 // - Find the name of the symbol at this address, eg, work out we have a symbol called __ZN8OSObject10gMetaClassE
1855 // - Get the superclassic from that symbol name, eg, 8OSObject
1856 // - Get the VTable name from that symbol, eg, __ZTV8OSObject
1857 // - Find the superclass vtable address from that name by searching the image and dependents for __ZTV8OSObject
1858 for (VTableDylib& dylib : dylibs) {
1859 // Only process dylibs in the level we are building
1860 // Existing collections were handled elsewhere
1861 if ( dylib.cacheLevel != currentLevel )
1862 continue;
1863
1864 const dyld3::MachOAnalyzer* ma = dylib.ma;
1865 const std::string& dylibID = dylib.dylibID;
1866 Diagnostics& dylibDiags = *dylib.diags;
1867 const std::vector<std::string>& dependencies = dylib.dependencies;
1868
1869 uint64_t loadAddress = ma->preferredLoadAddress();
1870 bool alreadyPatched = (ma == kernelMA);
1871 auto visitSymbols = ^(const char *symbolName, uint64_t n_value) {
1872 if ( strstr(symbolName, superMetaclassPointerToken) == nullptr )
1873 return;
1874
1875 uint8_t* fixupLoc = (uint8_t*)ma + (n_value - loadAddress);
1876 logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName, dylibID.c_str(), fixupLoc);
1877
1878 // 2 - Derive the name of the class from the super MetaClass pointer.
1879 std::string_view className = extractString(symbolName, osObjPrefix, superMetaclassPointerToken);
1880 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1881 if ( className.empty() ) {
1882 logFunc("Unsupported vtable superclass name\n");
1883 return;
1884 }
1885 logFunc("Class name: '%s'\n", std::string(className).c_str());
1886
1887 // 3 - Derive the name of the class's vtable from the name of the class
1888 // We support namespaces too which means adding an N before the class name and E after
1889 std::string classVTableName = std::string(vtablePrefix) + std::string(className);
1890 logFunc("Class vtable name: '%s'\n", classVTableName.c_str());
1891
1892 uint64_t classVTableVMAddr = 0;
1893 const DylibSymbols& dylibSymbols = dylibsToSymbols[dylibID];
1894 {
1895 std::string namespacedVTableName;
1896 SymbolLocation symbolLocation = findVTablePatchingSymbol(classVTableName, dylibSymbols);
1897 if ( !symbolLocation.found() ) {
1898 // If we didn't find a name then try again with namespaces
1899 namespacedVTableName = std::string(vtablePrefix) + "N" + std::string(className) + "E";
1900 logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName.c_str());
1901 symbolLocation = findVTablePatchingSymbol(namespacedVTableName, dylibSymbols);
1902 }
1903 if ( symbolLocation.found() ) {
1904 classVTableVMAddr = symbolLocation.vmAddr;
1905 } else {
1906 dylibDiags.error("Class vtables '%s' or '%s' is not an exported symbol",
1907 classVTableName.c_str(), namespacedVTableName.c_str());
1908 return;
1909 }
1910 }
1911
1912 logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr);
1913 const uint8_t* classVTableLoc = (uint8_t*)ma + (classVTableVMAddr - loadAddress);
1914
1915 // 4 - Follow the super MetaClass pointer to get the address of the super MetaClass's symbol
1916 uint64_t superMetaclassSymbolAddress = 0;
1917 {
1918 uint32_t vmAddr32 = 0;
1919 uint64_t vmAddr64 = 0;
1920 if ( aslrTracker.hasRebaseTarget32(fixupLoc, &vmAddr32) ) {
1921 superMetaclassSymbolAddress = vmAddr32;
1922 } else if ( aslrTracker.hasRebaseTarget64(fixupLoc, &vmAddr64) ) {
1923 superMetaclassSymbolAddress = vmAddr64;
1924 } else {
1925 assert(is64);
1926 superMetaclassSymbolAddress = *(uint64_t*)fixupLoc;
1927 }
1928 uint8_t highByte = 0;
1929 if ( aslrTracker.hasHigh8(fixupLoc, &highByte) ) {
1930 uint64_t tbi = (uint64_t)highByte << 56;
1931 superMetaclassSymbolAddress |= tbi;
1932 }
1933 }
1934 logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress);
1935
1936 if ( superMetaclassSymbolAddress == 0 ) {
1937 if ( classVTableName == "__ZTV8OSObject" ) {
1938 // This is the base class of all objects, so it doesn't have a super class
1939 // We add it as a placeholder and set it to 'true' to show its already been processed
1940 VTable& vtable = vtables[classVTableLoc];
1941 vtable.ma = ma;
1942 vtable.dylib = &dylibSymbols;
1943 vtable.fromParentCollection = false;
1944 vtable.patched = true;
1945 vtable.name = classVTableName;
1946 return;
1947 }
1948 }
1949
1950 // 5 - Look up the super MetaClass symbol by address
1951 // FIXME: VTable patching the auxKC with the superclass in the baseKC
1952 uint8_t superclassFixupLevel = currentLevel;
1953 aslrTracker.has(fixupLoc, &superclassFixupLevel);
1954
1955 auto& metaclassDefinitions = collections[superclassFixupLevel].metaclassDefinitions;
1956 auto metaclassIt = metaclassDefinitions.find(superMetaclassSymbolAddress);
1957 if ( metaclassIt == metaclassDefinitions.end() ) {
1958 auto bindIt = missingBindLocations.find(fixupLoc);
1959 if ( bindIt != missingBindLocations.end() ) {
1960 dylibDiags.error("Cannot find symbol for metaclass pointed to by '%s'. "
1961 "Expected symbol '%s' to be defined in another kext",
1962 symbolName, bindIt->second.symbolName.c_str());
1963 } else {
1964 dylibDiags.error("Cannot find symbol for metaclass pointed to by '%s'",
1965 symbolName);
1966 }
1967 return;
1968 }
1969
1970 // 6 - Derive the super class's name from the super MetaClass name
1971 std::string_view superClassName = extractString(metaclassIt->second, osObjPrefix, metaclassToken);
1972 // If the string isn't prefixed/suffixed appropriately, then give up on this one
1973 if ( superClassName.empty() ) {
1974 logFunc("Unsupported vtable superclass name\n");
1975 return;
1976 }
1977 logFunc("Superclass name: '%s'\n", std::string(superClassName).c_str());
1978
1979 // 7 - Derive the super class's vtable from the super class's name
1980 std::string superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
1981
1982 // We support namespaces, so first try the superclass without the namespace, then again with it
1983 const uint8_t* superclassVTableLoc = nullptr;
1984 bool superVTableIsInParentCollection = false;
1985 for (unsigned i = 0; i != 2; ++i) {
1986 if ( i == 1 ) {
1987 superclassVTableName = std::string(vtablePrefix) + + "N" + std::string(superClassName) + "E";
1988 }
1989 logFunc("Superclass vtable name: '%s'\n", superclassVTableName.c_str());
1990
1991 {
1992 // First check if the superclass vtable comes from a dependent kext
1993 for (const std::string& dependencyID : dependencies) {
1994 auto depIt = dylibsToSymbols.find(dependencyID);
1995 if (depIt == dylibsToSymbols.end()) {
1996 dylibDiags.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
1997 symbolName, dependencyID.c_str());
1998 return;
1999 }
2000
2001 const DylibSymbols& dylibSymbols = depIt->second;
2002 SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
2003 if ( !symbolLocation.found() )
2004 continue;
2005
2006 uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
2007 logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
2008 superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
2009 superVTableIsInParentCollection = dylibSymbols.dylibLevel != currentLevel;
2010 break;
2011 }
2012
2013 if ( superclassVTableLoc == nullptr ) {
2014 SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
2015 if ( symbolLocation.found() ) {
2016 uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
2017 superclassVTableLoc = (uint8_t*)collectionBasePointer + (superclassVTableVMAddr - collectionBaseAddress);
2018 superVTableIsInParentCollection = false;
2019 }
2020 }
2021 }
2022
2023 if ( superclassVTableLoc != nullptr )
2024 break;
2025 }
2026
2027 if ( superclassVTableLoc == nullptr ) {
2028 superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
2029 dylibDiags.error("Superclass vtable '%s' is not exported from kext or its dependencies",
2030 superclassVTableName.c_str());
2031 return;
2032 }
2033
2034 // Add an entry for this vtable
2035 VTable& vtable = vtables[classVTableLoc];
2036 vtable.superVTable = superclassVTableLoc;
2037 vtable.ma = ma;
2038 vtable.dylib = &dylibSymbols;
2039 vtable.fromParentCollection = false;
2040 vtable.patched |= alreadyPatched;
2041 vtable.name = classVTableName;
2042
2043 // And an entry for the superclass vtable
2044 VTable& supervtable = vtables[superclassVTableLoc];
2045 supervtable.fromParentCollection = superVTableIsInParentCollection;
2046 supervtable.patched |= alreadyPatched;
2047 supervtable.name = superclassVTableName;
2048
2049 // Also calculate the metaclass vtable name so that we can patch it
2050 std::string metaclassVTableName = std::string(metaclassVTablePrefix) + std::string(className) + metaclassVTableSuffix;
2051 logFunc("Metaclass vtable name: '%s'\n", metaclassVTableName.c_str());
2052
2053 {
2054 // Note its safe to just ignore missing metaclass symbols if we can't find them
2055 // If the binary links then kxld would have let it run
2056 SymbolLocation symbolLocation = findVTablePatchingSymbol(metaclassVTableName, dylibSymbols);
2057 if ( symbolLocation.found() ) {
2058 uint64_t metaclassVTableVMAddr = symbolLocation.vmAddr;
2059
2060 logFunc("Metaclass vtable vmAddr: '0x%llx'\n", metaclassVTableVMAddr);
2061 uint8_t* metaclassVTableLoc = (uint8_t*)ma + (metaclassVTableVMAddr - loadAddress);
2062
2063 // Add an entry for this vtable
2064 VTable& vtable = vtables[metaclassVTableLoc];
2065 vtable.superVTable = baseMetaClassVTableLoc;
2066 vtable.ma = ma;
2067 vtable.dylib = &dylibSymbols;
2068 vtable.fromParentCollection = false;
2069 vtable.patched |= alreadyPatched;
2070 vtable.name = metaclassVTableName;
2071 }
2072 }
2073 };
2074
2075 ma->forEachGlobalSymbol(dylibDiags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
2076 uint8_t n_sect, uint16_t n_desc, bool &stop) {
2077 visitSymbols(symbolName, n_value);
2078 });
2079
2080 ma->forEachLocalSymbol(dylibDiags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
2081 uint8_t n_sect, uint16_t n_desc, bool &stop) {
2082 visitSymbols(symbolName, n_value);
2083 });
2084 }
2085 }
2086
2087 void VTablePatcher::calculateSymbols() {
2088 for (VTableDylib& dylib : dylibs) {
2089 auto& symbolNames = collections[dylib.cacheLevel].symbolNames;
2090 dylib.ma->forEachGlobalSymbol(*dylib.diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
2091 uint8_t n_sect, uint16_t n_desc, bool &stop) {
2092 symbolNames[n_value] = symbolName;
2093 });
2094 dylib.ma->forEachLocalSymbol(*dylib.diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
2095 uint8_t n_sect, uint16_t n_desc, bool &stop) {
2096 symbolNames[n_value] = symbolName;
2097 });
2098 }
2099 }
2100
2101 void VTablePatcher::patchVTables(Diagnostics& diags,
2102 std::map<const uint8_t*, const VTableBindSymbol>& missingBindLocations,
2103 AppCacheBuilder::ASLR_Tracker& aslrTracker,
2104 uint8_t currentLevel)
2105 {
2106 const bool is64 = pointerSize == 8;
2107
2108 // If we have vtables to patch, then make sure we found the OSMetaClass symbol to patch against
2109 if ( (baseMetaClassVTableLoc == nullptr) && !vtables.empty() ) {
2110 diags.error("Could not find OSMetaClass vtable in kernel binary");
2111 return;
2112 }
2113
2114 calculateSymbols();
2115
2116 auto calculateVTableEntries = ^(const uint8_t* vtableLoc, VTable& vtable) {
2117 assert(vtable.patched);
2118 logFunc("Calculating vtable: '%s'\n", vtable.name.c_str());
2119
2120 // The first entry we want to patch is 2 pointers from the start of the vtable
2121 const uint8_t* relocLoc = vtableLoc + (2 * pointerSize);
2122
2123 if ( vtable.fromParentCollection ) {
2124 auto it = existingCollectionFixupLocations.find(relocLoc);
2125 while ( it != existingCollectionFixupLocations.end() ) {
2126 const Fixup& fixup = it->second;
2127 uint64_t targetVMAddr = fixup.targetVMAddr;
2128 uint16_t diversity = fixup.diversity;
2129 bool hasAddrDiv = fixup.hasAddrDiv;
2130 uint8_t key = fixup.key;
2131 bool hasPointerAuth = fixup.hasPointerAuth;
2132 uint32_t cacheLevel = fixup.cacheLevel;
2133 vtable.entries.push_back({ relocLoc, targetVMAddr, cacheLevel, diversity, hasAddrDiv, key, hasPointerAuth });
2134 relocLoc += pointerSize;
2135 it = existingCollectionFixupLocations.find(relocLoc);
2136 }
2137 } else {
2138 while ( aslrTracker.has((void*)relocLoc) ||
2139 (missingBindLocations.find(relocLoc) != missingBindLocations.end()) ) {
2140
2141 uint16_t diversity = 0;
2142 bool hasAddrDiv = false;
2143 uint8_t key = 0;
2144 bool hasPointerAuth = false;
2145 uint8_t cacheLevel = currentLevel;
2146
2147 if ( aslrTracker.has((void*)relocLoc, &cacheLevel) ) {
2148 hasPointerAuth = aslrTracker.hasAuthData((void*)relocLoc, &diversity, &hasAddrDiv, &key);
2149 }
2150
2151 uint64_t targetVMAddr = 0;
2152 {
2153 uint32_t vmAddr32 = 0;
2154 uint64_t vmAddr64 = 0;
2155 if ( aslrTracker.hasRebaseTarget32((void*)relocLoc, &vmAddr32) ) {
2156 targetVMAddr = vmAddr32;
2157 } else if ( aslrTracker.hasRebaseTarget64((void*)relocLoc, &vmAddr64) ) {
2158 targetVMAddr = vmAddr64;
2159 } else {
2160 assert(is64);
2161 targetVMAddr = *(uint64_t*)relocLoc;
2162 }
2163 uint8_t highByte = 0;
2164 if ( aslrTracker.hasHigh8((void*)relocLoc, &highByte) ) {
2165 uint64_t tbi = (uint64_t)highByte << 56;
2166 targetVMAddr |= tbi;
2167 }
2168 }
2169
2170 vtable.entries.push_back({ relocLoc, targetVMAddr, cacheLevel, diversity, hasAddrDiv, key, hasPointerAuth });
2171 relocLoc += pointerSize;
2172 }
2173 }
2174
2175 logFunc("Found %d vtable items: '%s'\n", vtable.entries.size(), vtable.name.c_str());
2176 };
2177
2178 // Map from MachO to diagnostics to emit for that file
2179 std::unordered_map<const dyld3::MachOAnalyzer*, Diagnostics*> diagsMap;
2180 for (VTableDylib& dylib : dylibs)
2181 diagsMap[dylib.ma] = dylib.diags;
2182
2183 uint32_t numPatchedVTables = 0;
2184 for (auto& vtableEntry : vtables) {
2185 if ( vtableEntry.second.patched ) {
2186 calculateVTableEntries(vtableEntry.first, vtableEntry.second);
2187 ++numPatchedVTables;
2188 }
2189 }
2190 while ( numPatchedVTables != vtables.size() ) {
2191 typedef std::pair<const uint8_t*, VTable*> VTableEntry;
2192 std::vector<VTableEntry> toBePatched;
2193 for (auto& vtableEntry : vtables) {
2194 if ( vtableEntry.second.patched )
2195 continue;
2196 auto superIt = vtables.find(vtableEntry.second.superVTable);
2197 assert(superIt != vtables.end());
2198 if ( !superIt->second.patched )
2199 continue;
2200 logFunc("Found unpatched vtable: '%s' with patched superclass '%s'\n",
2201 vtableEntry.second.name.c_str(), superIt->second.name.c_str());
2202 toBePatched.push_back({ vtableEntry.first, &vtableEntry.second });
2203 }
2204
2205 if ( toBePatched.empty() ) {
2206 // If we can't find anything to patch, then print out what we have left
2207 for (const auto& vtableEntry : vtables) {
2208 if ( vtableEntry.second.patched )
2209 continue;
2210 auto superIt = vtables.find(vtableEntry.second.superVTable);
2211 assert(superIt != vtables.end());
2212 diags.error("Found unpatched vtable: '%s' with unpatched superclass '%s'\n",
2213 vtableEntry.second.name.c_str(), superIt->second.name.c_str());
2214 }
2215 break;
2216 }
2217
2218 for (VTableEntry& vtableEntry : toBePatched) {
2219 VTable& vtable = *vtableEntry.second;
2220
2221 // We can immediately mark this as patched as then calculateVTableEntries can make
2222 // sure we never ask for vtables which aren't ready yet
2223 vtable.patched = true;
2224 ++numPatchedVTables;
2225
2226 auto superIt = vtables.find(vtable.superVTable);
2227 logFunc("Processing unpatched vtable: '%s' with patched superclass '%s'\n",
2228 vtable.name.c_str(), superIt->second.name.c_str());
2229
2230 calculateVTableEntries(vtableEntry.first, vtable);
2231
2232 const VTable& supervtable = superIt->second;
2233 if ( vtable.entries.size() < supervtable.entries.size() ) {
2234 // Try emit the error to a per dylib diagnostic object if we can find one
2235 auto diagIt = diagsMap.find(vtable.ma);
2236 Diagnostics* diag = (diagIt != diagsMap.end()) ? diagIt->second : &diags;
2237 diag->error("Malformed vtable. Super class '%s' has %lu entries vs subclass '%s' with %lu entries",
2238 supervtable.name.c_str(), supervtable.entries.size(),
2239 vtable.name.c_str(), vtable.entries.size());
2240 return;
2241 }
2242
2243 const std::unordered_map<const uint8_t*, VTableBindSymbol>& resolvedBindLocations = vtable.dylib->resolvedBindLocations;
2244 for (uint64_t entryIndex = 0; entryIndex != supervtable.entries.size(); ++entryIndex) {
2245 logFuncVerbose("Processing entry %lld: super[0x%llx] vs subclass[0x%llx]\n", entryIndex,
2246 *(uint64_t*)supervtable.entries[entryIndex].location,
2247 *(uint64_t*)vtable.entries[entryIndex].location);
2248
2249 VTable::Entry& vtableEntry = vtable.entries[entryIndex];
2250 const VTable::Entry& superVTableEntry = supervtable.entries[entryIndex];
2251
2252 const uint8_t* patchLoc = vtableEntry.location;
2253 uint64_t targetVMAddr = superVTableEntry.targetVMAddr;
2254
2255 // 1) If the symbol is defined locally, do not patch
2256 // This corresponds to a rebase not a bind, so if we have a match in our bind set
2257 // we were bound to another image, and should see if that bind should be overridden by a
2258 // better vtable patch.
2259 auto resolvedBindIt = resolvedBindLocations.find(patchLoc);
2260 auto unresolvedBindIt = missingBindLocations.find(patchLoc);
2261 if ( (resolvedBindIt == resolvedBindLocations.end()) && (unresolvedBindIt == missingBindLocations.end()) )
2262 continue;
2263
2264 // Find the child and parent symbols, if any
2265 const char* childSymbolName = nullptr;
2266 const char* parentSymbolName = nullptr;
2267
2268 if ( resolvedBindIt != resolvedBindLocations.end() ) {
2269 childSymbolName = resolvedBindIt->second.symbolName.c_str();
2270 } else {
2271 assert(unresolvedBindIt != missingBindLocations.end());
2272 childSymbolName = unresolvedBindIt->second.symbolName.c_str();
2273 }
2274
2275 auto& symbolNames = collections[superVTableEntry.targetCacheLevel].symbolNames;
2276 auto parentNameIt = symbolNames.find(superVTableEntry.targetVMAddr);
2277 if ( parentNameIt != symbolNames.end() )
2278 parentSymbolName = parentNameIt->second;
2279
2280 // The child entry can be NULL when a locally-defined, non-external
2281 // symbol is stripped. We wouldn't patch this entry anyway, so we just skip it.
2282 if ( childSymbolName == nullptr ) {
2283 continue;
2284 }
2285
2286 // It's possible for the patched parent entry not to have a symbol
2287 // (e.g. when the definition is inlined). We can't patch this entry no
2288 // matter what, so we'll just skip it and die later if it's a problem
2289 // (which is not likely).
2290 if ( parentSymbolName == nullptr ) {
2291 continue;
2292 }
2293
2294 logFuncVerbose("Processing entry %lld: super[%s] vs subclass[%s]\n", entryIndex,
2295 parentSymbolName, childSymbolName);
2296
2297 // 2) If the child is a pure virtual function, do not patch.
2298 // In general, we want to proceed with patching when the symbol is
2299 // externally defined because pad slots fall into this category.
2300 // The pure virtual function symbol is special case, as the pure
2301 // virtual property itself overrides the parent's implementation.
2302 if ( !strcmp(childSymbolName, "___cxa_pure_virtual") ) {
2303 continue;
2304 }
2305
2306 // 3) If the symbols are the same, do not patch
2307 // Note that if the symbol was a missing bind, then we'll still patch
2308 // This is the case where the vtable entry itself was a local symbol
2309 // so we had originally failed to bind to it as it wasn't exported, but it
2310 // has the same name as the parent name
2311 if ( !strcmp(childSymbolName, parentSymbolName) && (unresolvedBindIt == missingBindLocations.end()) ) {
2312 continue;
2313 }
2314
2315 #if 0
2316 // FIXME: Implement this
2317
2318 // 4) If the parent vtable entry is a pad slot, and the child does not
2319 // match it, then the child was built against a newer version of the
2320 // libraries, so it is binary-incompatible.
2321 require_action(!kxld_sym_name_is_padslot(parent_entry->patched.name),
2322 finish, rval = KERN_FAILURE;
2323 kxld_log(kKxldLogPatching, kKxldLogErr,
2324 kKxldLogParentOutOfDate,
2325 kxld_demangle(super_vtable->name, &demangled_name1,
2326 &demangled_length1),
2327 kxld_demangle(vtable->name, &demangled_name2,
2328 &demangled_length2)));
2329 #endif
2330
2331 logFunc("Patching entry '%s' in '%s' to point to '%s' in superclass '%s'\n",
2332 childSymbolName, vtable.name.c_str(), parentSymbolName, supervtable.name.c_str());
2333
2334 if ( is64 ) {
2335 *((uint64_t*)patchLoc) = targetVMAddr;
2336 } else {
2337 *((uint32_t*)patchLoc) = (uint32_t)targetVMAddr;
2338 }
2339
2340 // FIXME: When we support a baseKC, pageableKC, and auxKC, the supervtable cache level
2341 // may no longer be correct here as we may be:
2342 // - patching a vtable in auxKC
2343 // - where the supervtable is in pageableKC
2344 // - but the entry slot points to baseKC
2345 aslrTracker.add((void*)patchLoc, superVTableEntry.targetCacheLevel);
2346
2347 // Add pointer auth if the super vtable had it
2348 if ( superVTableEntry.hasPointerAuth )
2349 aslrTracker.setAuthData((void*)patchLoc, superVTableEntry.diversity,
2350 superVTableEntry.hasAddrDiv, superVTableEntry.key);
2351
2352 // Update this vtable entry in case there are any subclasses which then need to use it
2353 // to be patched themselves
2354 vtableEntry.targetVMAddr = superVTableEntry.targetVMAddr;
2355 vtableEntry.targetCacheLevel = superVTableEntry.targetCacheLevel;
2356 vtableEntry.diversity = superVTableEntry.diversity;
2357 vtableEntry.hasAddrDiv = superVTableEntry.hasAddrDiv;
2358 vtableEntry.key = superVTableEntry.key;
2359 vtableEntry.hasPointerAuth = superVTableEntry.hasPointerAuth;
2360
2361 missingBindLocations.erase(patchLoc);
2362 }
2363 }
2364 }
2365 }
2366
2367 typedef std::pair<uint8_t, uint64_t> CacheOffset;
2368
2369 struct DylibSymbolLocation {
2370 const DylibSymbols* dylibSymbols;
2371 uint64_t symbolVMAddr;
2372 bool isKPI;
2373 };
2374
2375 struct DylibFixups {
2376 void processFixups(const std::map<std::string, DylibSymbols>& dylibsToSymbols,
2377 const std::unordered_map<std::string_view, std::vector<DylibSymbolLocation>>& symbolMap,
2378 const std::string& kernelID, const CacheBuilder::ASLR_Tracker& aslrTracker);
2379
2380 // Inputs
2381 const dyld3::MachOAnalyzer* ma = nullptr;
2382 DylibSymbols& dylibSymbols;
2383 Diagnostics& dylibDiag;
2384 const std::vector<std::string>& dependencies;
2385
2386 // Outputs
2387 struct AuthData {
2388 uint16_t diversity;
2389 bool addrDiv;
2390 uint8_t key;
2391 };
2392 struct BranchStubData {
2393 CacheOffset targetCacheOffset;
2394 const void* fixupLoc;
2395 uint64_t fixupVMOffset;
2396 };
2397 std::unordered_map<const uint8_t*, VTableBindSymbol> missingBindLocations;
2398 std::unordered_map<void*, uint8_t> fixupLocs;
2399 std::unordered_map<void*, uint8_t> fixupHigh8s;
2400 std::unordered_map<void*, AuthData> fixupAuths;
2401 std::vector<BranchStubData> branchStubs;
2402 };
2403
2404 void DylibFixups::processFixups(const std::map<std::string, DylibSymbols>& dylibsToSymbols,
2405 const std::unordered_map<std::string_view, std::vector<DylibSymbolLocation>>& symbolMap,
2406 const std::string& kernelID, const CacheBuilder::ASLR_Tracker& aslrTracker) {
2407 auto& resolvedBindLocations = dylibSymbols.resolvedBindLocations;
2408 const std::string& dylibID = dylibSymbols.dylibName;
2409
2410 const bool _is64 = true;
2411 const bool isThirdPartyKext = (dylibID.find("com.apple") != 0);
2412
2413 // The magic symbol for missing weak imports
2414 const char* missingWeakImportSymbolName = "_gOSKextUnresolved";
2415
2416 struct SymbolDefinition {
2417 uint64_t symbolVMAddr;
2418 uint32_t kernelCollectionLevel;
2419 };
2420 auto findDependencyWithSymbol = [&symbolMap, &isThirdPartyKext](const char* symbolName,
2421 const std::vector<std::string>& dependencies) {
2422 auto symbolMapIt = symbolMap.find(symbolName);
2423 if ( symbolMapIt == symbolMap.end() )
2424 return (SymbolDefinition){ ~0ULL, 0 };
2425 // Find the first dependency in the list
2426 const std::vector<DylibSymbolLocation>& dylibSymbols = symbolMapIt->second;
2427 // The massively common case is 1 or 2 definitions of a given symbol, so a basic searhc should be
2428 // fine
2429 for (const std::string& dependency : dependencies) {
2430 for (const DylibSymbolLocation& dylibSymbol : dylibSymbols) {
2431 if ( dependency == dylibSymbol.dylibSymbols->dylibName ) {
2432 // If the Apple kext we are linking has a symbol set, and the user is a third-party kext,
2433 // then only allow the third party kext to see symbols in the kext export list, if it has one
2434 const bool isAppleKext = (dependency.find("com.apple") == 0);
2435 if ( isThirdPartyKext && isAppleKext && !dylibSymbol.isKPI )
2436 continue;
2437 return (SymbolDefinition){ dylibSymbol.symbolVMAddr, dylibSymbol.dylibSymbols->dylibLevel };
2438 }
2439 }
2440 }
2441 return (SymbolDefinition){ ~0ULL, 0 };
2442 };
2443
2444 if (ma->hasChainedFixups()) {
2445 // build array of targets
2446 struct BindTarget {
2447 const VTableBindSymbol bindSymbol;
2448 uint64_t vmAddr;
2449 uint32_t dylibLevel;
2450 bool isMissingWeakImport;
2451 bool isMissingSymbol;
2452 };
2453 __block std::vector<BindTarget> bindTargets;
2454 __block bool foundMissingWeakImport = false;
2455 ma->forEachChainedFixupTarget(dylibDiag, ^(int libOrdinal, const char* symbolName, uint64_t addend,
2456 bool weakImport, bool& stop) {
2457 if ( (libOrdinal != BIND_SPECIAL_DYLIB_FLAT_LOOKUP) && (libOrdinal != BIND_SPECIAL_DYLIB_WEAK_LOOKUP) ) {
2458 dylibDiag.error("All chained binds should be flat namespace or weak lookups");
2459 stop = true;
2460 return;
2461 }
2462
2463 if ( addend != 0 ) {
2464 dylibDiag.error("Chained bind addends are not supported right now");
2465 stop = true;
2466 return;
2467 }
2468
2469 VTableBindSymbol bindSymbol = { dylibID, symbolName };
2470 bool isMissingSymbol = false;
2471
2472 for (const std::string& dependencyID : dependencies) {
2473 auto depIt = dylibsToSymbols.find(dependencyID);
2474 if (depIt == dylibsToSymbols.end()) {
2475 dylibDiag.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
2476 symbolName, dependencyID.c_str());
2477 stop = true;
2478 return;
2479 }
2480
2481 const DylibSymbols& dylibSymbols = depIt->second;
2482 auto exportIt = dylibSymbols.globals.find(symbolName);
2483 if ( exportIt == dylibSymbols.globals.end() )
2484 continue;
2485
2486 isMissingSymbol = false;
2487 bindTargets.push_back({ bindSymbol, exportIt->second, dylibSymbols.dylibLevel, false, isMissingSymbol });
2488 return;
2489 }
2490
2491 // If the symbol is weak, and we didn't find it in our listed
2492 // dependencies, then use our own definition
2493 if ( libOrdinal == BIND_SPECIAL_DYLIB_WEAK_LOOKUP ) {
2494 auto dylibIt = dylibsToSymbols.find(dylibID);
2495 if (dylibIt == dylibsToSymbols.end()) {
2496 dylibDiag.error("Failed to bind weak '%s' as could not find a define in self",
2497 symbolName);
2498 stop = true;
2499 return;
2500 }
2501 const DylibSymbols& dylibSymbols = dylibIt->second;
2502 auto exportIt = dylibSymbols.globals.find(symbolName);
2503 if ( exportIt != dylibSymbols.globals.end() ) {
2504 isMissingSymbol = false;
2505 bindTargets.push_back({ bindSymbol, exportIt->second, dylibSymbols.dylibLevel, false, isMissingSymbol });
2506 return;
2507 }
2508 }
2509
2510 if ( weakImport ) {
2511 // Find _gOSKextUnresolved in the kernel
2512 // Weak imports are not compared against null, but instead against the address of that symbol
2513 auto kernelSymbolsIt = dylibsToSymbols.find(kernelID);
2514 assert(kernelSymbolsIt != dylibsToSymbols.end());
2515 const DylibSymbols& kernelSymbols = kernelSymbolsIt->second;
2516 auto exportIt = kernelSymbols.globals.find(missingWeakImportSymbolName);
2517 if (exportIt != kernelSymbols.globals.end()) {
2518 foundMissingWeakImport = true;
2519 isMissingSymbol = false;
2520 bindTargets.push_back({ bindSymbol, exportIt->second, kernelSymbols.dylibLevel, true, isMissingSymbol });
2521 return;
2522 }
2523 dylibDiag.error("Weak bind symbol '%s' not found in kernel", missingWeakImportSymbolName);
2524 return;
2525 }
2526
2527 // Store missing binds for later. They may be fixed by vtable patching
2528 isMissingSymbol = true;
2529 bindTargets.push_back({ bindSymbol, 0, 0, false, isMissingSymbol });
2530 });
2531 if ( dylibDiag.hasError() )
2532 return;
2533
2534 if( foundMissingWeakImport ) {
2535 // If we found a missing weak import, then we need to check that the user did
2536 // something like "if ( &foo == &gOSKextUnresolved )"
2537 // If they didn't use gOSKextUnresolved at all, then there's no way they could be doing that check
2538 auto kernelSymbolsIt = dylibsToSymbols.find(kernelID);
2539 assert(kernelSymbolsIt != dylibsToSymbols.end());
2540 const DylibSymbols& kernelSymbols = kernelSymbolsIt->second;
2541 auto exportIt = kernelSymbols.globals.find(missingWeakImportSymbolName);
2542 assert(exportIt != kernelSymbols.globals.end());
2543 bool foundUseOfMagicSymbol = false;
2544 for (const BindTarget& bindTarget : bindTargets) {
2545 // Skip the missing weak imports
2546 if ( bindTarget.isMissingWeakImport || bindTarget.isMissingSymbol )
2547 continue;
2548 // Skip anything which isn't the symbol we are looking for
2549 if ( (bindTarget.dylibLevel != 0) && (bindTarget.vmAddr != exportIt->second) )
2550 continue;
2551 foundUseOfMagicSymbol = true;
2552 break;
2553 }
2554
2555 if ( !foundUseOfMagicSymbol ) {
2556 dylibDiag.error("Has weak references but does not test for them. "
2557 "Test for weak references with OSKextSymbolIsResolved().");
2558 return;
2559 }
2560 }
2561
2562 // uint64_t baseAddress = ma->preferredLoadAddress();
2563
2564 ma->withChainStarts(dylibDiag, 0, ^(const dyld_chained_starts_in_image* starts) {
2565 ma->forEachFixupInAllChains(dylibDiag, starts, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
2566 switch (segInfo->pointer_format) {
2567 case DYLD_CHAINED_PTR_64_OFFSET:
2568 if ( fixupLoc->generic64.bind.bind ) {
2569 uint64_t bindOrdinal = fixupLoc->generic64.bind.ordinal;
2570 if ( bindOrdinal >= bindTargets.size() ) {
2571 dylibDiag.error("Bind ordinal %lld out of range %lu", bindOrdinal, bindTargets.size());
2572 stop = true;
2573 return;
2574 }
2575
2576 const BindTarget& bindTarget = bindTargets[bindOrdinal];
2577 if ( bindTarget.isMissingSymbol ) {
2578 // Track this missing bind for later
2579 // For now we bind it to null and don't slide it.
2580 fixupLoc->raw64 = 0;
2581 missingBindLocations[(const uint8_t*)fixupLoc] = bindTarget.bindSymbol;
2582 } else {
2583 fixupLoc->raw64 = bindTarget.vmAddr;
2584 fixupLocs[fixupLoc] = bindTarget.dylibLevel;
2585 resolvedBindLocations[(const uint8_t*)fixupLoc] = bindTarget.bindSymbol;
2586 }
2587 }
2588 else {
2589 // convert rebase chain entry to raw pointer to target vmaddr
2590 uint64_t targetVMAddr = fixupLoc->generic64.rebase.target;
2591 uint64_t sideTableAddr = 0;
2592 if ( aslrTracker.hasRebaseTarget64(fixupLoc, &sideTableAddr) )
2593 targetVMAddr = sideTableAddr;
2594 // store high8 in side table
2595 if ( fixupLoc->generic64.rebase.high8 )
2596 fixupHigh8s[fixupLoc] = fixupLoc->generic64.rebase.high8;
2597 fixupLoc->raw64 = targetVMAddr;
2598 }
2599 break;
2600 case DYLD_CHAINED_PTR_ARM64E_KERNEL:
2601 if ( fixupLoc->arm64e.bind.bind ) {
2602 uint64_t bindOrdinal = fixupLoc->arm64e.bind.ordinal;
2603 if ( bindOrdinal >= bindTargets.size() ) {
2604 dylibDiag.error("Bind ordinal %lld out of range %lu", bindOrdinal, bindTargets.size());
2605 stop = true;
2606 return;
2607 }
2608
2609 const BindTarget& bindTarget = bindTargets[bindOrdinal];
2610 uint64_t targetVMAddr = bindTarget.vmAddr;
2611
2612 if ( fixupLoc->arm64e.authBind.auth ) {
2613 // store auth data in side table
2614 fixupAuths[fixupLoc] = {
2615 (uint16_t)fixupLoc->arm64e.authBind.diversity,
2616 (bool)fixupLoc->arm64e.authBind.addrDiv,
2617 (uint8_t)fixupLoc->arm64e.authBind.key
2618 };
2619 }
2620 else {
2621 // plain binds can have addend in chain
2622 targetVMAddr += fixupLoc->arm64e.bind.addend;
2623 }
2624 // change location from a chain ptr into a raw pointer to the target vmaddr
2625 if ( bindTarget.isMissingSymbol ) {
2626 // Track this missing bind for later
2627 // For now we bind it to null and don't slide it.
2628 fixupLoc->raw64 = 0;
2629 missingBindLocations[(const uint8_t*)fixupLoc] = bindTarget.bindSymbol;
2630 } else {
2631 fixupLoc->raw64 = targetVMAddr;
2632 fixupLocs[fixupLoc] = bindTarget.dylibLevel;
2633 resolvedBindLocations[(const uint8_t*)fixupLoc] = bindTarget.bindSymbol;
2634 }
2635 }
2636 else {
2637 // convert rebase chain entry to raw pointer to target vmaddr
2638 if ( fixupLoc->arm64e.rebase.auth ) {
2639 // store auth data in side table
2640 fixupAuths[fixupLoc] = {
2641 (uint16_t)fixupLoc->arm64e.authRebase.diversity,
2642 (bool)fixupLoc->arm64e.authRebase.addrDiv,
2643 (uint8_t)fixupLoc->arm64e.authRebase.key
2644 };
2645 uint64_t targetVMAddr = fixupLoc->arm64e.authRebase.target;
2646 fixupLoc->raw64 = targetVMAddr;
2647 }
2648 else {
2649 uint64_t targetVMAddr = fixupLoc->arm64e.rebase.target;
2650 uint64_t sideTableAddr;
2651 if ( aslrTracker.hasRebaseTarget64(fixupLoc, &sideTableAddr) )
2652 targetVMAddr = sideTableAddr;
2653 // store high8 in side table
2654 if ( fixupLoc->arm64e.rebase.high8 )
2655 fixupHigh8s[fixupLoc] = fixupLoc->arm64e.rebase.high8;
2656 fixupLoc->raw64 = targetVMAddr;
2657 }
2658 }
2659 break;
2660 default:
2661 fprintf(stderr, "unknown pointer type %d\n", segInfo->pointer_format);
2662 break;
2663 }
2664 });
2665 });
2666 return;
2667 }
2668
2669 // If we have any missing imports, then they should check for the kernel symbol
2670 // Grab a hold of that now if it exists so we can check it later
2671 __block bool foundUseOfMagicSymbol = false;
2672 __block bool foundMissingWeakImport = false;
2673
2674 const uint64_t loadAddress = ma->preferredLoadAddress();
2675 ma->forEachBind(dylibDiag, ^(uint64_t runtimeOffset, int libOrdinal, uint8_t bindType,
2676 const char *symbolName, bool weakImport, bool lazyBind, uint64_t addend, bool &stop) {
2677 // printf("Bind at 0x%llx to '%s'\n", runtimeOffset, symbolName);
2678 // Kext binds are a flat namespace so walk until we find the symbol we need
2679 bool foundSymbol = false;
2680 VTableBindSymbol bindSymbol = { dylibID, symbolName };
2681 if (SymbolDefinition symbolDef = findDependencyWithSymbol(symbolName, dependencies); symbolDef.symbolVMAddr != ~0ULL) {
2682 // Set the bind to the target address since we found it
2683 uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
2684 if ( bindType == BIND_TYPE_POINTER ) {
2685 if ( _is64 )
2686 *((uint64_t*)fixupLoc) = symbolDef.symbolVMAddr;
2687 else
2688 *((uint32_t*)fixupLoc) = (uint32_t)symbolDef.symbolVMAddr;
2689
2690 // Only track regular fixups for ASLR, not branch fixups
2691 fixupLocs[fixupLoc] = symbolDef.kernelCollectionLevel;
2692 resolvedBindLocations[(const uint8_t*)fixupLoc] = bindSymbol;
2693 } else if ( bindType == BIND_TYPE_TEXT_PCREL32 ) {
2694 // The value to store is the difference between the bind target
2695 // and the value of the PC after this instruction
2696 uint64_t targetAddress = 0;
2697 if ( dylibSymbols.dylibLevel != symbolDef.kernelCollectionLevel ) {
2698 // Record this for later as we want to create stubs serially
2699 CacheOffset targetCacheOffset = { symbolDef.kernelCollectionLevel, symbolDef.symbolVMAddr };
2700 branchStubs.emplace_back((BranchStubData){
2701 .targetCacheOffset = targetCacheOffset,
2702 .fixupLoc = fixupLoc,
2703 .fixupVMOffset = runtimeOffset
2704 });
2705 } else {
2706 targetAddress = symbolDef.symbolVMAddr;
2707 uint64_t diffValue = targetAddress - (loadAddress + runtimeOffset + 4);
2708 *((uint32_t*)fixupLoc) = (uint32_t)diffValue;
2709 }
2710 } else {
2711 dylibDiag.error("Unexpected bind type: %d", bindType);
2712 stop = true;
2713 return;
2714 }
2715
2716 foundSymbol = true;
2717 }
2718
2719 if ( foundSymbol && !foundUseOfMagicSymbol ) {
2720 foundUseOfMagicSymbol = (strcmp(symbolName, missingWeakImportSymbolName) == 0);
2721 }
2722
2723 if (!foundSymbol) {
2724 for (const std::string& dependencyID : dependencies) {
2725 auto depIt = dylibsToSymbols.find(dependencyID);
2726 if (depIt == dylibsToSymbols.end()) {
2727 dylibDiag.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
2728 symbolName, dependencyID.c_str());
2729 stop = true;
2730 return;
2731 }
2732
2733 const DylibSymbols& dylibSymbols = depIt->second;
2734 auto exportIt = dylibSymbols.globals.find(symbolName);
2735 if ( exportIt == dylibSymbols.globals.end() )
2736 continue;
2737 findDependencyWithSymbol(symbolName, dependencies);
2738 break;
2739 }
2740 }
2741
2742 // If the symbol is weak, and we didn't find it in our listed
2743 // dependencies, then use our own definition
2744 if ( !foundSymbol && (libOrdinal == BIND_SPECIAL_DYLIB_WEAK_LOOKUP) ) {
2745 auto dylibIt = dylibsToSymbols.find(dylibID);
2746 if (dylibIt == dylibsToSymbols.end()) {
2747 dylibDiag.error("Failed to bind weak '%s' as could not find a define in self",
2748 symbolName);
2749 stop = true;
2750 return;
2751 }
2752
2753 const DylibSymbols& dylibSymbols = dylibIt->second;
2754 auto exportIt = dylibSymbols.globals.find(symbolName);
2755 if ( exportIt != dylibSymbols.globals.end() ) {
2756 // Set the bind to the target address since we found it
2757 uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
2758 if ( bindType == BIND_TYPE_POINTER ) {
2759 if ( _is64 )
2760 *((uint64_t*)fixupLoc) = exportIt->second;
2761 else
2762 *((uint32_t*)fixupLoc) = (uint32_t)exportIt->second;
2763
2764 // Only track regular fixups for ASLR, not branch fixups
2765 fixupLocs[fixupLoc] = dylibSymbols.dylibLevel;
2766 resolvedBindLocations[(const uint8_t*)fixupLoc] = bindSymbol;
2767 } else if ( bindType == BIND_TYPE_TEXT_PCREL32 ) {
2768 // We should never have a branch to a weak bind as we should have had a GOT for these
2769 dylibDiag.error("Unexpected weak bind type: %d", bindType);
2770 stop = true;
2771 return;
2772 } else {
2773 dylibDiag.error("Unexpected bind type: %d", bindType);
2774 stop = true;
2775 return;
2776 }
2777
2778 foundSymbol = true;
2779 }
2780 }
2781
2782 if ( !foundSymbol && weakImport ) {
2783 if ( bindType != BIND_TYPE_POINTER ) {
2784 dylibDiag.error("Unexpected bind type: %d", bindType);
2785 stop = true;
2786 return;
2787 }
2788 // Find _gOSKextUnresolved in the kernel
2789 // Weak imports are not compared against null, but instead against the address of that symbol
2790 auto kernelSymbolsIt = dylibsToSymbols.find(kernelID);
2791 assert(kernelSymbolsIt != dylibsToSymbols.end());
2792 const DylibSymbols& kernelSymbols = kernelSymbolsIt->second;
2793 auto exportIt = kernelSymbols.globals.find(missingWeakImportSymbolName);
2794 if (exportIt != kernelSymbols.globals.end()) {
2795 foundMissingWeakImport = true;
2796
2797 uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
2798 if ( _is64 )
2799 *((uint64_t*)fixupLoc) = exportIt->second;
2800 else
2801 *((uint32_t*)fixupLoc) = (uint32_t)exportIt->second;
2802
2803 // Only track regular fixups for ASLR, not branch fixups
2804 fixupLocs[fixupLoc] = kernelSymbols.dylibLevel;
2805 return;
2806 }
2807 dylibDiag.error("Weak bind symbol '%s' not found in kernel", missingWeakImportSymbolName);
2808 return;
2809 }
2810
2811 if ( !foundSymbol ) {
2812 // Store missing binds for later. They may be fixed by vtable patching
2813 const uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
2814 missingBindLocations[fixupLoc] = bindSymbol;
2815 }
2816 }, ^(const char *symbolName) {
2817 dylibDiag.error("Strong binds are not supported right now");
2818 });
2819
2820 if ( foundMissingWeakImport && !foundUseOfMagicSymbol ) {
2821 dylibDiag.error("Has weak references but does not test for them. "
2822 "Test for weak references with OSKextSymbolIsResolved().");
2823 return;
2824 }
2825
2826 ma->forEachRebase(dylibDiag, false, ^(uint64_t runtimeOffset, bool &stop) {
2827 uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
2828 fixupLocs[fixupLoc] = (uint8_t)~0U;
2829 });
2830 }
2831
2832 // A helper to automatically call CFRelease when we go out of scope
2833 struct AutoReleaseTypeRef {
2834 AutoReleaseTypeRef() = default;
2835 ~AutoReleaseTypeRef() {
2836 if ( ref != nullptr ) {
2837 CFRelease(ref);
2838 }
2839 }
2840 void setRef(CFTypeRef typeRef) {
2841 assert(ref == nullptr);
2842 ref = typeRef;
2843 }
2844
2845 CFTypeRef ref = nullptr;
2846 };
2847
2848 static std::unique_ptr<std::unordered_set<std::string>> getKPI(Diagnostics& diags, const dyld3::MachOAnalyzer* ma,
2849 std::string_view dylibID) {
2850 bool isAppleKext = (dylibID.find("com.apple") == 0);
2851 if ( !isAppleKext )
2852 return {};
2853
2854 __block std::list<std::string> nonASCIIStrings;
2855 auto getString = ^(Diagnostics& diags, CFStringRef symbolNameRef) {
2856 const char* symbolName = CFStringGetCStringPtr(symbolNameRef, kCFStringEncodingUTF8);
2857 if ( symbolName != nullptr )
2858 return symbolName;
2859
2860 CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef), kCFStringEncodingUTF8);
2861 char buffer[len + 1];
2862 if ( !CFStringGetCString(symbolNameRef, buffer, len, kCFStringEncodingUTF8) ) {
2863 diags.error("Could not convert string to ASCII");
2864 return (const char*)nullptr;
2865 }
2866 buffer[len] = '\0';
2867 nonASCIIStrings.push_back(buffer);
2868 return nonASCIIStrings.back().c_str();
2869 };
2870
2871 uint64_t symbolSetsSize = 0;
2872 const void* symbolSetsContent = ma->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize);
2873 if ( symbolSetsContent == nullptr )
2874 return {};
2875
2876 AutoReleaseTypeRef dataRefReleaser;
2877 AutoReleaseTypeRef plistRefReleaser;
2878
2879 std::unordered_set<std::string> symbols;
2880 CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)symbolSetsContent, symbolSetsSize, kCFAllocatorNull);
2881 if ( dataRef == nullptr ) {
2882 diags.error("Could not create data ref for kpi");
2883 return {};
2884 }
2885 dataRefReleaser.setRef(dataRef);
2886
2887 CFErrorRef errorRef = nullptr;
2888 CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, dataRef, kCFPropertyListImmutable, nullptr, &errorRef);
2889 if (errorRef != nullptr) {
2890 CFStringRef errorString = CFErrorCopyDescription(errorRef);
2891 diags.error("Could not load plist because :%s", CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
2892 CFRelease(errorRef);
2893 return {};
2894 }
2895 if ( plistRef == nullptr ) {
2896 diags.error("Could not create plist ref for kpi");
2897 return {};
2898 }
2899 plistRefReleaser.setRef(plistRef);
2900
2901 if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) {
2902 diags.error("kpi plist should be a dictionary");
2903 return {};
2904 }
2905
2906 CFDictionaryRef symbolSetsDictRef = (CFDictionaryRef)plistRef;
2907
2908 // CFBundleIdentifier
2909 CFStringRef bundleIDRef = (CFStringRef)CFDictionaryGetValue(symbolSetsDictRef, CFSTR("CFBundleIdentifier"));
2910 if ( (bundleIDRef == nullptr) || (CFGetTypeID(bundleIDRef) != CFStringGetTypeID()) ) {
2911 diags.error("kpi bundle ID should be a string");
2912 return {};
2913 }
2914
2915 const char* bundleID = getString(diags, bundleIDRef);
2916 if ( bundleID == nullptr )
2917 return {};
2918
2919 if ( dylibID != bundleID ) {
2920 diags.error("kpi bundle ID doesn't match kext");
2921 return {};
2922 }
2923
2924 CFArrayRef symbolsArrayRef = (CFArrayRef)CFDictionaryGetValue(symbolSetsDictRef, CFSTR("Symbols"));
2925 if ( symbolsArrayRef != nullptr ) {
2926 if ( CFGetTypeID(symbolsArrayRef) != CFArrayGetTypeID() ) {
2927 diags.error("Symbols value should be an array");
2928 return {};
2929 }
2930 for (CFIndex symbolSetIndex = 0; symbolSetIndex != CFArrayGetCount(symbolsArrayRef); ++symbolSetIndex) {
2931 CFStringRef symbolNameRef = (CFStringRef)CFArrayGetValueAtIndex(symbolsArrayRef, symbolSetIndex);
2932 if ( (symbolNameRef == nullptr) || (CFGetTypeID(symbolNameRef) != CFStringGetTypeID()) ) {
2933 diags.error("Symbol name should be a string");
2934 return {};
2935 }
2936
2937 const char* symbolName = getString(diags, symbolNameRef);
2938 if ( symbolName == nullptr )
2939 return {};
2940 symbols.insert(symbolName);
2941 }
2942 }
2943
2944 return std::make_unique<std::unordered_set<std::string>>(std::move(symbols));
2945 }
2946
2947 void AppCacheBuilder::processFixups()
2948 {
2949 auto dylibsToSymbolsOwner = std::make_unique<std::map<std::string, DylibSymbols>>();
2950 std::map<std::string, DylibSymbols>& dylibsToSymbols = *dylibsToSymbolsOwner.get();
2951
2952 auto vtablePatcherOwner = std::make_unique<VTablePatcher>(numFixupLevels);
2953 VTablePatcher& vtablePatcher = *vtablePatcherOwner.get();
2954
2955 const uint32_t kernelLevel = 0;
2956 uint8_t currentLevel = getCurrentFixupLevel();
2957
2958 // Keep track of missing binds until later. They may be "resolved" by vtable patching
2959 std::map<const uint8_t*, const VTableBindSymbol> missingBindLocations;
2960
2961 __block std::string kernelID;
2962 __block const dyld3::MachOAnalyzer* kernelMA = nullptr;
2963 if ( appCacheOptions.cacheKind == Options::AppCacheKind::kernel ) {
2964 kernelMA = getKernelStaticExecutableFromCache();
2965 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
2966 DylibStripMode stripMode, const std::vector<std::string> &dependencies,
2967 Diagnostics& dylibDiag,
2968 bool &stop) {
2969 if ( ma == kernelMA ) {
2970 kernelID = dylibID;
2971 stop = true;
2972 }
2973 });
2974 assert(!kernelID.empty());
2975 } else {
2976 assert(existingKernelCollection != nullptr);
2977 existingKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *name, bool &stop) {
2978 if ( ma->isStaticExecutable() ) {
2979 kernelMA = ma;
2980 kernelID = name;
2981 }
2982 });
2983 if ( kernelMA == nullptr ) {
2984 _diagnostics.error("Could not find kernel in kernel collection");
2985 return;
2986 }
2987 }
2988
2989 auto getGlobals = [](Diagnostics& diags, const dyld3::MachOAnalyzer *ma) -> std::map<std::string_view, uint64_t> {
2990 // Note we don't put __block on the variable directly as then it gets copied in to the return value
2991 std::map<std::string_view, uint64_t> exports;
2992 __block std::map<std::string_view, uint64_t>& exportsRef = exports;
2993 ma->forEachGlobalSymbol(diags, ^(const char *symbolName, uint64_t n_value,
2994 uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
2995 exportsRef[symbolName] = n_value;
2996 });
2997 return exports;
2998 };
2999
3000 auto getLocals = [](Diagnostics& diags, const dyld3::MachOAnalyzer *ma) -> std::map<std::string_view, uint64_t> {
3001 // Note we don't put __block on the variable directly as then it gets copied in to the return value
3002 std::map<std::string_view, uint64_t> exports;
3003 __block std::map<std::string_view, uint64_t>& exportsRef = exports;
3004 ma->forEachLocalSymbol(diags, ^(const char *symbolName, uint64_t n_value,
3005 uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
3006 exportsRef[symbolName] = n_value;
3007 });
3008 return exports;
3009 };
3010
3011 dylibsToSymbols[kernelID] = {
3012 getGlobals(_diagnostics, kernelMA),
3013 getLocals(_diagnostics, kernelMA),
3014 nullptr,
3015 kernelLevel,
3016 std::string(kernelID)
3017 };
3018
3019 // Add all the codeless kext's as kext's can list them as dependencies
3020 // Note we add placeholders here which can be legitimately replaced by symbol sets
3021 for (const InputDylib& dylib : codelessKexts) {
3022 dylibsToSymbols[dylib.dylibID] = { };
3023 }
3024
3025 // Similarly, add placeholders for codeless kexts in the baseKC
3026 if ( existingKernelCollection != nullptr ) {
3027 existingKernelCollection->forEachPrelinkInfoLibrary(_diagnostics,
3028 ^(const char *bundleName, const char* relativePath,
3029 const std::vector<const char *> &deps) {
3030 dylibsToSymbols[bundleName] = { };
3031 });
3032 }
3033
3034 // And placeholders for codeless kexts in the pageableKC
3035 if ( pageableKernelCollection != nullptr ) {
3036 pageableKernelCollection->forEachPrelinkInfoLibrary(_diagnostics,
3037 ^(const char *bundleName, const char* relativePath,
3038 const std::vector<const char *> &deps) {
3039 dylibsToSymbols[bundleName] = { };
3040 });
3041 }
3042
3043 // Get the symbol sets
3044 AutoReleaseTypeRef dataRefReleaser;
3045 AutoReleaseTypeRef plistRefReleaser;
3046
3047 __block std::list<std::string> nonASCIIStrings;
3048 auto getString = ^(Diagnostics& diags, CFStringRef symbolNameRef) {
3049 const char* symbolName = CFStringGetCStringPtr(symbolNameRef, kCFStringEncodingUTF8);
3050 if ( symbolName != nullptr )
3051 return symbolName;
3052
3053 CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef), kCFStringEncodingUTF8);
3054 char buffer[len + 1];
3055 if ( !CFStringGetCString(symbolNameRef, buffer, len, kCFStringEncodingUTF8) ) {
3056 diags.error("Could not convert string to ASCII");
3057 return (const char*)nullptr;
3058 }
3059 buffer[len] = '\0';
3060 nonASCIIStrings.push_back(buffer);
3061 return nonASCIIStrings.back().c_str();
3062 };
3063
3064 uint64_t symbolSetsSize = 0;
3065 const void* symbolSetsContent = kernelMA->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize);
3066 if ( symbolSetsContent != nullptr ) {
3067 const DylibSymbols& kernelSymbols = dylibsToSymbols[kernelID];
3068
3069 CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)symbolSetsContent, symbolSetsSize, kCFAllocatorNull);
3070 if ( dataRef == nullptr ) {
3071 _diagnostics.error("Could not create data ref for symbol sets");
3072 return;
3073 }
3074 dataRefReleaser.setRef(dataRef);
3075
3076 CFErrorRef errorRef = nullptr;
3077 CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, dataRef, kCFPropertyListImmutable, nullptr, &errorRef);
3078 if (errorRef != nullptr) {
3079 CFStringRef errorString = CFErrorCopyDescription(errorRef);
3080 _diagnostics.error("Could not load plist because :%s",
3081 CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
3082 CFRelease(errorRef);
3083 return;
3084 }
3085 if ( plistRef == nullptr ) {
3086 _diagnostics.error("Could not create plist ref for symbol sets");
3087 return;
3088 }
3089 plistRefReleaser.setRef(plistRef);
3090
3091 if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) {
3092 _diagnostics.error("Symbol set plist should be a dictionary");
3093 return;
3094 }
3095 CFDictionaryRef symbolSetsDictRef = (CFDictionaryRef)plistRef;
3096 CFArrayRef symbolSetArrayRef = (CFArrayRef)CFDictionaryGetValue(symbolSetsDictRef, CFSTR("SymbolsSets"));
3097 if ( symbolSetArrayRef != nullptr ) {
3098 if ( CFGetTypeID(symbolSetArrayRef) != CFArrayGetTypeID() ) {
3099 _diagnostics.error("SymbolsSets value should be an array");
3100 return;
3101 }
3102 for (CFIndex symbolSetIndex = 0; symbolSetIndex != CFArrayGetCount(symbolSetArrayRef); ++symbolSetIndex) {
3103 CFDictionaryRef symbolSetDictRef = (CFDictionaryRef)CFArrayGetValueAtIndex(symbolSetArrayRef, symbolSetIndex);
3104 if ( CFGetTypeID(symbolSetDictRef) != CFDictionaryGetTypeID() ) {
3105 _diagnostics.error("Symbol set element should be a dictionary");
3106 return;
3107 }
3108
3109 // CFBundleIdentifier
3110 CFStringRef bundleIDRef = (CFStringRef)CFDictionaryGetValue(symbolSetDictRef, CFSTR("CFBundleIdentifier"));
3111 if ( (bundleIDRef == nullptr) || (CFGetTypeID(bundleIDRef) != CFStringGetTypeID()) ) {
3112 _diagnostics.error("Symbol set bundle ID should be a string");
3113 return;
3114 }
3115
3116 // Symbols
3117 CFArrayRef symbolsArrayRef = (CFArrayRef)CFDictionaryGetValue(symbolSetDictRef, CFSTR("Symbols"));
3118 if ( (symbolsArrayRef == nullptr) || (CFGetTypeID(symbolsArrayRef) != CFArrayGetTypeID()) ) {
3119 _diagnostics.error("Symbol set symbols should be an array");
3120 return;
3121 }
3122
3123 std::map<std::string_view, uint64_t> symbolSetGlobals;
3124 std::map<std::string_view, uint64_t> symbolSetLocals;
3125 for (CFIndex symbolIndex = 0; symbolIndex != CFArrayGetCount(symbolsArrayRef); ++symbolIndex) {
3126 CFDictionaryRef symbolDictRef = (CFDictionaryRef)CFArrayGetValueAtIndex(symbolsArrayRef, symbolIndex);
3127 if ( CFGetTypeID(symbolDictRef) != CFDictionaryGetTypeID() ) {
3128 _diagnostics.error("Symbols array element should be a dictionary");
3129 return;
3130 }
3131
3132 // SymbolPrefix
3133 CFStringRef symbolPrefixRef = (CFStringRef)CFDictionaryGetValue(symbolDictRef, CFSTR("SymbolPrefix"));
3134 if ( symbolPrefixRef != nullptr ) {
3135 if ( CFGetTypeID(symbolPrefixRef) != CFStringGetTypeID() ) {
3136 _diagnostics.error("Symbol prefix should be a string");
3137 return;
3138 }
3139
3140 const char* symbolPrefix = getString(_diagnostics, symbolPrefixRef);
3141 if ( symbolPrefix == nullptr )
3142 return;
3143 size_t symbolPrefixLen = strlen(symbolPrefix);
3144
3145 // FIXME: Brute force might not be the best thing here
3146 for (std::pair<std::string_view, uint64_t> kernelGlobal : kernelSymbols.globals) {
3147 if ( strncmp(kernelGlobal.first.data(), symbolPrefix, symbolPrefixLen) == 0 ) {
3148 symbolSetGlobals[kernelGlobal.first] = kernelGlobal.second;
3149 }
3150 }
3151 for (std::pair<std::string_view, uint64_t> kernelLocal : kernelSymbols.locals) {
3152 if ( strncmp(kernelLocal.first.data(), symbolPrefix, symbolPrefixLen) == 0 ) {
3153 symbolSetLocals[kernelLocal.first] = kernelLocal.second;
3154 }
3155 }
3156 continue;
3157 }
3158
3159 // SymbolName
3160 CFStringRef symbolNameRef = (CFStringRef)CFDictionaryGetValue(symbolDictRef, CFSTR("SymbolName"));
3161 if ( (symbolNameRef == nullptr) || (CFGetTypeID(symbolNameRef) != CFStringGetTypeID()) ) {
3162 _diagnostics.error("Symbol name should be a string");
3163 return;
3164 }
3165
3166 // AliasTarget [Optional]
3167 CFStringRef aliasTargetRef = (CFStringRef)CFDictionaryGetValue(symbolDictRef, CFSTR("AliasTarget"));
3168 if ( aliasTargetRef == nullptr ) {
3169 // No alias
3170 const char* symbolName = getString(_diagnostics, symbolNameRef);
3171 if ( symbolName == nullptr )
3172 return;
3173
3174 // Find the symbol in xnu
3175 auto globalIt = kernelSymbols.globals.find(symbolName);
3176 if (globalIt != kernelSymbols.globals.end()) {
3177 symbolSetGlobals[symbolName] = globalIt->second;
3178 }
3179
3180 auto localIt = kernelSymbols.locals.find(symbolName);
3181 if (localIt != kernelSymbols.locals.end()) {
3182 symbolSetLocals[symbolName] = localIt->second;
3183 }
3184 } else {
3185 // We have an alias
3186 if ( CFGetTypeID(aliasTargetRef) != CFStringGetTypeID() ) {
3187 _diagnostics.error("Alias should be a string");
3188 return;
3189 }
3190
3191 const char* symbolName = getString(_diagnostics, symbolNameRef);
3192 if ( symbolName == nullptr )
3193 return;
3194 const char* aliasTargetName = getString(_diagnostics, aliasTargetRef);
3195 if ( aliasTargetName == nullptr )
3196 return;
3197
3198 // Find the alias symbol in xnu
3199 auto globalIt = kernelSymbols.globals.find(aliasTargetName);
3200 if (globalIt != kernelSymbols.globals.end()) {
3201 symbolSetGlobals[symbolName] = globalIt->second;
3202 } else {
3203 _diagnostics.error("Alias '%s' not found in kernel", aliasTargetName);
3204 return;
3205 }
3206
3207 auto localIt = kernelSymbols.locals.find(aliasTargetName);
3208 if (localIt != kernelSymbols.locals.end()) {
3209 symbolSetLocals[symbolName] = localIt->second;
3210 } else {
3211 // This is not an error, as aliases from symbol sets from the kernel
3212 // are only for vtable patching, not general binding
3213 }
3214 }
3215 }
3216 const char* dylibID = getString(_diagnostics, bundleIDRef);
3217 if ( dylibID == nullptr )
3218 return;
3219
3220 // HACK: kxld aliases __ZN15OSMetaClassBase25_RESERVEDOSMetaClassBase3Ev to __ZN15OSMetaClassBase8DispatchE5IORPC
3221 auto metaclassHackIt = symbolSetGlobals.find("__ZN15OSMetaClassBase8DispatchE5IORPC");
3222 if ( metaclassHackIt != symbolSetGlobals.end() )
3223 symbolSetGlobals["__ZN15OSMetaClassBase25_RESERVEDOSMetaClassBase3Ev"] = metaclassHackIt->second;
3224 dylibsToSymbols[dylibID] = {
3225 std::move(symbolSetGlobals),
3226 std::move(symbolSetLocals),
3227 nullptr,
3228 kernelLevel,
3229 dylibID
3230 };
3231 }
3232 }
3233 }
3234
3235 auto processBinary = ^(Diagnostics& dylibDiags, const dyld3::MachOAnalyzer *ma,
3236 const std::string& dylibID, uint32_t dylibLevel) {
3237 // We dont support export trie's for now
3238 uint32_t unusedExportTrieOffset = 0;
3239 uint32_t unusedExportTrieSize = 0;
3240 if (ma->hasExportTrie(unusedExportTrieOffset, unusedExportTrieSize))
3241 assert(false);
3242
3243 // Already done the kernel before.
3244 if ( ma == kernelMA )
3245 return;
3246
3247 // Regular kext.
3248 dylibsToSymbols[dylibID] = {
3249 getGlobals(dylibDiags, ma),
3250 getLocals(dylibDiags, ma),
3251 getKPI(dylibDiags, ma, dylibID),
3252 dylibLevel,
3253 dylibID };
3254 };
3255
3256 // Process binary symbols in parallel
3257 {
3258 struct DylibData {
3259 const dyld3::MachOAnalyzer* ma = nullptr;
3260 Diagnostics& dylibDiag;
3261 const std::string& dylibID;
3262 };
3263
3264 __block std::vector<DylibData> dylibDatas;
3265 dylibDatas.reserve(sortedDylibs.size());
3266 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID, DylibStripMode stripMode,
3267 const std::vector<std::string> &dependencies, Diagnostics &dylibDiag, bool &stop) {
3268 // Already done the kernel before.
3269 if ( ma == kernelMA )
3270 return;
3271
3272 // Make space for all the map entries so that we know they are there when we write their values later
3273 dylibsToSymbols[dylibID] = { };
3274 dylibDatas.emplace_back((DylibData){ ma, dylibDiag, dylibID });
3275 });
3276
3277 dispatch_apply(dylibDatas.size(), DISPATCH_APPLY_AUTO, ^(size_t index) {
3278 DylibData& dylibData = dylibDatas[index];
3279 processBinary(dylibData.dylibDiag, dylibData.ma, dylibData.dylibID, currentLevel);
3280 });
3281 }
3282
3283 // Add exports from the kernel collection if we have it
3284 if ( existingKernelCollection != nullptr ) {
3285 uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::kernel);
3286 existingKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *name, bool &stop) {
3287 processBinary(_diagnostics, ma, name, fixupLevel);
3288 });
3289 }
3290
3291 // Add exports from the pageable collection if we have it
3292 if ( pageableKernelCollection != nullptr ) {
3293 uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::pageableKC);
3294 pageableKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *name, bool &stop) {
3295 processBinary(_diagnostics, ma, name, fixupLevel);
3296 });
3297 }
3298
3299 // Map from an offset in to a KC to a synthesized stub which branches to that offset
3300 struct CacheOffsetHash
3301 {
3302 size_t operator() (const CacheOffset& cacheOffset) const
3303 {
3304 return std::hash<uint32_t>{}(cacheOffset.first) ^ std::hash<uint64_t>{}(cacheOffset.second);
3305 }
3306 };
3307 std::unordered_map<CacheOffset, uint64_t, CacheOffsetHash> branchStubs;
3308
3309 // Clear the branch regions sizes so that we fill them up to their buffer sizes as we go
3310 branchStubsRegion.sizeInUse = 0;
3311 branchGOTsRegion.sizeInUse = 0;
3312
3313 {
3314 // Map from each symbol to the list of dylibs which export it
3315 auto symbolMapOwner = std::make_unique<std::unordered_map<std::string_view, std::vector<DylibSymbolLocation>>>();
3316 __block auto& symbolMap = *symbolMapOwner.get();
3317 for (const auto& dylibNameAndSymbols : dylibsToSymbols) {
3318 const DylibSymbols& dylibSymbols = dylibNameAndSymbols.second;
3319 for (const auto& symbolNameAndAddress : dylibSymbols.globals) {
3320 // By default, everything i KPI, ie, can be linked by third parties.
3321 // If a symbol is is provided, even an empty one, then it can override this
3322 bool isKPI = true;
3323 if ( dylibSymbols.dylibName == "com.apple.kpi.private" ) {
3324 // com.apple.kpi.private is always hidden from third parties. They shouldn't even list it as a dependency
3325 isKPI = false;
3326 } else if ( dylibSymbols.kpiSymbols ) {
3327 const std::unordered_set<std::string>* kpiSymbols = dylibSymbols.kpiSymbols.get();
3328 if ( kpiSymbols->count(symbolNameAndAddress.first.data()) == 0 )
3329 isKPI = false;
3330 }
3331 symbolMap[symbolNameAndAddress.first].push_back({ &dylibSymbols, symbolNameAndAddress.second, isKPI });
3332 }
3333 }
3334
3335 auto dylibFixupsOwner = std::make_unique<std::vector<DylibFixups>>();
3336 __block auto& dylibFixups = *dylibFixupsOwner.get();
3337 dylibFixups.reserve(sortedDylibs.size());
3338 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID, DylibStripMode stripMode,
3339 const std::vector<std::string> &dependencies, Diagnostics &dylibDiag, bool &stop) {
3340
3341 auto dylibSymbolsIt = dylibsToSymbols.find(dylibID);
3342 assert(dylibSymbolsIt != dylibsToSymbols.end());
3343
3344 dylibFixups.emplace_back((DylibFixups){
3345 .ma = ma,
3346 .dylibSymbols = dylibSymbolsIt->second,
3347 .dylibDiag = dylibDiag,
3348 .dependencies = dependencies
3349 });
3350 });
3351
3352 dispatch_apply(dylibFixups.size(), DISPATCH_APPLY_AUTO, ^(size_t index) {
3353 DylibFixups& dylibFixup = dylibFixups[index];
3354 dylibFixup.processFixups(dylibsToSymbols, symbolMap, kernelID, _aslrTracker);
3355 });
3356
3357 // Merge all the dylib results in serial
3358 for (DylibFixups& dylibFixup : dylibFixups) {
3359 // Skip bad dylibs
3360 if ( dylibFixup.dylibDiag.hasError() ) {
3361 if ( !_diagnostics.hasError() ) {
3362 _diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
3363 }
3364 return;
3365 }
3366
3367 if ( !dylibFixup.missingBindLocations.empty() ) {
3368 missingBindLocations.insert(dylibFixup.missingBindLocations.begin(),
3369 dylibFixup.missingBindLocations.end());
3370 }
3371
3372 if ( !dylibFixup.fixupLocs.empty() ) {
3373 for (auto fixupLocAndLevel : dylibFixup.fixupLocs) {
3374 _aslrTracker.add(fixupLocAndLevel.first, fixupLocAndLevel.second);
3375 }
3376 }
3377
3378 if ( !dylibFixup.fixupHigh8s.empty() ) {
3379 for (auto fixupLocAndHigh8 : dylibFixup.fixupHigh8s) {
3380 _aslrTracker.setHigh8(fixupLocAndHigh8.first, fixupLocAndHigh8.second);
3381 }
3382 }
3383
3384 if ( !dylibFixup.fixupAuths.empty() ) {
3385 for (auto fixupLocAndAuth : dylibFixup.fixupAuths) {
3386 _aslrTracker.setAuthData(fixupLocAndAuth.first, fixupLocAndAuth.second.diversity,
3387 fixupLocAndAuth.second.addrDiv, fixupLocAndAuth.second.key);
3388 }
3389 }
3390
3391 // Emit branch stubs
3392 const uint64_t loadAddress = dylibFixup.ma->preferredLoadAddress();
3393 for (const DylibFixups::BranchStubData& branchData : dylibFixup.branchStubs) {
3394 // Branching from the auxKC to baseKC. ld64 doesn't emit a stub in x86_64 kexts
3395 // so we need to synthesize one now
3396 uint64_t targetAddress = 0;
3397 const CacheOffset& targetCacheOffset = branchData.targetCacheOffset;
3398 auto itAndInserted = branchStubs.insert({ targetCacheOffset, 0 });
3399 if ( itAndInserted.second ) {
3400 // We inserted the branch location, so we need to create new stubs and GOTs
3401 if ( branchStubsRegion.sizeInUse == branchStubsRegion.bufferSize ) {
3402 _diagnostics.error("Overflow in branch stubs region");
3403 return;
3404 }
3405 if ( branchGOTsRegion.sizeInUse == branchGOTsRegion.bufferSize ) {
3406 _diagnostics.error("Overflow in branch GOTs region");
3407 return;
3408 }
3409 uint64_t stubAddress = branchStubsRegion.unslidLoadAddress + branchStubsRegion.sizeInUse;
3410 uint8_t* stubBuffer = branchStubsRegion.buffer + branchStubsRegion.sizeInUse;
3411 uint64_t gotAddress = branchGOTsRegion.unslidLoadAddress + branchGOTsRegion.sizeInUse;
3412 uint8_t* gotBuffer = branchGOTsRegion.buffer + branchGOTsRegion.sizeInUse;
3413
3414 // Write the stub
3415 // ff 25 aa bb cc dd jmpq *0xddccbbaa(%rip)
3416 uint64_t diffValue = gotAddress - (stubAddress + 6);
3417 stubBuffer[0] = 0xFF;
3418 stubBuffer[1] = 0x25;
3419 memcpy(&stubBuffer[2], &diffValue, sizeof(uint32_t));
3420
3421 // And write the GOT
3422 uint8_t symbolCacheLevel = targetCacheOffset.first;
3423 uint64_t symbolVMAddr = targetCacheOffset.second;
3424 if ( _is64 )
3425 *((uint64_t*)gotBuffer) = symbolVMAddr;
3426 else
3427 *((uint32_t*)gotBuffer) = (uint32_t)symbolVMAddr;
3428 _aslrTracker.add(gotBuffer, symbolCacheLevel);
3429
3430 branchStubsRegion.sizeInUse += 6;
3431 branchGOTsRegion.sizeInUse += 8;
3432 targetAddress = stubAddress;
3433 itAndInserted.first->second = targetAddress;
3434 } else {
3435 // The stub already existed, so use it
3436 targetAddress = itAndInserted.first->second;
3437 }
3438 uint64_t diffValue = targetAddress - (loadAddress + branchData.fixupVMOffset + 4);
3439 *((uint32_t*)branchData.fixupLoc) = (uint32_t)diffValue;
3440 }
3441 }
3442
3443 // FIXME: We could move symbolOwner and dylibFixupsOwner to a worker thread to be destroyed
3444 }
3445
3446 // Now that we've processes all rebases/binds, patch all the vtables
3447
3448 // Add all the collections to the vtable patcher
3449 if ( existingKernelCollection != nullptr ) {
3450 // The baseKC for x86_64 has __HIB mapped first , so we need to get either the __DATA or __TEXT depending on what is earliest
3451 // The kernel base address is still __TEXT, even if __DATA or __HIB is mapped prior to that.
3452 // The loader may have loaded something before __TEXT, but the existingKernelCollection pointer still corresponds to __TEXT
3453 __block uint64_t baseAddress = ~0ULL;
3454 existingKernelCollection->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
3455 baseAddress = std::min(baseAddress, info.vmAddr);
3456 });
3457
3458 // The existing collection is a pointer to the mach_header for the baseKC, but __HIB and other segments may be before that
3459 // Offset those here
3460 uint64_t basePointerOffset = existingKernelCollection->preferredLoadAddress() - baseAddress;
3461 const uint8_t* basePointer = (uint8_t*)existingKernelCollection - basePointerOffset;
3462
3463 vtablePatcher.addKernelCollection(existingKernelCollection, Options::AppCacheKind::kernel,
3464 basePointer, baseAddress);
3465 }
3466
3467 if ( pageableKernelCollection != nullptr ) {
3468 // The baseKC for x86_64 has __HIB mapped first , so we need to get either the __DATA or __TEXT depending on what is earliest
3469 // The kernel base address is still __TEXT, even if __DATA or __HIB is mapped prior to that.
3470 // The loader may have loaded something before __TEXT, but the existingKernelCollection pointer still corresponds to __TEXT
3471 __block uint64_t baseAddress = ~0ULL;
3472 pageableKernelCollection->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
3473 baseAddress = std::min(baseAddress, info.vmAddr);
3474 });
3475
3476 // The existing collection is a pointer to the mach_header for the baseKC, but __HIB and other segments may be before that
3477 // Offset those here
3478 uint64_t basePointerOffset = pageableKernelCollection->preferredLoadAddress() - baseAddress;
3479 const uint8_t* basePointer = (uint8_t*)pageableKernelCollection - basePointerOffset;
3480
3481 vtablePatcher.addKernelCollection(pageableKernelCollection, Options::AppCacheKind::pageableKC,
3482 basePointer, baseAddress);
3483 }
3484
3485 // Also add our KC
3486 vtablePatcher.addKernelCollection((const dyld3::MachOAppCache*)cacheHeader.header, appCacheOptions.cacheKind,
3487 (const uint8_t*)_fullAllocatedBuffer, cacheBaseAddress);
3488
3489 // Add all the dylibs to the patcher
3490 {
3491 if ( existingKernelCollection != nullptr ) {
3492 uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::kernel);
3493
3494 __block std::map<std::string, std::vector<std::string>> kextDependencies;
3495 kextDependencies[kernelID] = {};
3496 existingKernelCollection->forEachPrelinkInfoLibrary(_diagnostics,
3497 ^(const char *bundleName, const char* relativePath,
3498 const std::vector<const char *> &deps) {
3499 std::vector<std::string>& dependencies = kextDependencies[bundleName];
3500 dependencies.insert(dependencies.end(), deps.begin(), deps.end());
3501 });
3502
3503 existingKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *dylibID, bool &stop) {
3504 auto depsIt = kextDependencies.find(dylibID);
3505 assert(depsIt != kextDependencies.end());
3506 vtablePatcher.addDylib(_diagnostics, ma, dylibID, depsIt->second, fixupLevel);
3507 });
3508 }
3509
3510 if ( pageableKernelCollection != nullptr ) {
3511 uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::pageableKC);
3512
3513 __block std::map<std::string, std::vector<std::string>> kextDependencies;
3514 pageableKernelCollection->forEachPrelinkInfoLibrary(_diagnostics,
3515 ^(const char *bundleName, const char* relativePath,
3516 const std::vector<const char *> &deps) {
3517 std::vector<std::string>& dependencies = kextDependencies[bundleName];
3518 dependencies.insert(dependencies.end(), deps.begin(), deps.end());
3519 });
3520
3521 pageableKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *dylibID, bool &stop) {
3522 auto depsIt = kextDependencies.find(dylibID);
3523 assert(depsIt != kextDependencies.end());
3524 vtablePatcher.addDylib(_diagnostics, ma, dylibID, depsIt->second, fixupLevel);
3525 });
3526 }
3527
3528 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID, DylibStripMode stripMode,
3529 const std::vector<std::string> &dependencies, Diagnostics& dylibDiag, bool &stop) {
3530 vtablePatcher.addDylib(dylibDiag, ma, dylibID, dependencies, currentLevel);
3531 });
3532 }
3533
3534 vtablePatcher.findMetaclassDefinitions(dylibsToSymbols, kernelID, kernelMA, appCacheOptions.cacheKind);
3535 vtablePatcher.findExistingFixups(_diagnostics, existingKernelCollection, pageableKernelCollection);
3536 if ( _diagnostics.hasError() )
3537 return;
3538
3539 // Add vtables from the base KC if we have one
3540 if ( existingKernelCollection != nullptr ) {
3541 vtablePatcher.findBaseKernelVTables(_diagnostics, existingKernelCollection, dylibsToSymbols);
3542 if ( _diagnostics.hasError() )
3543 return;
3544 }
3545
3546 // Add vtables from the pageable KC if we have one
3547 if ( pageableKernelCollection != nullptr ) {
3548 vtablePatcher.findPageableKernelVTables(_diagnostics, pageableKernelCollection, dylibsToSymbols);
3549 if ( _diagnostics.hasError() )
3550 return;
3551 }
3552
3553 // Add vables from our level
3554 vtablePatcher.findVTables(currentLevel, kernelMA, dylibsToSymbols, _aslrTracker, missingBindLocations);
3555
3556 // Don't run the patcher if we have a failure finding the vtables
3557 if ( vtablePatcher.hasError() ) {
3558 _diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
3559 return;
3560 }
3561
3562 // Now patch all of the vtables.
3563 vtablePatcher.patchVTables(_diagnostics, missingBindLocations, _aslrTracker, currentLevel);
3564 if ( _diagnostics.hasError() )
3565 return;
3566
3567 if ( vtablePatcher.hasError() ) {
3568 _diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
3569 return;
3570 }
3571
3572 // FIXME: We could move vtablePatcherOwner to a worker thread to be destroyed
3573 vtablePatcherOwner.reset();
3574
3575 // Also error out if we have an error on any of the dylib diagnostic objects
3576
3577 // Log any binds which are still missing
3578 for (const auto& missingLocationAndBind : missingBindLocations) {
3579 const uint8_t* missingBindLoc = missingLocationAndBind.first;
3580 const VTableBindSymbol& missingBind = missingLocationAndBind.second;
3581
3582 // Work out which segment and section this missing bind was in
3583 __block bool reportedError = false;
3584 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID, DylibStripMode stripMode,
3585 const std::vector<std::string> &dependencies, Diagnostics& dylibDiag, bool &stopDylib) {
3586 intptr_t slide = ma->getSlide();
3587 ma->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo &sectInfo,
3588 bool malformedSectionRange, bool &stopSection) {
3589 const uint8_t* content = (uint8_t*)(sectInfo.sectAddr + slide);
3590 const uint8_t* start = (uint8_t*)content;
3591 const uint8_t* end = start + sectInfo.sectSize;
3592 if ( (missingBindLoc >= start) && (missingBindLoc < end) ) {
3593 std::string segmentName = sectInfo.segInfo.segName;
3594 std::string sectionName = sectInfo.sectName;
3595 uint64_t sectionOffset = (missingBindLoc - start);
3596
3597 dylibDiag.error("Failed to bind '%s' in '%s' (at offset 0x%llx in %s, %s) as "
3598 "could not find a kext which exports this symbol",
3599 missingBind.symbolName.c_str(), missingBind.binaryID.data(),
3600 sectionOffset, segmentName.c_str(), sectionName.c_str());
3601
3602 reportedError = true;
3603 stopSection = true;
3604 stopDylib = true;
3605 }
3606 });
3607 });
3608
3609 if ( !reportedError ) {
3610 _diagnostics.error("Failed to bind '%s' in '%s' as could not find a kext which exports this symbol",
3611 missingBind.symbolName.c_str(), missingBind.binaryID.data());
3612 }
3613 }
3614
3615 // If we had missing binds and reported no other errors, then generate an error to give the diagnostics something to track
3616 if ( !missingBindLocations.empty() && _diagnostics.noError() ) {
3617 _diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
3618 }
3619
3620 // FIXME: We could move dylibsToSymbolsOwner to a worker thread to be destroyed
3621 }
3622
3623 namespace {
3624
3625 class ByteBuffer {
3626 public:
3627 ByteBuffer(uint8_t* storage, uintptr_t allocCount) {
3628 buffer.setInitialStorage(storage, allocCount);
3629 }
3630
3631 uint8_t* makeSpace(size_t bytesNeeded) {
3632 // Make space in the buffer
3633 for (size_t i = 0; i != bytesNeeded; ++i)
3634 buffer.default_constuct_back();
3635
3636 // Grab a pointer to our position in the buffer
3637 uint8_t* data = buffer.begin();
3638
3639 // Move the buffer to start after our data
3640 dyld3::Array<uint8_t> newBuffer(buffer.end(), buffer.freeCount(), 0);
3641 buffer = newBuffer;
3642
3643 return data;
3644 };
3645
3646 const uint8_t* begin() const {
3647 return buffer.begin();
3648 }
3649
3650 const uint8_t* end() const {
3651 return buffer.end();
3652 }
3653
3654 private:
3655 dyld3::Array<uint8_t> buffer;
3656 };
3657
3658 }
3659
3660 void AppCacheBuilder::writeFixups()
3661 {
3662 if ( fixupsSubRegion.sizeInUse == 0 )
3663 return;
3664
3665 __block ByteBuffer byteBuffer(fixupsSubRegion.buffer, fixupsSubRegion.bufferSize);
3666
3667 // Keep track of where we put the fixups
3668 const uint8_t* classicRelocsBufferStart = nullptr;
3669 const uint8_t* classicRelocsBufferEnd = nullptr;
3670
3671 // If the kernel needs classic relocs, emit those first
3672 CacheHeader64& header = cacheHeader;
3673 if ( header.dynSymbolTable != nullptr ) {
3674 classicRelocsBufferStart = byteBuffer.begin();
3675
3676 dyld3::MachOAnalyzer* cacheMA = (dyld3::MachOAnalyzer*)header.header;
3677 __block uint64_t localRelocBaseAddress = 0;
3678 cacheMA->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
3679 if ( info.protections & VM_PROT_WRITE ) {
3680 localRelocBaseAddress = info.vmAddr;
3681 stop = true;
3682 }
3683 });
3684
3685 const std::vector<void*> allRebaseTargets = _aslrTracker.getRebaseTargets();
3686
3687 const dyld3::MachOAnalyzer* kernelMA = getKernelStaticExecutableFromCache();
3688 kernelMA->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
3689 std::vector<void*> segmentRebaseTargets;
3690 uint64_t segmentVMOffset = info.vmAddr - cacheBaseAddress;
3691 const uint8_t* segmentStartAddr = (const uint8_t*)(_fullAllocatedBuffer + segmentVMOffset);
3692 const uint8_t* segmentEndAddr = (const uint8_t*)(segmentStartAddr + info.vmSize);
3693 for (void* target : allRebaseTargets) {
3694 if ( (target >= segmentStartAddr) && (target < segmentEndAddr) ) {
3695 segmentRebaseTargets.push_back(target);
3696 }
3697 }
3698 std::sort(segmentRebaseTargets.begin(), segmentRebaseTargets.end());
3699
3700 for (void* target : segmentRebaseTargets) {
3701 uint64_t targetSegmentOffset = (uint64_t)target - (uint64_t)segmentStartAddr;
3702 //printf("Target: %s + 0x%llx: %p\n", info.segName, targetSegmentOffset, target);
3703
3704 uint64_t offsetFromBaseAddress = (info.vmAddr + targetSegmentOffset) - localRelocBaseAddress;
3705 relocation_info* reloc = (relocation_info*)byteBuffer.makeSpace(sizeof(relocation_info));
3706 reloc->r_address = (uint32_t)offsetFromBaseAddress;
3707 reloc->r_symbolnum = 0;
3708 reloc->r_pcrel = false;
3709 reloc->r_length = 0;
3710 reloc->r_extern = 0;
3711 reloc->r_type = 0;
3712
3713 uint32_t vmAddr32 = 0;
3714 uint64_t vmAddr64 = 0;
3715 if ( _aslrTracker.hasRebaseTarget32(target, &vmAddr32) ) {
3716 reloc->r_length = 2;
3717 *(uint32_t*)target = vmAddr32;
3718 } else if ( _aslrTracker.hasRebaseTarget64(target, &vmAddr64) ) {
3719 reloc->r_length = 3;
3720 *(uint64_t*)target = vmAddr64;
3721 }
3722 }
3723
3724 // Remove these fixups so that we don't also emit chained fixups for them
3725 for (void* target : segmentRebaseTargets)
3726 _aslrTracker.remove(target);
3727 });
3728
3729 classicRelocsBufferEnd = byteBuffer.begin();
3730 }
3731
3732 // TODO: 32-bit pointer format
3733 assert(_is64);
3734 const uint8_t currentLevel = getCurrentFixupLevel();
3735
3736 // We can have up to 4 levels in the fixup format. These are the base addresses from
3737 // which each level starts
3738 BLOCK_ACCCESSIBLE_ARRAY(uint64_t, levelBaseAddresses, 4);
3739 for (unsigned i = 0; i != numFixupLevels; ++i)
3740 levelBaseAddresses[i] = 0;
3741
3742 levelBaseAddresses[currentLevel] = cacheBaseAddress;
3743 if ( appCacheOptions.cacheKind != Options::AppCacheKind::kernel ) {
3744 assert(existingKernelCollection != nullptr);
3745 // The auxKC is mapped with __DATA first, so we need to get either the __DATA or __TEXT depending on what is earliest
3746 __block uint64_t baseAddress = ~0ULL;
3747 existingKernelCollection->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
3748 baseAddress = std::min(baseAddress, info.vmAddr);
3749 });
3750 levelBaseAddresses[0] = baseAddress;
3751 }
3752
3753 if ( pageableKernelCollection != nullptr ) {
3754 // We may have __DATA first, so we need to get either the __DATA or __TEXT depending on what is earliest
3755 __block uint64_t baseAddress = ~0ULL;
3756 pageableKernelCollection->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
3757 baseAddress = std::min(baseAddress, info.vmAddr);
3758 });
3759 uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::pageableKC);
3760 levelBaseAddresses[fixupLevel] = baseAddress;
3761 }
3762
3763 // We have a dyld_chained_starts_in_segment plus an offset for each page
3764 struct SegmentFixups {
3765 //const Region* region = nullptr;
3766 uint8_t* segmentBuffer = nullptr;
3767 uint64_t segmentIndex = 0;
3768 uint64_t unslidLoadAddress = 0;
3769 uint64_t sizeInUse = 0;
3770 dyld_chained_starts_in_segment* starts = nullptr;
3771 uint64_t startsByteSize = 0;
3772 uint64_t numPagesToFixup = 0;
3773 };
3774
3775 auto buildChainedFixups = ^(uint64_t baseAddress, uint64_t segmentCount, std::vector<SegmentFixups>& startsInSegments) {
3776
3777 const uint8_t* chainedFixupsBufferStart = nullptr;
3778 const uint8_t* chainedFixupsBufferEnd = nullptr;
3779
3780 chainedFixupsBufferStart = byteBuffer.begin();
3781
3782 // Start with dyld_chained_fixups_header which is fixed size
3783 dyld_chained_fixups_header* fixupsHeader = (dyld_chained_fixups_header*)byteBuffer.makeSpace(sizeof(dyld_chained_fixups_header));
3784
3785 // We have a dyld_chained_starts_in_image plus an offset for each segment
3786 dyld_chained_starts_in_image* startsInImage = (dyld_chained_starts_in_image*)byteBuffer.makeSpace(sizeof(dyld_chained_starts_in_image) + (segmentCount * sizeof(uint32_t)));
3787
3788 const uint8_t* endOfStarts = nullptr;
3789 for (SegmentFixups& segmentFixups : startsInSegments) {
3790 uint64_t startsInSegmentByteSize = sizeof(dyld_chained_starts_in_segment) + (segmentFixups.numPagesToFixup * sizeof(uint16_t));
3791 dyld_chained_starts_in_segment* startsInSegment = (dyld_chained_starts_in_segment*)byteBuffer.makeSpace(startsInSegmentByteSize);
3792 endOfStarts = (const uint8_t*)startsInSegment + startsInSegmentByteSize;
3793
3794 segmentFixups.starts = startsInSegment;
3795 segmentFixups.startsByteSize = startsInSegmentByteSize;
3796 }
3797
3798 // Starts in image
3799 startsInImage->seg_count = (uint32_t)segmentCount;
3800 for (uint32_t segmentIndex = 0; segmentIndex != segmentCount; ++segmentIndex) {
3801 startsInImage->seg_info_offset[segmentIndex] = 0;
3802 }
3803 for (const SegmentFixups& segmentFixups : startsInSegments) {
3804 dyld_chained_starts_in_segment* startsInSegment = segmentFixups.starts;
3805 uint64_t segmentIndex = segmentFixups.segmentIndex;
3806 assert(segmentIndex < segmentCount);
3807 assert(startsInImage->seg_info_offset[segmentIndex] == 0);
3808 startsInImage->seg_info_offset[segmentIndex] = (uint32_t)((uint8_t*)startsInSegment - (uint8_t*)startsInImage);
3809 }
3810
3811 const unsigned chainedPointerStride = dyld3::MachOAnalyzer::ChainedFixupPointerOnDisk::strideSize(chainedPointerFormat);
3812
3813 // Starts in segment
3814 for (const SegmentFixups& segmentFixups : startsInSegments) {
3815 dyld_chained_starts_in_segment* startsInSegment = segmentFixups.starts;
3816 startsInSegment->size = (uint32_t)segmentFixups.startsByteSize;
3817 startsInSegment->page_size = fixupsPageSize();
3818 startsInSegment->pointer_format = chainedPointerFormat;
3819 startsInSegment->segment_offset = segmentFixups.unslidLoadAddress - baseAddress;
3820 startsInSegment->max_valid_pointer = 0; // FIXME: Needed in 32-bit only
3821 startsInSegment->page_count = (segmentFixups.sizeInUse + startsInSegment->page_size - 1) / startsInSegment->page_size;
3822 for (uint64_t pageIndex = 0; pageIndex != startsInSegment->page_count; ++pageIndex) {
3823 startsInSegment->page_start[pageIndex] = DYLD_CHAINED_PTR_START_NONE;
3824 uint8_t* lastLoc = nullptr;
3825 // Note we always walk in 1-byte at a time as x86_64 has unaligned fixups
3826 for (uint64_t pageOffset = 0; pageOffset != startsInSegment->page_size; pageOffset += 1) {
3827 uint8_t* fixupLoc = segmentFixups.segmentBuffer + (pageIndex * startsInSegment->page_size) + pageOffset;
3828 uint8_t fixupLevel = currentLevel;
3829 if ( !_aslrTracker.has(fixupLoc, &fixupLevel) )
3830 continue;
3831 assert((pageOffset % chainedPointerStride) == 0);
3832 if ( lastLoc ) {
3833 // Patch last loc to point here
3834 assert(_is64);
3835 dyld_chained_ptr_64_kernel_cache_rebase* lastLocBits = (dyld_chained_ptr_64_kernel_cache_rebase*)lastLoc;
3836 assert(lastLocBits->next == 0);
3837 uint64_t next = (fixupLoc - lastLoc) / chainedPointerStride;
3838 lastLocBits->next = next;
3839 assert(lastLocBits->next == next && "next location truncated");
3840 } else {
3841 // First fixup on this page
3842 startsInSegment->page_start[pageIndex] = pageOffset;
3843 }
3844 lastLoc = fixupLoc;
3845
3846 uint64_t targetVMAddr = *(uint64_t*)fixupLoc;
3847
3848 uint8_t highByte = 0;
3849 if ( _aslrTracker.hasHigh8(fixupLoc, &highByte) ) {
3850 uint64_t tbi = (uint64_t)highByte << 56;
3851 targetVMAddr |= tbi;
3852 }
3853
3854 assert(fixupLevel < numFixupLevels);
3855 uint64_t targetVMOffset = targetVMAddr - levelBaseAddresses[fixupLevel];
3856
3857 // Pack the vmAddr on this location in to the fixup format
3858 dyld_chained_ptr_64_kernel_cache_rebase* locBits = (dyld_chained_ptr_64_kernel_cache_rebase*)fixupLoc;
3859
3860 uint16_t diversity;
3861 bool hasAddrDiv;
3862 uint8_t key;
3863 if ( _aslrTracker.hasAuthData(fixupLoc, &diversity, &hasAddrDiv, &key) ) {
3864 locBits->target = targetVMOffset;
3865 locBits->cacheLevel = fixupLevel;
3866 locBits->diversity = diversity;
3867 locBits->addrDiv = hasAddrDiv;
3868 locBits->key = key;
3869 locBits->next = 0;
3870 locBits->isAuth = 1;
3871 assert(locBits->target == targetVMOffset && "target truncated");
3872 }
3873 else {
3874 locBits->target = targetVMOffset;
3875 locBits->cacheLevel = fixupLevel;
3876 locBits->diversity = 0;
3877 locBits->addrDiv = 0;
3878 locBits->key = 0;
3879 locBits->next = 0;
3880 locBits->isAuth = 0;
3881 assert(locBits->target == targetVMOffset && "target truncated");
3882 }
3883 }
3884 }
3885 }
3886
3887 chainedFixupsBufferEnd = byteBuffer.begin();
3888
3889 // Header
3890 fixupsHeader->fixups_version = 0;
3891 fixupsHeader->starts_offset = (uint32_t)((uint8_t*)startsInImage - (uint8_t*)fixupsHeader);
3892 fixupsHeader->imports_offset = (uint32_t)((uint8_t*)chainedFixupsBufferEnd - (uint8_t*)fixupsHeader);
3893 fixupsHeader->symbols_offset = fixupsHeader->imports_offset;
3894 fixupsHeader->imports_count = 0;
3895 fixupsHeader->imports_format = DYLD_CHAINED_IMPORT; // The validate code wants a value here
3896 fixupsHeader->symbols_format = 0;
3897
3898 return std::make_pair(chainedFixupsBufferStart, chainedFixupsBufferEnd);
3899 };
3900
3901 if ( fixupsArePerKext() ) {
3902 // The pageableKC (and sometimes auxKC) has one LC_DYLD_CHAINED_FIXUPS per kext, not 1 total
3903 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
3904 DylibStripMode stripMode, const std::vector<std::string> &dependencies,
3905 Diagnostics& dylibDiag, bool &stop) {
3906 uint64_t loadAddress = ma->preferredLoadAddress();
3907
3908 __block uint64_t numSegments = 0;
3909 __block std::vector<SegmentFixups> segmentFixups;
3910 ma->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
3911 // Third party kexts have writable __TEXT, so we need to add starts for all segments
3912 // other than LINKEDIT
3913 bool segmentCanHaveFixups = false;
3914 if ( appCacheOptions.cacheKind == Options::AppCacheKind::pageableKC ) {
3915 segmentCanHaveFixups = (info.protections & VM_PROT_WRITE) != 0;
3916 } else {
3917 // auxKC
3918 segmentCanHaveFixups = (strcmp(info.segName, "__LINKEDIT") != 0);
3919 }
3920
3921 if ( segmentCanHaveFixups) {
3922 SegmentFixups segmentToFixup;
3923 segmentToFixup.segmentBuffer = (uint8_t*)ma + (info.vmAddr - loadAddress);
3924 segmentToFixup.segmentIndex = info.segIndex;
3925 segmentToFixup.unslidLoadAddress = info.vmAddr;
3926 segmentToFixup.sizeInUse = info.vmSize;
3927 segmentToFixup.starts = nullptr;
3928 segmentToFixup.startsByteSize = 0;
3929 segmentToFixup.numPagesToFixup = numWritablePagesToFixup(info.vmSize);
3930 segmentFixups.push_back(segmentToFixup);
3931 }
3932
3933 ++numSegments;
3934 });
3935
3936
3937 std::pair<const uint8_t*, const uint8_t*> chainedFixupsRange = buildChainedFixups(loadAddress,
3938 numSegments, segmentFixups);
3939 const uint8_t* chainedFixupsBufferStart = chainedFixupsRange.first;
3940 const uint8_t* chainedFixupsBufferEnd = chainedFixupsRange.second;
3941
3942 if ( chainedFixupsBufferStart != chainedFixupsBufferEnd ) {
3943 // Add the load command to our file
3944
3945 uint64_t fixupsOffset = (uint64_t)chainedFixupsBufferStart - (uint64_t)fixupsSubRegion.buffer;
3946 uint64_t fixupsSize = (uint64_t)chainedFixupsBufferEnd - (uint64_t)chainedFixupsBufferStart;
3947
3948 // 64-bit
3949 assert(_is64);
3950 typedef Pointer64<LittleEndian> P;
3951
3952 uint32_t freeSpace = ma->loadCommandsFreeSpace();
3953 assert(freeSpace >= sizeof(macho_linkedit_data_command<P>));
3954 uint8_t* endOfLoadCommands = (uint8_t*)ma + sizeof(macho_header<P>) + ma->sizeofcmds;
3955
3956 // update mach_header to account for new load commands
3957 macho_header<P>* mh = (macho_header<P>*)ma;
3958 mh->set_sizeofcmds(mh->sizeofcmds() + sizeof(macho_linkedit_data_command<P>));
3959 mh->set_ncmds(mh->ncmds() + 1);
3960
3961 // Add the new load command
3962 macho_linkedit_data_command<P>* cmd = (macho_linkedit_data_command<P>*)endOfLoadCommands;
3963 cmd->set_cmd(LC_DYLD_CHAINED_FIXUPS);
3964 cmd->set_cmdsize(sizeof(linkedit_data_command));
3965 cmd->set_dataoff((uint32_t)(_readOnlyRegion.cacheFileOffset + _readOnlyRegion.sizeInUse + fixupsOffset));
3966 cmd->set_datasize((uint32_t)fixupsSize);
3967 }
3968 });
3969
3970 // Also build chained fixups on the top level for the branch stub GOTs
3971 // FIXME: We don't need numRegions() here, but instead just up to an including the RW region
3972 uint64_t segmentCount = numRegions();
3973 __block std::vector<SegmentFixups> segmentFixups;
3974
3975 if ( branchGOTsRegion.sizeInUse != 0 ) {
3976 SegmentFixups segmentToFixup;
3977 segmentToFixup.segmentBuffer = branchGOTsRegion.buffer;
3978 segmentToFixup.segmentIndex = branchGOTsRegion.index;
3979 segmentToFixup.unslidLoadAddress = branchGOTsRegion.unslidLoadAddress;
3980 segmentToFixup.sizeInUse = branchGOTsRegion.sizeInUse;
3981 segmentToFixup.starts = nullptr;
3982 segmentToFixup.startsByteSize = 0;
3983 segmentToFixup.numPagesToFixup = numWritablePagesToFixup(branchGOTsRegion.bufferSize);
3984 segmentFixups.push_back(segmentToFixup);
3985 }
3986
3987 std::pair<const uint8_t*, const uint8_t*> chainedFixupsRange = buildChainedFixups(cacheHeaderRegion.unslidLoadAddress,
3988 segmentCount, segmentFixups);
3989 const uint8_t* chainedFixupsBufferStart = chainedFixupsRange.first;
3990 const uint8_t* chainedFixupsBufferEnd = chainedFixupsRange.second;
3991
3992 if ( chainedFixupsBufferStart != chainedFixupsBufferEnd ) {
3993 uint64_t fixupsOffset = (uint64_t)chainedFixupsBufferStart - (uint64_t)fixupsSubRegion.buffer;
3994 uint64_t fixupsSize = (uint64_t)chainedFixupsBufferEnd - (uint64_t)chainedFixupsBufferStart;
3995 header.chainedFixups->dataoff = (uint32_t)_readOnlyRegion.cacheFileOffset + (uint32_t)_readOnlyRegion.sizeInUse + (uint32_t)fixupsOffset;;
3996 header.chainedFixups->datasize = (uint32_t)fixupsSize;
3997 }
3998 } else {
3999 // Build the chained fixups for just the kernel collection itself
4000 // FIXME: We don't need numRegions() here, but instead just up to an including the RW region
4001 uint64_t segmentCount = numRegions();
4002 __block std::vector<SegmentFixups> segmentFixups;
4003
4004 auto addSegmentStarts = ^(const Region& region) {
4005 SegmentFixups segmentToFixup;
4006 segmentToFixup.segmentBuffer = region.buffer;
4007 segmentToFixup.segmentIndex = region.index;
4008 segmentToFixup.unslidLoadAddress = region.unslidLoadAddress;
4009 segmentToFixup.sizeInUse = region.sizeInUse;
4010 segmentToFixup.starts = nullptr;
4011 segmentToFixup.startsByteSize = 0;
4012 segmentToFixup.numPagesToFixup = numWritablePagesToFixup(region.bufferSize);
4013 segmentFixups.push_back(segmentToFixup);
4014 };
4015
4016 if ( dataConstRegion.sizeInUse != 0 )
4017 addSegmentStarts(dataConstRegion);
4018 if ( branchGOTsRegion.sizeInUse != 0 )
4019 addSegmentStarts(branchGOTsRegion);
4020 if ( readWriteRegion.sizeInUse != 0 )
4021 addSegmentStarts(readWriteRegion);
4022 if ( hibernateRegion.sizeInUse != 0 )
4023 addSegmentStarts(hibernateRegion);
4024 for (const Region& region : nonSplitSegRegions) {
4025 // Assume writable regions have fixups to emit
4026 // Note, third party kext's have __TEXT fixups, so assume all of these have fixups
4027 // LINKEDIT is already elsewhere
4028 addSegmentStarts(region);
4029 }
4030
4031 std::pair<const uint8_t*, const uint8_t*> chainedFixupsRange = buildChainedFixups(cacheHeaderRegion.unslidLoadAddress,
4032 segmentCount, segmentFixups);
4033 const uint8_t* chainedFixupsBufferStart = chainedFixupsRange.first;
4034 const uint8_t* chainedFixupsBufferEnd = chainedFixupsRange.second;
4035
4036 if ( chainedFixupsBufferStart != chainedFixupsBufferEnd ) {
4037 uint64_t fixupsOffset = (uint64_t)chainedFixupsBufferStart - (uint64_t)fixupsSubRegion.buffer;
4038 uint64_t fixupsSize = (uint64_t)chainedFixupsBufferEnd - (uint64_t)chainedFixupsBufferStart;
4039 header.chainedFixups->dataoff = (uint32_t)_readOnlyRegion.cacheFileOffset + (uint32_t)_readOnlyRegion.sizeInUse + (uint32_t)fixupsOffset;;
4040 header.chainedFixups->datasize = (uint32_t)fixupsSize;
4041 }
4042 }
4043
4044 // Move the fixups to the end of __LINKEDIT
4045 if ( classicRelocsBufferStart != classicRelocsBufferEnd ) {
4046 uint64_t fixupsOffset = (uint64_t)classicRelocsBufferStart - (uint64_t)fixupsSubRegion.buffer;
4047 uint64_t fixupsSize = (uint64_t)classicRelocsBufferEnd - (uint64_t)classicRelocsBufferStart;
4048 header.dynSymbolTable->locreloff = (uint32_t)_readOnlyRegion.cacheFileOffset + (uint32_t)_readOnlyRegion.sizeInUse + (uint32_t)fixupsOffset;
4049 header.dynSymbolTable->nlocrel = (uint32_t)fixupsSize / sizeof(fixupsSize);
4050 }
4051
4052 uint64_t fixupsSpace = (uint64_t)byteBuffer.end() - (uint64_t)fixupsSubRegion.buffer;
4053
4054 uint8_t* linkeditEnd = _readOnlyRegion.buffer + _readOnlyRegion.sizeInUse;
4055 memcpy(linkeditEnd, fixupsSubRegion.buffer, fixupsSpace);
4056 uint8_t* fixupsEnd = linkeditEnd + fixupsSpace;
4057
4058 _readOnlyRegion.sizeInUse += align(fixupsSpace, _is64 ? 3 : 2);
4059 _readOnlyRegion.sizeInUse = align(_readOnlyRegion.sizeInUse, 14);
4060 _readOnlyRegion.bufferSize = _readOnlyRegion.sizeInUse;
4061
4062 // Zero the alignment gap, just in case there's any unoptimized LINKEDIT in there
4063 uint8_t* alignedBufferEnd = _readOnlyRegion.buffer + _readOnlyRegion.sizeInUse;
4064 if ( fixupsEnd != alignedBufferEnd ){
4065 memset(fixupsEnd, 0, alignedBufferEnd - fixupsEnd);
4066 }
4067
4068 #if 0
4069 dyld3::MachOAnalyzer* cacheMA = (dyld3::MachOAnalyzer*)header.header;
4070 uint64_t cachePreferredLoadAddress = cacheMA->preferredLoadAddress();
4071 cacheMA->forEachRebase(_diagnostics, false, ^(uint64_t runtimeOffset, bool &stop) {
4072 printf("Rebase: 0x%llx = 0x%llx\n", runtimeOffset, runtimeOffset + cachePreferredLoadAddress);
4073 });
4074 #endif
4075 }
4076
4077 void AppCacheBuilder::allocateBuffer()
4078 {
4079 // Whether to order the regions __TEXT, __DATA, __LINKEDIT or __DATA, __TEXT, __LINKEDIT in VM address order
4080 bool dataRegionFirstInVMOrder = false;
4081 bool hibernateRegionFirstInVMOrder = false;
4082 switch (appCacheOptions.cacheKind) {
4083 case Options::AppCacheKind::none:
4084 assert(0 && "Cache kind should have been set");
4085 break;
4086 case Options::AppCacheKind::kernel:
4087 if ( hibernateAddress != 0 )
4088 hibernateRegionFirstInVMOrder = true;
4089 break;
4090 case Options::AppCacheKind::pageableKC:
4091 // There's no interesting ordering for the pageableKC
4092 break;
4093 case Options::AppCacheKind::kernelCollectionLevel2:
4094 assert(0 && "Unimplemented");
4095 break;
4096 case Options::AppCacheKind::auxKC:
4097 dataRegionFirstInVMOrder = true;
4098 break;
4099 }
4100
4101 // Count how many bytes we need from all our regions
4102 __block uint64_t numRegionFileBytes = 0;
4103 __block uint64_t numRegionVMBytes = 0;
4104
4105 std::vector<std::pair<Region*, uint64_t>> regions;
4106 std::vector<std::pair<Region*, uint64_t>> regionsVMOrder;
4107 std::map<const Region*, uint32_t> sectionsToAddToRegions;
4108
4109 if ( hibernateRegionFirstInVMOrder ) {
4110 regionsVMOrder.push_back({ &hibernateRegion, numRegionVMBytes });
4111 // Pad out the VM offset so that the cache header starts where the base address
4112 // really should be
4113 uint64_t paddedSize = cacheBaseAddress - hibernateAddress;
4114 if ( hibernateRegion.bufferSize > paddedSize ) {
4115 _diagnostics.error("Could not lay out __HIB segment");
4116 return;
4117 }
4118 numRegionVMBytes = paddedSize;
4119 // Set the base address to the hibernate address so that we actually put the
4120 // hibernate segment there
4121 cacheBaseAddress = hibernateAddress;
4122
4123 // Add a section too
4124 sectionsToAddToRegions[&hibernateRegion] = 1;
4125 } else if ( dataRegionFirstInVMOrder ) {
4126 if ( prelinkInfoDict != nullptr ) {
4127 numRegionVMBytes = align(numRegionVMBytes, 14);
4128 regionsVMOrder.push_back({ &prelinkInfoRegion, numRegionVMBytes });
4129 numRegionVMBytes += prelinkInfoRegion.bufferSize;
4130 }
4131 if ( readWriteRegion.sizeInUse != 0 ) {
4132 numRegionVMBytes = align(numRegionVMBytes, 14);
4133 regionsVMOrder.push_back({ &readWriteRegion, numRegionVMBytes });
4134 numRegionVMBytes += readWriteRegion.bufferSize;
4135 }
4136 }
4137
4138 // Cache header
4139 numRegionVMBytes = align(numRegionVMBytes, 14);
4140 regions.push_back({ &cacheHeaderRegion, 0 });
4141 regionsVMOrder.push_back({ &cacheHeaderRegion, numRegionVMBytes });
4142
4143 // Split seg __TEXT
4144 {
4145 // File offset
4146 readOnlyTextRegion.cacheFileOffset = numRegionFileBytes;
4147 numRegionFileBytes += readOnlyTextRegion.bufferSize;
4148 regions.push_back({ &readOnlyTextRegion, 0 });
4149 // VM offset
4150 numRegionVMBytes = align(numRegionVMBytes, 14);
4151 regionsVMOrder.push_back({ &readOnlyTextRegion, numRegionVMBytes });
4152 numRegionVMBytes += readOnlyTextRegion.bufferSize;
4153
4154 // Add a section too
4155 sectionsToAddToRegions[&readOnlyTextRegion] = 1;
4156 }
4157
4158 // Split seg __TEXT_EXEC
4159 if ( readExecuteRegion.sizeInUse != 0 ) {
4160 // File offset
4161 readExecuteRegion.cacheFileOffset = numRegionFileBytes;
4162 numRegionFileBytes += readExecuteRegion.bufferSize;
4163 regions.push_back({ &readExecuteRegion, 0 });
4164 // VM offset
4165 numRegionVMBytes = align(numRegionVMBytes, 14);
4166 regionsVMOrder.push_back({ &readExecuteRegion, numRegionVMBytes });
4167 numRegionVMBytes += readExecuteRegion.bufferSize;
4168 }
4169
4170 // __BRANCH_STUBS
4171 if ( branchStubsRegion.bufferSize != 0 ) {
4172 // File offset
4173 branchStubsRegion.cacheFileOffset = numRegionFileBytes;
4174 numRegionFileBytes += branchStubsRegion.bufferSize;
4175 regions.push_back({ &branchStubsRegion, 0 });
4176 // VM offset
4177 numRegionVMBytes = align(numRegionVMBytes, 14);
4178 regionsVMOrder.push_back({ &branchStubsRegion, numRegionVMBytes });
4179 numRegionVMBytes += branchStubsRegion.bufferSize;
4180 }
4181
4182 // __DATA_CONST
4183 if ( dataConstRegion.sizeInUse != 0 ) {
4184 // File offset
4185 dataConstRegion.cacheFileOffset = numRegionFileBytes;
4186 numRegionFileBytes += dataConstRegion.bufferSize;
4187 regions.push_back({ &dataConstRegion, 0 });
4188 // VM offset
4189 numRegionVMBytes = align(numRegionVMBytes, 14);
4190 regionsVMOrder.push_back({ &dataConstRegion, numRegionVMBytes });
4191 numRegionVMBytes += dataConstRegion.bufferSize;
4192 }
4193
4194 // __BRANCH_GOTS
4195 if ( branchGOTsRegion.bufferSize != 0 ) {
4196 // File offset
4197 branchGOTsRegion.cacheFileOffset = numRegionFileBytes;
4198 numRegionFileBytes += branchGOTsRegion.bufferSize;
4199 regions.push_back({ &branchGOTsRegion, 0 });
4200 // VM offset
4201 numRegionVMBytes = align(numRegionVMBytes, 14);
4202 regionsVMOrder.push_back({ &branchGOTsRegion, numRegionVMBytes });
4203 numRegionVMBytes += branchGOTsRegion.bufferSize;
4204 }
4205
4206 // -sectcreate
4207 // Align to 16k before we lay out all contiguous regions
4208 numRegionFileBytes = align(numRegionFileBytes, 14);
4209 for (CustomSegment& customSegment : customSegments) {
4210 Region& region = *customSegment.parentRegion;
4211
4212 region.cacheFileOffset = numRegionFileBytes;
4213 numRegionFileBytes += region.bufferSize;
4214 regions.push_back({ &region, 0 });
4215 // VM offset
4216 // Note we can't align the vm offset in here
4217 assert( (numRegionVMBytes % 4096) == 0);
4218 regionsVMOrder.push_back({ &region, numRegionVMBytes });
4219 numRegionVMBytes += region.bufferSize;
4220
4221 // Maybe add sections too
4222 uint32_t sectionsToAdd = 0;
4223 if ( customSegment.sections.size() > 1 ) {
4224 // More than one section, so they all need names
4225 sectionsToAdd = (uint32_t)customSegment.sections.size();
4226 } else if ( !customSegment.sections.front().sectionName.empty() ) {
4227 // Only one section, but it has a name
4228 sectionsToAdd = 1;
4229 }
4230 sectionsToAddToRegions[&region] = sectionsToAdd;
4231 }
4232 numRegionVMBytes = align(numRegionVMBytes, 14);
4233
4234 // __PRELINK_INFO
4235 // Align to 16k
4236 numRegionFileBytes = align(numRegionFileBytes, 14);
4237 if ( prelinkInfoDict != nullptr )
4238 {
4239 // File offset
4240 prelinkInfoRegion.cacheFileOffset = numRegionFileBytes;
4241 numRegionFileBytes += prelinkInfoRegion.bufferSize;
4242 regions.push_back({ &prelinkInfoRegion, 0 });
4243
4244 if ( !dataRegionFirstInVMOrder ) {
4245 // VM offset
4246 numRegionVMBytes = align(numRegionVMBytes, 14);
4247 regionsVMOrder.push_back({ &prelinkInfoRegion, numRegionVMBytes });
4248 numRegionVMBytes += prelinkInfoRegion.bufferSize;
4249 }
4250
4251 // Add a section too
4252 sectionsToAddToRegions[&prelinkInfoRegion] = 1;
4253 }
4254
4255 // Split seg __DATA
4256 // Align to 16k
4257 numRegionFileBytes = align(numRegionFileBytes, 14);
4258 if ( readWriteRegion.sizeInUse != 0 ) {
4259 // File offset
4260 readWriteRegion.cacheFileOffset = numRegionFileBytes;
4261 numRegionFileBytes += readWriteRegion.bufferSize;
4262 regions.push_back({ &readWriteRegion, 0 });
4263
4264 if ( !dataRegionFirstInVMOrder ) {
4265 // VM offset
4266 numRegionVMBytes = align(numRegionVMBytes, 14);
4267 regionsVMOrder.push_back({ &readWriteRegion, numRegionVMBytes });
4268 numRegionVMBytes += readWriteRegion.bufferSize;
4269 }
4270 }
4271
4272 // Split seg __HIB
4273 // Align to 16k
4274 numRegionFileBytes = align(numRegionFileBytes, 14);
4275 if ( hibernateRegion.sizeInUse != 0 ) {
4276 // File offset
4277 hibernateRegion.cacheFileOffset = numRegionFileBytes;
4278 numRegionFileBytes += hibernateRegion.bufferSize;
4279 regions.push_back({ &hibernateRegion, 0 });
4280
4281 // VM offset was already handled earlier
4282 }
4283
4284 // Non split seg regions
4285 // Align to 16k before we lay out all contiguous regions
4286 numRegionFileBytes = align(numRegionFileBytes, 14);
4287 for (Region& region : nonSplitSegRegions) {
4288 region.cacheFileOffset = numRegionFileBytes;
4289 numRegionFileBytes += region.bufferSize;
4290 regions.push_back({ &region, 0 });
4291 // VM offset
4292 // Note we can't align the vm offset in here
4293 assert( (numRegionVMBytes % 4096) == 0);
4294 regionsVMOrder.push_back({ &region, numRegionVMBytes });
4295 numRegionVMBytes += region.bufferSize;
4296 }
4297 numRegionVMBytes = align(numRegionVMBytes, 14);
4298
4299 // __LINKEDIT
4300 // Align to 16k
4301 // File offset
4302 numRegionFileBytes = align(numRegionFileBytes, 14);
4303 _readOnlyRegion.cacheFileOffset = numRegionFileBytes;
4304 numRegionFileBytes += _readOnlyRegion.bufferSize;
4305 regions.push_back({ &_readOnlyRegion, 0 });
4306 // VM offset
4307 numRegionVMBytes = align(numRegionVMBytes, 14);
4308 regionsVMOrder.push_back({ &_readOnlyRegion, numRegionVMBytes });
4309 numRegionVMBytes += _readOnlyRegion.bufferSize;
4310
4311 // __LINKEDIT fixups sub region
4312 // Align to 16k
4313 numRegionFileBytes = align(numRegionFileBytes, 14);
4314 if ( fixupsSubRegion.sizeInUse != 0 ) {
4315 fixupsSubRegion.cacheFileOffset = numRegionFileBytes;
4316 numRegionFileBytes += fixupsSubRegion.bufferSize;
4317 //regions.push_back({ &fixupsSubRegion, 0 });
4318
4319 // VM offset
4320 regionsVMOrder.push_back({ &fixupsSubRegion, numRegionVMBytes });
4321 numRegionVMBytes += fixupsSubRegion.bufferSize;
4322 }
4323
4324 const thread_command* unixThread = nullptr;
4325 if (const DylibInfo* dylib = getKernelStaticExecutableInputFile()) {
4326 unixThread = dylib->input->mappedFile.mh->unixThreadLoadCommand();
4327 }
4328
4329 if (_is64) {
4330
4331 const uint64_t cacheHeaderSize = sizeof(mach_header_64);
4332 uint64_t cacheLoadCommandsSize = 0;
4333 uint64_t cacheNumLoadCommands = 0;
4334
4335 // UUID
4336 ++cacheNumLoadCommands;
4337 uint64_t uuidOffset = cacheHeaderSize + cacheLoadCommandsSize;
4338 cacheLoadCommandsSize += sizeof(uuid_command);
4339
4340 // BUILD VERSION
4341 ++cacheNumLoadCommands;
4342 uint64_t buildVersionOffset = cacheHeaderSize + cacheLoadCommandsSize;
4343 cacheLoadCommandsSize += sizeof(build_version_command);
4344
4345 // UNIX THREAD
4346 uint64_t unixThreadOffset = 0;
4347 if ( unixThread != nullptr ) {
4348 ++cacheNumLoadCommands;
4349 unixThreadOffset = cacheHeaderSize + cacheLoadCommandsSize;
4350 cacheLoadCommandsSize += unixThread->cmdsize;
4351 }
4352
4353 // SYMTAB and DYSYMTAB
4354 uint64_t symbolTableOffset = 0;
4355 uint64_t dynSymbolTableOffset = 0;
4356 if (const DylibInfo* dylib = getKernelStaticExecutableInputFile()) {
4357 if ( dylib->input->mappedFile.mh->usesClassicRelocationsInKernelCollection() ) {
4358 // SYMTAB
4359 ++cacheNumLoadCommands;
4360 symbolTableOffset = cacheHeaderSize + cacheLoadCommandsSize;
4361 cacheLoadCommandsSize += sizeof(symtab_command);
4362
4363 // DYSYMTAB
4364 ++cacheNumLoadCommands;
4365 dynSymbolTableOffset = cacheHeaderSize + cacheLoadCommandsSize;
4366 cacheLoadCommandsSize += sizeof(dysymtab_command);
4367 }
4368 }
4369
4370 // LC_DYLD_CHAINED_FIXUPS
4371 // The pageableKC has one LC_DYLD_CHAINED_FIXUPS per kext, and 1 more on the top-level
4372 // for the branch GOTs
4373 uint64_t chainedFixupsOffset = 0;
4374 if ( fixupsSubRegion.bufferSize != 0 ) {
4375 ++cacheNumLoadCommands;
4376 chainedFixupsOffset = cacheHeaderSize + cacheLoadCommandsSize;
4377 cacheLoadCommandsSize += sizeof(linkedit_data_command);
4378 }
4379
4380 // Add an LC_SEGMENT_64 for each region
4381 for (auto& regionAndOffset : regions) {
4382 ++cacheNumLoadCommands;
4383 regionAndOffset.second = cacheHeaderSize + cacheLoadCommandsSize;
4384 cacheLoadCommandsSize += sizeof(segment_command_64);
4385
4386 // Add space for any sections too
4387 auto sectionIt = sectionsToAddToRegions.find(regionAndOffset.first);
4388 if ( sectionIt != sectionsToAddToRegions.end() ) {
4389 uint32_t numSections = sectionIt->second;
4390 cacheLoadCommandsSize += sizeof(section_64) * numSections;
4391 }
4392 }
4393
4394 // Add an LC_FILESET_ENTRY for each dylib
4395 std::vector<std::pair<const DylibInfo*, uint64_t>> dylibs;
4396 for (const auto& dylib : sortedDylibs) {
4397 ++cacheNumLoadCommands;
4398 const char* dylibID = dylib.dylibID.c_str();
4399 dylibs.push_back({ &dylib, cacheHeaderSize + cacheLoadCommandsSize });
4400 uint64_t size = align(sizeof(fileset_entry_command) + strlen(dylibID) + 1, 3);
4401 cacheLoadCommandsSize += size;
4402 }
4403
4404 uint64_t cacheHeaderRegionSize = cacheHeaderSize + cacheLoadCommandsSize;
4405
4406 // Align the app cache header before the rest of the bytes
4407 cacheHeaderRegionSize = align(cacheHeaderRegionSize, 14);
4408
4409 assert(numRegionFileBytes <= numRegionVMBytes);
4410
4411 _allocatedBufferSize = cacheHeaderRegionSize + numRegionVMBytes;
4412
4413 // The fixup format cannot handle a KC over 1GB (64MB for arm64e auxKC). Error out if we exceed that
4414 uint64_t cacheLimit = 1 << 30;
4415 if ( (appCacheOptions.cacheKind == Options::AppCacheKind::auxKC) && (_options.archs == &dyld3::GradedArchs::arm64e) )
4416 cacheLimit = 64 * (1 << 20);
4417 if ( _allocatedBufferSize >= cacheLimit ) {
4418 _diagnostics.error("kernel collection size exceeds maximum size of %lld vs actual size of %lld",
4419 cacheLimit, _allocatedBufferSize);
4420 return;
4421 }
4422
4423 if ( vm_allocate(mach_task_self(), &_fullAllocatedBuffer, _allocatedBufferSize, VM_FLAGS_ANYWHERE) != 0 ) {
4424 _diagnostics.error("could not allocate buffer");
4425 return;
4426 }
4427
4428 // Assign region vm and buffer addresses now that we know the size of
4429 // the cache header
4430 {
4431 // All vm offsets prior to the cache header are already correct
4432 // All those after the cache header need to be shifted by the cache
4433 // header size
4434 bool seenCacheHeader = false;
4435 for (const auto& regionAndVMOffset : regionsVMOrder) {
4436 Region* region = regionAndVMOffset.first;
4437 uint64_t vmOffset = regionAndVMOffset.second;
4438 region->unslidLoadAddress = cacheBaseAddress + vmOffset;
4439 if ( seenCacheHeader ) {
4440 // Shift by the cache header size
4441 region->unslidLoadAddress += cacheHeaderRegionSize;
4442 } else {
4443 // The offset is correct but add in the base address
4444 seenCacheHeader = (region == &cacheHeaderRegion);
4445 }
4446 region->buffer = (uint8_t*)_fullAllocatedBuffer + (region->unslidLoadAddress - cacheBaseAddress);
4447 }
4448 }
4449
4450 // Cache header
4451 cacheHeaderRegion.bufferSize = cacheHeaderRegionSize;
4452 cacheHeaderRegion.sizeInUse = cacheHeaderRegion.bufferSize;
4453 cacheHeaderRegion.cacheFileOffset = 0;
4454 cacheHeaderRegion.permissions = VM_PROT_READ;
4455 cacheHeaderRegion.name = "__TEXT";
4456
4457 #if 0
4458 for (const auto& regionAndVMOffset : regionsVMOrder) {
4459 printf("0x%llx : %s\n", regionAndVMOffset.first->unslidLoadAddress, regionAndVMOffset.first->name.c_str());
4460 }
4461 #endif
4462
4463 CacheHeader64& header = cacheHeader;
4464 header.header = (mach_header_64*)cacheHeaderRegion.buffer;
4465 header.numLoadCommands = cacheNumLoadCommands;
4466 header.loadCommandsSize = cacheLoadCommandsSize;
4467 header.uuid = (uuid_command*)(cacheHeaderRegion.buffer + uuidOffset);
4468 header.buildVersion = (build_version_command*)(cacheHeaderRegion.buffer + buildVersionOffset);
4469 if ( unixThread != nullptr ) {
4470 header.unixThread = (thread_command*)(cacheHeaderRegion.buffer + unixThreadOffset);
4471 // Copy the contents here while we have the source pointer available
4472 memcpy(header.unixThread, unixThread, unixThread->cmdsize);
4473 }
4474
4475 if ( symbolTableOffset != 0 ) {
4476 header.symbolTable = (symtab_command*)(cacheHeaderRegion.buffer + symbolTableOffset);
4477 }
4478
4479 if ( dynSymbolTableOffset != 0 ) {
4480 header.dynSymbolTable = (dysymtab_command*)(cacheHeaderRegion.buffer + dynSymbolTableOffset);
4481 }
4482
4483 if ( chainedFixupsOffset != 0 ) {
4484 header.chainedFixups = (linkedit_data_command*)(cacheHeaderRegion.buffer + chainedFixupsOffset);
4485 }
4486
4487 for (auto& regionAndOffset : regions) {
4488 assert(regionAndOffset.first->permissions != 0);
4489 segment_command_64* loadCommand = (segment_command_64*)(cacheHeaderRegion.buffer + regionAndOffset.second);
4490 header.segments.push_back({ loadCommand, regionAndOffset.first });
4491 }
4492 for (const auto& dylibAndOffset : dylibs) {
4493 fileset_entry_command* loadCommand = (fileset_entry_command*)(cacheHeaderRegion.buffer + dylibAndOffset.second);
4494 header.dylibs.push_back({ loadCommand, dylibAndOffset.first });
4495 }
4496
4497 // Move the offsets of all the other regions
4498 // Split seg __TEXT
4499 readOnlyTextRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4500
4501 // Split seg __TEXT_EXEC
4502 readExecuteRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4503
4504 // __BRANCH_STUBS
4505 branchStubsRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4506
4507 // Split seg __DATA_CONST
4508 dataConstRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4509
4510 // __BRANCH_GOTS
4511 branchGOTsRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4512
4513 // Split seg __DATA
4514 readWriteRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4515
4516 // Split seg __HIB
4517 hibernateRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4518
4519 // -sectcreate
4520 for (Region& region : customDataRegions) {
4521 region.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4522 }
4523
4524 // Non split seg regions
4525 for (Region& region : nonSplitSegRegions) {
4526 region.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4527 }
4528
4529 // __PRELINK_INFO
4530 prelinkInfoRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4531
4532 // __LINKEDIT
4533 _readOnlyRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4534
4535 // __LINKEDIT fixups sub region
4536 fixupsSubRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
4537 } else {
4538 assert(false);
4539 }
4540 }
4541
4542 void AppCacheBuilder::generateCacheHeader() {
4543 if ( !_is64 )
4544 assert(0 && "Unimplemented");
4545
4546 {
4547 // 64-bit
4548 typedef Pointer64<LittleEndian> P;
4549 CacheHeader64& header = cacheHeader;
4550
4551 // Write the header
4552 macho_header<P>* mh = (macho_header<P>*)header.header;
4553 mh->set_magic(MH_MAGIC_64);
4554 mh->set_cputype(_options.archs->_orderedCpuTypes[0].type);
4555 mh->set_cpusubtype(_options.archs->_orderedCpuTypes[0].subtype);
4556 mh->set_filetype(MH_FILESET);
4557 mh->set_ncmds((uint32_t)header.numLoadCommands);
4558 mh->set_sizeofcmds((uint32_t)header.loadCommandsSize);
4559 mh->set_flags(0);
4560 mh->set_reserved(0);
4561
4562 // FIXME: Move this to writeAppCacheHeader
4563 {
4564 macho_uuid_command<P>* cmd = (macho_uuid_command<P>*)header.uuid;
4565 cmd->set_cmd(LC_UUID);
4566 cmd->set_cmdsize(sizeof(uuid_command));
4567 cmd->set_uuid((uuid_t){});
4568 }
4569
4570 // FIXME: Move this to writeAppCacheHeader
4571 {
4572 macho_build_version_command<P>* cmd = (macho_build_version_command<P>*)header.buildVersion;
4573 cmd->set_cmd(LC_BUILD_VERSION);
4574 cmd->set_cmdsize(sizeof(build_version_command));
4575 cmd->set_platform((uint32_t)_options.platform);
4576 cmd->set_minos(0);
4577 cmd->set_sdk(0);
4578 cmd->set_ntools(0);
4579 }
4580
4581 // FIXME: Move this to writeAppCacheHeader
4582 // LC_UNIXTHREAD was already memcpy()'ed from the source dylib when we allocated space for it
4583 // We still need to slide its PC value here before we lose the information about the slide
4584 if ( header.unixThread != nullptr ) {
4585 const DylibInfo* dylib = getKernelStaticExecutableInputFile();
4586 const dyld3::MachOAnalyzer* ma = dylib->input->mappedFile.mh;
4587 ma->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
4588 uint64_t startAddress = dylib->input->mappedFile.mh->entryAddrFromThreadCmd(header.unixThread);
4589 if ( (startAddress < info.vmAddr) || (startAddress >= (info.vmAddr + info.vmSize)) )
4590 return;
4591
4592 uint64_t segSlide = dylib->cacheLocation[info.segIndex].dstCacheUnslidAddress - info.vmAddr;
4593 startAddress += segSlide;
4594
4595 macho_thread_command<P>* cmd = (macho_thread_command<P>*)header.unixThread;
4596 cmd->set_thread_register(ma->entryAddrRegisterIndexForThreadCmd(), startAddress);
4597
4598 stop = true;
4599 });
4600 }
4601
4602 if ( header.symbolTable != nullptr ) {
4603 macho_symtab_command<P>* cmd = (macho_symtab_command<P>*)header.symbolTable;
4604 cmd->set_cmd(LC_SYMTAB);
4605 cmd->set_cmdsize(sizeof(symtab_command));
4606 cmd->set_symoff(0);
4607 cmd->set_nsyms(0);
4608 cmd->set_stroff(0);
4609 cmd->set_strsize(0);
4610 }
4611
4612 if ( header.dynSymbolTable != nullptr ) {
4613 macho_dysymtab_command<P>* cmd = (macho_dysymtab_command<P>*)header.dynSymbolTable;
4614 cmd->set_cmd(LC_DYSYMTAB);
4615 cmd->set_cmdsize(sizeof(dysymtab_command));
4616 cmd->set_ilocalsym(0);
4617 cmd->set_nlocalsym(0);
4618 cmd->set_iextdefsym(0);
4619 cmd->set_nextdefsym(0);
4620 cmd->set_iundefsym(0);
4621 cmd->set_nundefsym(0);
4622 cmd->set_tocoff(0);
4623 cmd->set_ntoc(0);
4624 cmd->set_modtaboff(0);
4625 cmd->set_nmodtab(0);
4626 cmd->set_extrefsymoff(0);
4627 cmd->set_nextrefsyms(0);
4628 cmd->set_indirectsymoff(0);
4629 cmd->set_nindirectsyms(0);
4630 cmd->set_extreloff(0);
4631 cmd->set_nextrel(0);
4632 cmd->set_locreloff(0);
4633 cmd->set_nlocrel(0);
4634 }
4635
4636 if ( header.chainedFixups != nullptr ) {
4637 macho_linkedit_data_command<P>* cmd = (macho_linkedit_data_command<P>*)header.chainedFixups;
4638 cmd->set_cmd(LC_DYLD_CHAINED_FIXUPS);
4639 cmd->set_cmdsize(sizeof(linkedit_data_command));
4640 cmd->set_dataoff(0);
4641 cmd->set_datasize(0);
4642 }
4643
4644 // FIXME: Move this to writeAppCacheHeader
4645 uint64_t segmentIndex = 0;
4646 for (CacheHeader64::SegmentCommandAndRegion& cmdAndInfo : header.segments) {
4647 macho_segment_command<P>* cmd = (macho_segment_command<P>*)cmdAndInfo.first;
4648 Region* region = cmdAndInfo.second;
4649 region->index = segmentIndex;
4650 ++segmentIndex;
4651
4652 assert(region->permissions != 0);
4653
4654 const char* name = region->name.c_str();
4655
4656 cmd->set_cmd(LC_SEGMENT_64);
4657 cmd->set_cmdsize(sizeof(segment_command_64));
4658 cmd->set_segname(name);
4659 cmd->set_vmaddr(region->unslidLoadAddress);
4660 cmd->set_vmsize(region->sizeInUse);
4661 cmd->set_fileoff(region->cacheFileOffset);
4662 cmd->set_filesize(region->sizeInUse);
4663 cmd->set_maxprot(region->permissions);
4664 cmd->set_initprot(region->permissions);
4665 cmd->set_nsects(0);
4666 cmd->set_flags(0);
4667
4668 if ( region == &readOnlyTextRegion ) {
4669 // __PRELINK_TEXT should also get a section
4670 cmd->set_cmdsize(cmd->cmdsize() + sizeof(section_64));
4671 cmd->set_nsects(1);
4672
4673 macho_section<P>* section = (macho_section<P>*)((uint64_t)cmd + sizeof(*cmd));
4674 section->set_sectname("__text");
4675 section->set_segname(name);
4676 section->set_addr(region->unslidLoadAddress);
4677 section->set_size(region->sizeInUse);
4678 section->set_offset((uint32_t)region->cacheFileOffset);
4679 section->set_align(0);
4680 section->set_reloff(0);
4681 section->set_nreloc(0);
4682 section->set_flags(S_REGULAR | S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS);
4683 section->set_reserved1(0);
4684 section->set_reserved2(0);
4685 } else if ( region == &prelinkInfoRegion ) {
4686 // __PRELINK_INFO should also get a section
4687 cmd->set_cmdsize(cmd->cmdsize() + sizeof(section_64));
4688 cmd->set_nsects(1);
4689
4690 macho_section<P>* section = (macho_section<P>*)((uint64_t)cmd + sizeof(*cmd));
4691 section->set_sectname("__info");
4692 section->set_segname(name);
4693 section->set_addr(region->unslidLoadAddress);
4694 section->set_size(region->sizeInUse);
4695 section->set_offset((uint32_t)region->cacheFileOffset);
4696 section->set_align(0);
4697 section->set_reloff(0);
4698 section->set_nreloc(0);
4699 section->set_flags(S_REGULAR);
4700 section->set_reserved1(0);
4701 section->set_reserved2(0);
4702 } else if ( region == &hibernateRegion ) {
4703 // __HIB should also get a section
4704 cmd->set_cmdsize(cmd->cmdsize() + sizeof(section_64));
4705 cmd->set_nsects(1);
4706
4707 macho_section<P>* section = (macho_section<P>*)((uint64_t)cmd + sizeof(*cmd));
4708 section->set_sectname("__text");
4709 section->set_segname(name);
4710 section->set_addr(region->unslidLoadAddress);
4711 section->set_size(region->sizeInUse);
4712 section->set_offset((uint32_t)region->cacheFileOffset);
4713 section->set_align(0);
4714 section->set_reloff(0);
4715 section->set_nreloc(0);
4716 section->set_flags(S_REGULAR | S_ATTR_SOME_INSTRUCTIONS);
4717 section->set_reserved1(0);
4718 section->set_reserved2(0);
4719 } else {
4720 // Custom segments may have sections
4721 for (CustomSegment &customSegment : customSegments) {
4722 if ( region != customSegment.parentRegion )
4723 continue;
4724
4725 // Found a segment for this region. Now work out how many sections to emit
4726 // Maybe add sections too
4727 uint32_t sectionsToAdd = 0;
4728 if ( customSegment.sections.size() > 1 ) {
4729 // More than one section, so they all need names
4730 sectionsToAdd = (uint32_t)customSegment.sections.size();
4731 } else if ( !customSegment.sections.front().sectionName.empty() ) {
4732 // Only one section, but it has a name
4733 sectionsToAdd = 1;
4734 } else {
4735 // Only 1 section, and it has no name, so don't add a section
4736 continue;
4737 }
4738
4739 cmd->set_cmdsize(cmd->cmdsize() + (sizeof(section_64) * sectionsToAdd));
4740 cmd->set_nsects(sectionsToAdd);
4741 uint8_t* bufferPos = (uint8_t*)cmd + sizeof(*cmd);
4742 for (const CustomSegment::CustomSection& customSection : customSegment.sections) {
4743 macho_section<P>* section = (macho_section<P>*)bufferPos;
4744 section->set_sectname(customSection.sectionName.c_str());
4745 section->set_segname(name);
4746 section->set_addr(region->unslidLoadAddress + customSection.offsetInRegion);
4747 section->set_size(customSection.data.size());
4748 section->set_offset((uint32_t)(region->cacheFileOffset + customSection.offsetInRegion));
4749 section->set_align(0);
4750 section->set_reloff(0);
4751 section->set_nreloc(0);
4752 section->set_flags(S_REGULAR);
4753 section->set_reserved1(0);
4754 section->set_reserved2(0);
4755
4756 bufferPos += sizeof(section_64);
4757 }
4758 }
4759 }
4760 }
4761
4762 // Write the dylibs. These are all we need for now to be able to walk the
4763 // app cache
4764 for (CacheHeader64::DylibCommandAndInfo& cmdAndInfo : header.dylibs) {
4765 macho_fileset_entry_command<P>* cmd = (macho_fileset_entry_command<P>*)cmdAndInfo.first;
4766 const DylibInfo* dylib = cmdAndInfo.second;
4767
4768 const char* dylibID = dylib->dylibID.c_str();
4769 uint64_t size = align(sizeof(fileset_entry_command) + strlen(dylibID) + 1, 3);
4770
4771 cmd->set_cmd(LC_FILESET_ENTRY);
4772 cmd->set_cmdsize((uint32_t)size);
4773 cmd->set_vmaddr(dylib->cacheLocation[0].dstCacheUnslidAddress);
4774 cmd->set_fileoff(dylib->cacheLocation[0].dstCacheFileOffset);
4775 cmd->set_entry_id(dylibID);
4776 }
4777 }
4778 }
4779
4780 void AppCacheBuilder::generatePrelinkInfo() {
4781 if ( prelinkInfoDict == nullptr ) {
4782 // The kernel doesn't need a prelink dictionary just for itself
4783 bool needsPrelink = true;
4784 if ( appCacheOptions.cacheKind == Options::AppCacheKind::kernel ) {
4785 if ( sortedDylibs.size() == 1 )
4786 needsPrelink = false;
4787 }
4788 if ( needsPrelink ) {
4789 _diagnostics.error("Expected prelink info dictionary");
4790 }
4791 return;
4792 }
4793
4794 CFMutableArrayRef arrayRef = (CFMutableArrayRef)CFDictionaryGetValue(prelinkInfoDict,
4795 CFSTR("_PrelinkInfoDictionary"));
4796 if ( arrayRef == nullptr ) {
4797 _diagnostics.error("Expected prelink info dictionary array");
4798 return;
4799 }
4800
4801 typedef std::pair<const dyld3::MachOAnalyzer*, Diagnostics*> DylibAndDiag;
4802 __block std::unordered_map<std::string_view, DylibAndDiag> dylibs;
4803 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
4804 DylibStripMode stripMode, const std::vector<std::string>& dependencies,
4805 Diagnostics& dylibDiag, bool& stop) {
4806 dylibs[dylibID] = { ma, &dylibDiag };
4807 });
4808 for (const InputDylib& dylib : codelessKexts) {
4809 dylibs[dylib.dylibID] = { nullptr, nullptr };
4810 }
4811
4812 __block std::list<std::string> nonASCIIStrings;
4813 auto getString = ^(Diagnostics& diags, CFStringRef symbolNameRef) {
4814 const char* symbolName = CFStringGetCStringPtr(symbolNameRef, kCFStringEncodingUTF8);
4815 if ( symbolName != nullptr )
4816 return symbolName;
4817
4818 CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef), kCFStringEncodingUTF8);
4819 char buffer[len + 1];
4820 if ( !CFStringGetCString(symbolNameRef, buffer, len, kCFStringEncodingUTF8) ) {
4821 diags.error("Could not convert string to ASCII");
4822 return (const char*)nullptr;
4823 }
4824 buffer[len] = '\0';
4825 nonASCIIStrings.push_back(buffer);
4826 return nonASCIIStrings.back().c_str();
4827 };
4828
4829 bool badKext = false;
4830 CFIndex arrayCount = CFArrayGetCount(arrayRef);
4831 for (CFIndex i = 0; i != arrayCount; ++i) {
4832 CFMutableDictionaryRef dictRef = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(arrayRef, i);
4833
4834 CFStringRef bundleIDRef = (CFStringRef)CFDictionaryGetValue(dictRef, CFSTR("CFBundleIdentifier"));
4835 if ( bundleIDRef == nullptr ) {
4836 _diagnostics.error("Cannot get bundle ID for dylib");
4837 return;
4838 }
4839
4840 const char* bundleIDStr = getString(_diagnostics, bundleIDRef);
4841 if ( _diagnostics.hasError() )
4842 return;
4843
4844 auto dylibIt = dylibs.find(bundleIDStr);
4845 if ( dylibIt == dylibs.end() ) {
4846 _diagnostics.error("Cannot get dylib for bundle ID %s", bundleIDStr);
4847 return;
4848 }
4849 const dyld3::MachOAnalyzer *ma = dylibIt->second.first;
4850 Diagnostics* dylibDiag = dylibIt->second.second;
4851 // Skip codeless kext's
4852 if ( ma == nullptr )
4853 continue;
4854 uint64_t loadAddress = ma->preferredLoadAddress();
4855
4856 // _PrelinkExecutableLoadAddr
4857 CFNumberRef loadAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &loadAddress);
4858 CFDictionarySetValue(dictRef, CFSTR("_PrelinkExecutableLoadAddr"), loadAddrRef);
4859 CFRelease(loadAddrRef);
4860
4861 // _PrelinkExecutableSourceAddr
4862 CFNumberRef sourceAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &loadAddress);
4863 CFDictionarySetValue(dictRef, CFSTR("_PrelinkExecutableSourceAddr"), sourceAddrRef);
4864 CFRelease(sourceAddrRef);
4865
4866 // _PrelinkKmodInfo
4867 __block uint64_t kmodInfoAddress = 0;
4868
4869 // Check for a global first
4870 __block bool found = false;
4871 {
4872 dyld3::MachOAnalyzer::FoundSymbol foundInfo;
4873 found = ma->findExportedSymbol(_diagnostics, "_kmod_info", true, foundInfo, nullptr);
4874 if ( found ) {
4875 kmodInfoAddress = loadAddress + foundInfo.value;
4876 }
4877 }
4878 // And fall back to a local if we need to
4879 if ( !found ) {
4880 ma->forEachLocalSymbol(_diagnostics, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type,
4881 uint8_t n_sect, uint16_t n_desc, bool& stop) {
4882 if ( strcmp(aSymbolName, "_kmod_info") == 0 ) {
4883 kmodInfoAddress = n_value;
4884 found = true;
4885 stop = true;
4886 }
4887 });
4888 }
4889
4890 if ( found ) {
4891 CFNumberRef kmodInfoAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &kmodInfoAddress);
4892 CFDictionarySetValue(dictRef, CFSTR("_PrelinkKmodInfo"), kmodInfoAddrRef);
4893 CFRelease(kmodInfoAddrRef);
4894
4895 // Since we have a reference to the kmod info anyway, set its address field to the correct value
4896 assert(_is64);
4897 uint64_t kmodInfoVMOffset = kmodInfoAddress - loadAddress;
4898 dyld3::MachOAppCache::KModInfo64_v1* kmodInfo = (dyld3::MachOAppCache::KModInfo64_v1*)((uint8_t*)ma + kmodInfoVMOffset);
4899 if ( kmodInfo->info_version != 1 ) {
4900 dylibDiag->error("unsupported kmod_info version of %d", kmodInfo->info_version);
4901 badKext = true;
4902 continue;
4903 }
4904 __block uint64_t textSegmnentVMAddr = 0;
4905 __block uint64_t textSegmnentVMSize = 0;
4906 ma->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
4907 if ( !strcmp(info.segName, "__TEXT") ) {
4908 textSegmnentVMAddr = info.vmAddr;
4909 textSegmnentVMSize = info.vmSize;
4910 stop = true;
4911 }
4912 });
4913 kmodInfo->address = textSegmnentVMAddr;
4914 kmodInfo->size = textSegmnentVMSize;
4915 }
4916 }
4917
4918 CFErrorRef errorRef = nullptr;
4919 CFDataRef xmlData = CFPropertyListCreateData(kCFAllocatorDefault, prelinkInfoDict,
4920 kCFPropertyListXMLFormat_v1_0, 0, &errorRef);
4921 if (errorRef != nullptr) {
4922 CFStringRef errorString = CFErrorCopyDescription(errorRef);
4923 _diagnostics.error("Could not serialise plist because :%s",
4924 CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
4925 CFRelease(xmlData);
4926 CFRelease(errorRef);
4927 return;
4928 } else {
4929 CFIndex xmlDataLength = CFDataGetLength(xmlData);
4930 if ( xmlDataLength > prelinkInfoRegion.bufferSize ) {
4931 _diagnostics.error("Overflow in prelink info segment. 0x%llx vs 0x%llx",
4932 (uint64_t)xmlDataLength, prelinkInfoRegion.bufferSize);
4933 CFRelease(xmlData);
4934 return;
4935 }
4936
4937 // Write the prelink info in to the buffer
4938 memcpy(prelinkInfoRegion.buffer, CFDataGetBytePtr(xmlData), xmlDataLength);
4939 CFRelease(xmlData);
4940 }
4941
4942 if ( badKext && _diagnostics.noError() ) {
4943 _diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
4944 }
4945 }
4946
4947 bool AppCacheBuilder::addCustomSection(const std::string& segmentName,
4948 CustomSegment::CustomSection section) {
4949 for (CustomSegment& segment: customSegments) {
4950 if ( segment.segmentName != segmentName )
4951 continue;
4952
4953 // Found a matching segment
4954 // Make sure we don't have a section with this name already
4955 if ( section.sectionName.empty() ) {
4956 // We can't add a segment only section if other sections exist
4957 _diagnostics.error("Cannot add empty section name with segment '%s' as other sections exist on that segment",
4958 segmentName.c_str());
4959 return false;
4960 }
4961
4962 for (const CustomSegment::CustomSection& existingSection : segment.sections) {
4963 if ( existingSection.sectionName.empty() ) {
4964 // We can't add a section with a name if an existing section exists with no name
4965 _diagnostics.error("Cannot add section named '%s' with segment '%s' as segment has existing nameless section",
4966 segmentName.c_str(), section.sectionName.c_str());
4967 return false;
4968 }
4969 if ( existingSection.sectionName == section.sectionName ) {
4970 // We can't add a section with the same name as an existing one
4971 _diagnostics.error("Cannot add section named '%s' with segment '%s' as section already exists",
4972 segmentName.c_str(), section.sectionName.c_str());
4973 return false;
4974 }
4975 }
4976 segment.sections.push_back(section);
4977 return true;
4978 }
4979
4980 // Didn't find a segment, so add a new one
4981 CustomSegment segment;
4982 segment.segmentName = segmentName;
4983 segment.sections.push_back(section);
4984 customSegments.push_back(segment);
4985 return true;
4986 }
4987
4988 void AppCacheBuilder::setExistingKernelCollection(const dyld3::MachOAppCache* appCacheMA) {
4989 existingKernelCollection = appCacheMA;
4990 }
4991
4992 void AppCacheBuilder::setExistingPageableKernelCollection(const dyld3::MachOAppCache* appCacheMA) {
4993 pageableKernelCollection = appCacheMA;
4994 }
4995
4996 void AppCacheBuilder::setExtraPrelinkInfo(CFDictionaryRef dictionary) {
4997 extraPrelinkInfo = dictionary;
4998 }
4999
5000
5001 inline uint32_t absolutetime_to_milliseconds(uint64_t abstime)
5002 {
5003 return (uint32_t)(abstime/1000/1000);
5004 }
5005
5006 void AppCacheBuilder::buildAppCache(const std::vector<InputDylib>& dylibs)
5007 {
5008 uint64_t t1 = mach_absolute_time();
5009
5010 // make copy of dylib list and sort
5011 makeSortedDylibs(dylibs);
5012
5013 // Set the chained pointer format
5014 // x86_64 uses unaligned fixups
5015 if ( (_options.archs == &dyld3::GradedArchs::x86_64) || (_options.archs == &dyld3::GradedArchs::x86_64h) ) {
5016 chainedPointerFormat = DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE;
5017 } else {
5018 chainedPointerFormat = DYLD_CHAINED_PTR_64_KERNEL_CACHE;
5019 }
5020
5021 // If we have only codeless kexts, then error out
5022 if ( sortedDylibs.empty() ) {
5023 if ( codelessKexts.empty() ) {
5024 _diagnostics.error("No binaries or codeless kexts were provided");
5025 } else {
5026 _diagnostics.error("Cannot build collection without binaries as only %lx codeless kexts provided",
5027 codelessKexts.size());
5028 }
5029 return;
5030 }
5031
5032 // assign addresses for each segment of each dylib in new cache
5033 assignSegmentRegionsAndOffsets();
5034 if ( _diagnostics.hasError() )
5035 return;
5036
5037 // allocate space used by largest possible cache plus room for LINKEDITS before optimization
5038 allocateBuffer();
5039 if ( _diagnostics.hasError() )
5040 return;
5041
5042 assignSegmentAddresses();
5043
5044 generateCacheHeader();
5045
5046 // copy all segments into cache
5047 uint64_t t2 = mach_absolute_time();
5048 copyRawSegments();
5049
5050 // rebase all dylibs for new location in cache
5051 uint64_t t3 = mach_absolute_time();
5052 if ( appCacheOptions.cacheKind == Options::AppCacheKind::auxKC ) {
5053 // We can have text fixups in the auxKC so ASLR should just track the whole buffer
5054 __block const Region* firstDataRegion = nullptr;
5055 __block const Region* lastDataRegion = nullptr;
5056 forEachRegion(^(const Region &region) {
5057 if ( (firstDataRegion == nullptr) || (region.buffer < firstDataRegion->buffer) )
5058 firstDataRegion = &region;
5059 if ( (lastDataRegion == nullptr) || (region.buffer > lastDataRegion->buffer) )
5060 lastDataRegion = &region;
5061 });
5062
5063 if ( firstDataRegion != nullptr ) {
5064 uint64_t size = (lastDataRegion->buffer - firstDataRegion->buffer) + lastDataRegion->bufferSize;
5065 _aslrTracker.setDataRegion(firstDataRegion->buffer, size);
5066 }
5067 } else {
5068 const Region* firstDataRegion = nullptr;
5069 const Region* lastDataRegion = nullptr;
5070 if ( hibernateRegion.sizeInUse != 0 ) {
5071 firstDataRegion = &hibernateRegion;
5072 lastDataRegion = &hibernateRegion;
5073 }
5074
5075 if ( dataConstRegion.sizeInUse != 0 ) {
5076 if ( firstDataRegion == nullptr )
5077 firstDataRegion = &dataConstRegion;
5078 if ( (lastDataRegion == nullptr) || (dataConstRegion.buffer > lastDataRegion->buffer) )
5079 lastDataRegion = &dataConstRegion;
5080 }
5081
5082 if ( branchGOTsRegion.bufferSize != 0 ) {
5083 if ( firstDataRegion == nullptr )
5084 firstDataRegion = &branchGOTsRegion;
5085 if ( (lastDataRegion == nullptr) || (branchGOTsRegion.buffer > lastDataRegion->buffer) )
5086 lastDataRegion = &branchGOTsRegion;
5087 }
5088
5089 if ( readWriteRegion.sizeInUse != 0 ) {
5090 // __DATA might be before __DATA_CONST in an auxKC
5091 if ( (firstDataRegion == nullptr) || (readWriteRegion.buffer < firstDataRegion->buffer) )
5092 firstDataRegion = &readWriteRegion;
5093 if ( (lastDataRegion == nullptr) || (readWriteRegion.buffer > lastDataRegion->buffer) )
5094 lastDataRegion = &readWriteRegion;
5095 }
5096
5097 for (const Region& region : nonSplitSegRegions) {
5098 // Assume writable regions have fixups to emit
5099 // Note, third party kext's have __TEXT fixups, so assume all of these have fixups
5100 // LINKEDIT is already elsewhere
5101 if ( readWriteRegion.sizeInUse != 0 ) {
5102 assert(region.buffer >= readWriteRegion.buffer);
5103 }
5104 if ( firstDataRegion == nullptr )
5105 firstDataRegion = &region;
5106 if ( (lastDataRegion == nullptr) || (region.buffer > lastDataRegion->buffer) )
5107 lastDataRegion = &region;
5108 }
5109
5110 if ( firstDataRegion != nullptr ) {
5111 uint64_t size = (lastDataRegion->buffer - firstDataRegion->buffer) + lastDataRegion->bufferSize;
5112 _aslrTracker.setDataRegion(firstDataRegion->buffer, size);
5113 }
5114 }
5115 adjustAllImagesForNewSegmentLocations(cacheBaseAddress, _aslrTracker, nullptr, nullptr);
5116 if ( _diagnostics.hasError() )
5117 return;
5118
5119 // Once we have the final addresses, we can emit the prelink info segment
5120 generatePrelinkInfo();
5121 if ( _diagnostics.hasError() )
5122 return;
5123
5124 // build ImageArray for dyld3, which has side effect of binding all cached dylibs
5125 uint64_t t4 = mach_absolute_time();
5126 processFixups();
5127 if ( _diagnostics.hasError() )
5128 return;
5129
5130 uint64_t t5 = mach_absolute_time();
5131
5132 // optimize away stubs
5133 uint64_t t6 = mach_absolute_time();
5134 {
5135 __block std::vector<std::pair<const mach_header*, const char*>> images;
5136 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
5137 DylibStripMode stripMode, const std::vector<std::string>& dependencies,
5138 Diagnostics& dylibDiag, bool& stop) {
5139 images.push_back({ ma, dylibID.c_str() });
5140 });
5141 // FIXME: Should we keep the same never stub eliminate symbols? Eg, for gmalloc.
5142 const char* const neverStubEliminateSymbols[] = {
5143 nullptr
5144 };
5145
5146 uint64_t cacheUnslidAddr = cacheBaseAddress;
5147 int64_t cacheSlide = (long)_fullAllocatedBuffer - cacheUnslidAddr;
5148 optimizeAwayStubs(images, cacheSlide, cacheUnslidAddr,
5149 nullptr, neverStubEliminateSymbols);
5150 }
5151
5152 // FIPS seal corecrypto, This must be done after stub elimination (so that __TEXT,__text is not changed after sealing)
5153 fipsSign();
5154
5155 // merge and compact LINKEDIT segments
5156 uint64_t t7 = mach_absolute_time();
5157 {
5158 __block std::vector<std::tuple<const mach_header*, const char*, DylibStripMode>> images;
5159 __block std::set<const mach_header*> imagesToStrip;
5160 __block const dyld3::MachOAnalyzer* kernelMA = nullptr;
5161 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
5162 DylibStripMode stripMode, const std::vector<std::string>& dependencies,
5163 Diagnostics& dylibDiag, bool& stop) {
5164 if ( stripMode == DylibStripMode::stripNone ) {
5165 // If the binary didn't have a strip mode, then use the global mode
5166 switch (appCacheOptions.cacheKind) {
5167 case Options::AppCacheKind::none:
5168 assert("Unhandled kind");
5169 break;
5170 case Options::AppCacheKind::kernel:
5171 switch (appCacheOptions.stripMode) {
5172 case Options::StripMode::none:
5173 break;
5174 case Options::StripMode::all:
5175 stripMode = CacheBuilder::DylibStripMode::stripAll;
5176 break;
5177 case Options::StripMode::allExceptKernel:
5178 // Strip all binaries which are not the kernel
5179 if ( kernelMA == nullptr ) {
5180 kernelMA = getKernelStaticExecutableFromCache();
5181 }
5182 if ( ma != kernelMA )
5183 stripMode = CacheBuilder::DylibStripMode::stripAll;
5184 break;
5185 }
5186 break;
5187 case Options::AppCacheKind::pageableKC:
5188 assert("Unhandled kind");
5189 break;
5190 case Options::AppCacheKind::kernelCollectionLevel2:
5191 assert("Unhandled kind");
5192 break;
5193 case Options::AppCacheKind::auxKC:
5194 assert("Unhandled kind");
5195 break;
5196 }
5197 }
5198 images.push_back({ ma, dylibID.c_str(), stripMode });
5199 });
5200 optimizeLinkedit(nullptr, images);
5201
5202 // update final readOnly region size
5203 if ( !_is64 )
5204 assert(0 && "Unimplemented");
5205
5206 {
5207 // 64-bit
5208 typedef Pointer64<LittleEndian> P;
5209 CacheHeader64& header = cacheHeader;
5210
5211 for (CacheHeader64::SegmentCommandAndRegion& cmdAndRegion : header.segments) {
5212 if (cmdAndRegion.second != &_readOnlyRegion)
5213 continue;
5214 cmdAndRegion.first->vmsize = _readOnlyRegion.sizeInUse;
5215 cmdAndRegion.first->filesize = _readOnlyRegion.sizeInUse;
5216 break;
5217 }
5218 }
5219 }
5220
5221 uint64_t t8 = mach_absolute_time();
5222
5223 uint64_t t9 = mach_absolute_time();
5224
5225 // Add fixups to rebase/bind the app cache
5226 writeFixups();
5227 {
5228 if ( !_is64 )
5229 assert(0 && "Unimplemented");
5230
5231 // update final readOnly region size
5232 {
5233 // 64-bit
5234 typedef Pointer64<LittleEndian> P;
5235 CacheHeader64& header = cacheHeader;
5236
5237 for (CacheHeader64::SegmentCommandAndRegion& cmdAndRegion : header.segments) {
5238 if (cmdAndRegion.second != &_readOnlyRegion)
5239 continue;
5240 cmdAndRegion.first->vmsize = _readOnlyRegion.sizeInUse;
5241 cmdAndRegion.first->filesize = _readOnlyRegion.sizeInUse;
5242 break;
5243 }
5244 }
5245 }
5246
5247 // FIXME: We could move _aslrTracker to a worker thread to be destroyed as we don't need it
5248 // after this point
5249
5250 uint64_t t10 = mach_absolute_time();
5251
5252 generateUUID();
5253 if ( _diagnostics.hasError() )
5254 return;
5255
5256 uint64_t t11 = mach_absolute_time();
5257
5258 if ( _options.verbose ) {
5259 fprintf(stderr, "time to layout cache: %ums\n", absolutetime_to_milliseconds(t2-t1));
5260 fprintf(stderr, "time to copy cached dylibs into buffer: %ums\n", absolutetime_to_milliseconds(t3-t2));
5261 fprintf(stderr, "time to adjust segments for new split locations: %ums\n", absolutetime_to_milliseconds(t4-t3));
5262 fprintf(stderr, "time to bind all images: %ums\n", absolutetime_to_milliseconds(t5-t4));
5263 fprintf(stderr, "time to optimize Objective-C: %ums\n", absolutetime_to_milliseconds(t6-t5));
5264 fprintf(stderr, "time to do stub elimination: %ums\n", absolutetime_to_milliseconds(t7-t6));
5265 fprintf(stderr, "time to optimize LINKEDITs: %ums\n", absolutetime_to_milliseconds(t8-t7));
5266 fprintf(stderr, "time to compute slide info: %ums\n", absolutetime_to_milliseconds(t10-t9));
5267 fprintf(stderr, "time to compute UUID and codesign cache file: %ums\n", absolutetime_to_milliseconds(t11-t10));
5268 }
5269 }
5270
5271 void AppCacheBuilder::fipsSign()
5272 {
5273 if ( appCacheOptions.cacheKind != Options::AppCacheKind::kernel )
5274 return;
5275
5276 // find com.apple.kec.corecrypto in collection being built
5277 __block const dyld3::MachOAnalyzer* kextMA = nullptr;
5278 forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
5279 DylibStripMode stripMode, const std::vector<std::string>& dependencies,
5280 Diagnostics& dylibDiag, bool& stop) {
5281 if ( dylibID == "com.apple.kec.corecrypto" ) {
5282 kextMA = ma;
5283 stop = true;
5284 }
5285 });
5286
5287 if ( kextMA == nullptr ) {
5288 _diagnostics.warning("Could not find com.apple.kec.corecrypto, skipping FIPS sealing");
5289 return;
5290 }
5291
5292 // find location in com.apple.kec.corecrypto to store hash of __text section
5293 uint64_t hashStoreSize;
5294 const void* hashStoreLocation = kextMA->findSectionContent("__TEXT", "__fips_hmacs", hashStoreSize);
5295 if ( hashStoreLocation == nullptr ) {
5296 _diagnostics.warning("Could not find __TEXT/__fips_hmacs section in com.apple.kec.corecrypto, skipping FIPS sealing");
5297 return;
5298 }
5299 if ( hashStoreSize != 32 ) {
5300 _diagnostics.warning("__TEXT/__fips_hmacs section in com.apple.kec.corecrypto is not 32 bytes in size, skipping FIPS sealing");
5301 return;
5302 }
5303
5304 // compute hmac hash of __text section. It may be in __TEXT_EXEC or __TEXT
5305 uint64_t textSize;
5306 const void* textLocation = kextMA->findSectionContent("__TEXT", "__text", textSize);
5307 if ( textLocation == nullptr ) {
5308 textLocation = kextMA->findSectionContent("__TEXT_EXEC", "__text", textSize);
5309 }
5310 if ( textLocation == nullptr ) {
5311 _diagnostics.warning("Could not find __TEXT/__text section in com.apple.kec.corecrypto, skipping FIPS sealing");
5312 return;
5313 }
5314 unsigned char hmac_key = 0;
5315 CCHmac(kCCHmacAlgSHA256, &hmac_key, 1, textLocation, textSize, (void*)hashStoreLocation); // store hash directly into hashStoreLocation
5316 }
5317
5318 void AppCacheBuilder::generateUUID() {
5319 uint8_t* uuidLoc = cacheHeader.uuid->uuid;
5320 assert(uuid_is_null(uuidLoc));
5321
5322 CCDigestRef digestRef = CCDigestCreate(kCCDigestSHA256);
5323 forEachRegion(^(const Region &region) {
5324 if ( _diagnostics.hasError() )
5325 return;
5326 if ( region.sizeInUse == 0 )
5327 return;
5328 int result = CCDigestUpdate(digestRef, region.buffer, region.sizeInUse);
5329 if ( result != 0 ) {
5330 _diagnostics.error("Could not generate UUID: %d", result);
5331 return;
5332 }
5333 });
5334 if ( !_diagnostics.hasError() ) {
5335 uint8_t buffer[CCDigestGetOutputSize(kCCDigestSHA256)];
5336 int result = CCDigestFinal(digestRef, buffer);
5337 memcpy(cacheHeader.uuid->uuid, buffer, sizeof(cacheHeader.uuid->uuid));
5338 if ( result != 0 ) {
5339 _diagnostics.error("Could not finalize UUID: %d", result);
5340 }
5341 }
5342 CCDigestDestroy(digestRef);
5343 if ( _diagnostics.hasError() )
5344 return;
5345
5346 // Update the prelink info dictionary too
5347 if ( prelinkInfoDict != nullptr ) {
5348 CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, &cacheHeader.uuid->uuid[0], sizeof(cacheHeader.uuid->uuid));
5349 CFDictionarySetValue(prelinkInfoDict, CFSTR("_PrelinkKCID"), dataRef);
5350 CFRelease(dataRef);
5351
5352 CFErrorRef errorRef = nullptr;
5353 CFDataRef xmlData = CFPropertyListCreateData(kCFAllocatorDefault, prelinkInfoDict,
5354 kCFPropertyListXMLFormat_v1_0, 0, &errorRef);
5355 if (errorRef != nullptr) {
5356 CFStringRef errorString = CFErrorCopyDescription(errorRef);
5357 _diagnostics.error("Could not serialise plist because :%s",
5358 CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
5359 CFRelease(xmlData);
5360 CFRelease(errorRef);
5361 return;
5362 } else {
5363 CFIndex xmlDataLength = CFDataGetLength(xmlData);
5364 if ( xmlDataLength > prelinkInfoRegion.bufferSize ) {
5365 _diagnostics.error("Overflow in prelink info segment. 0x%llx vs 0x%llx",
5366 (uint64_t)xmlDataLength, prelinkInfoRegion.bufferSize);
5367 CFRelease(xmlData);
5368 return;
5369 }
5370
5371 // Write the prelink info in to the buffer
5372 memcpy(prelinkInfoRegion.buffer, CFDataGetBytePtr(xmlData), xmlDataLength);
5373 CFRelease(xmlData);
5374 }
5375 }
5376 }
5377
5378
5379 void AppCacheBuilder::writeFile(const std::string& path)
5380 {
5381 std::string pathTemplate = path + "-XXXXXX";
5382 size_t templateLen = strlen(pathTemplate.c_str())+2;
5383 BLOCK_ACCCESSIBLE_ARRAY(char, pathTemplateSpace, templateLen);
5384 strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
5385 int fd = mkstemp(pathTemplateSpace);
5386 if ( fd == -1 ) {
5387 _diagnostics.error("could not open file %s", pathTemplateSpace);
5388 return;
5389 }
5390 uint64_t cacheFileSize = 0;
5391 // FIXME: Do we ever need to avoid allocating space for zero fill?
5392 cacheFileSize = _readOnlyRegion.cacheFileOffset + _readOnlyRegion.sizeInUse;
5393
5394 // set final cache file size (may help defragment file)
5395 ::ftruncate(fd, cacheFileSize);
5396
5397 // Write the whole buffer
5398 uint64_t writtenSize = pwrite(fd, (const uint8_t*)_fullAllocatedBuffer, cacheFileSize, 0);
5399 if (writtenSize == cacheFileSize) {
5400 ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
5401 if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
5402 ::close(fd);
5403 return; // success
5404 }
5405 } else {
5406 _diagnostics.error("could not write whole file. %lld bytes out of %lld were written",
5407 writtenSize, cacheFileSize);
5408 return;
5409 }
5410 ::close(fd);
5411 ::unlink(pathTemplateSpace);
5412 }
5413
5414 void AppCacheBuilder::writeBuffer(uint8_t*& buffer, uint64_t& bufferSize) const {
5415 bufferSize = _readOnlyRegion.cacheFileOffset + _readOnlyRegion.sizeInUse;
5416 buffer = (uint8_t*)malloc(bufferSize);
5417
5418 forEachRegion(^(const Region &region) {
5419 if ( region.sizeInUse == 0 )
5420 return;
5421 memcpy(buffer + region.cacheFileOffset, (const uint8_t*)region.buffer, region.sizeInUse);
5422 });
5423 }