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