]> git.saurik.com Git - apple/dyld.git/blob - dyld3/dyld_app_cache_util.cpp
dyld-832.7.3.tar.gz
[apple/dyld.git] / dyld3 / dyld_app_cache_util.cpp
1 /*
2 * Copyright (c) 2017 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/mman.h>
28 #include <list>
29 #include <map>
30 #include <vector>
31
32 #include <variant>
33
34 #include <CoreFoundation/CFBundle.h>
35 #include <CoreFoundation/CFPropertyList.h>
36 #include <CoreFoundation/CFStream.h>
37
38 #include "kernel_collection_builder.h"
39 #include "ClosureFileSystemPhysical.h"
40 #include "FileUtils.h"
41 #include "JSONWriter.h"
42 #include "MachOAppCache.h"
43
44 using namespace dyld3::json;
45
46 using dyld3::closure::FileSystemPhysical;
47 using dyld3::closure::LoadedFileInfo;
48 using dyld3::GradedArchs;
49 using dyld3::MachOAnalyzer;
50 using dyld3::MachOAppCache;
51 using dyld3::Platform;
52 using dyld3::json::Node;
53
54 __attribute__((noreturn))
55 static void exit_usage(const char* missingOption = nullptr) {
56 if ( missingOption != nullptr ) {
57 fprintf(stderr, "Missing option '%s'\n", missingOption);
58 fprintf(stderr, "\n");
59 }
60
61 fprintf(stderr, "Usage: dyld_app_cache_util [-layout] [-entrypoint] [-fixups] [-symbols] [-kmod] [-uuid] [-fips] -arch arch -platform platform -app-cache app-cache-path\n");
62 fprintf(stderr, " -layout print the layout of an existing app cache\n");
63 fprintf(stderr, " -entrypoint print the entrypoint of an existing app cache\n");
64 fprintf(stderr, " -fixups print the fixups of an existing app cache\n");
65 fprintf(stderr, " -symbols print the symbols of an existing app cache\n");
66 fprintf(stderr, " -kmod print the kmod_info of an existing app cache\n");
67 fprintf(stderr, " -uuid print the UUID of an existing app cache\n");
68 fprintf(stderr, " -fips print the FIPS section of an existing app cache\n");
69 fprintf(stderr, "\n");
70
71 fprintf(stderr, "Usage: dyld_app_cache_util -validate file-path -arch arch -platform platform\n");
72 fprintf(stderr, " -validate the path to check is valid for inserting in to an app cache\n");
73 fprintf(stderr, "\n");
74
75 fprintf(stderr, "Usage: dyld_app_cache_util -list-bundles directory-path\n");
76 fprintf(stderr, " -list-bundles the directory to index for bundles\n");
77 fprintf(stderr, "\n");
78
79 fprintf(stderr, "Usage: dyld_app_cache_util -create-kernel-collection kernel-collection -kernel kernel-path [-extensions path-to-extensions] [-bundle-id bundle-id]*\n");
80 fprintf(stderr, " -create-kernel-collection create a kernel collection and write to the given path\n");
81 fprintf(stderr, " -kernel path to the kernel static executable\n");
82 fprintf(stderr, " -extensions path to the kernel extensions directory\n");
83 fprintf(stderr, " -bundle-id zero or more bundle-ids to link in to the kernel collection\n");
84 fprintf(stderr, " -sectcreate segment name, section name, and payload file path for more data to embed in the kernel\n");
85 fprintf(stderr, "\n");
86
87 fprintf(stderr, "Usage: dyld_app_cache_util -create-pageable-kernel-collection aux-kernel-collection -kernel-collection kernel-collection-path [-extensions path-to-extensions] [-bundle-id bundle-id]*\n");
88 fprintf(stderr, " -create-pageable-kernel-collection create a pageable kernel collection and write to the given path\n");
89 fprintf(stderr, " -kernel-collection path to the kernel collection collection\n");
90 fprintf(stderr, " -extensions path to the kernel extensions directory\n");
91 fprintf(stderr, " -bundle-id zero or more bundle-ids to link in to the kernel collection\n");
92 fprintf(stderr, "\n");
93
94 fprintf(stderr, "Usage: dyld_app_cache_util -create-aux-kernel-collection aux-kernel-collection -kernel-collection kernel-collection-path [-extensions path-to-extensions] [-bundle-id bundle-id]*\n");
95 fprintf(stderr, " -create-aux-kernel-collection create an aux kernel collection and write to the given path\n");
96 fprintf(stderr, " -kernel-collection path to the kernel collection\n");
97 fprintf(stderr, " -pageable-collection path to the pageable collection\n");
98 fprintf(stderr, " -extensions path to the kernel extensions directory\n");
99 fprintf(stderr, " -bundle-id zero or more bundle-ids to link in to the kernel collection\n");
100 fprintf(stderr, "\n");
101
102 fprintf(stderr, "Common options:\n");
103 fprintf(stderr, " -arch xxx the arch to use to create the app cache\n");
104 fprintf(stderr, " -platform xxx the platform to use to create the app cache\n");
105
106 exit(1);
107 }
108
109 struct DumpOptions {
110 bool printLayout = false;
111 bool printEntryPoint = false;
112 bool printFixups = false;
113 bool printSymbols = false;
114 bool printUUID = false;
115 bool printKModInfo = false;
116 bool printFIPS = false;
117 };
118
119 struct ValidateOptions {
120 const char* filePath = nullptr;
121 };
122
123 struct ListBundlesOptions {
124 const char* directoryPath = nullptr;
125 };
126
127 // The payload of -sectcreate
128 struct SectionData {
129 const char* segmentName = nullptr;
130 const char* sectionName = nullptr;
131 const char* payloadFilePath = nullptr;
132 };
133
134 struct CreateKernelCollectionOptions {
135 const char* outputCachePath = nullptr;
136 const char* kernelPath = nullptr;
137 const char* kernelCollectionPath = nullptr;
138 const char* pageableCollectionPath = nullptr;
139 const char* extensionsPath = nullptr;
140 const char* volumeRoot = "";
141 std::vector<const char*> bundleIDs;
142 bool verbose = false;
143 bool printJSONErrors = false;
144 CollectionKind collectionKind = unknownKC;
145 StripMode stripMode = unknownStripMode;
146 std::vector<SectionData> sections;
147 const char* prelinkInfoExtraData = nullptr;
148 };
149
150 typedef std::variant<std::monostate, DumpOptions, ValidateOptions, ListBundlesOptions, CreateKernelCollectionOptions> OptionsVariants;
151
152 struct CommonOptions {
153 const char* appCachePath = nullptr;
154 std::vector<const char*> archs;
155 const char* platform = nullptr;
156 };
157
158 CommonOptions gOpts;
159
160 template<typename T>
161 static T& exitOrGetState(OptionsVariants& options, const char* argv) {
162 if (std::holds_alternative<std::monostate>(options)) {
163 return options.emplace<T>();
164 }
165 if (std::holds_alternative<T>(options))
166 return std::get<T>(options);
167 exit_usage();
168 }
169
170 static bool parseArgs(int argc, const char* argv[], OptionsVariants& options) {
171 for (int i = 1; i < argc; ++i) {
172 const char* arg = argv[i];
173 if (arg[0] != '-') {
174 fprintf(stderr, "unknown option: %s\n", arg);
175 exit_usage();
176 }
177
178 // Common options
179 if (strcmp(arg, "-app-cache") == 0) {
180 if (gOpts.appCachePath != nullptr)
181 exit_usage();
182 gOpts.appCachePath = argv[++i];
183 continue;
184 }
185 if (strcmp(arg, "-arch") == 0) {
186 gOpts.archs.push_back(argv[++i]);
187 continue;
188 }
189 if (strcmp(arg, "-platform") == 0) {
190 if (gOpts.platform != nullptr)
191 exit_usage();
192 gOpts.platform = argv[++i];
193 continue;
194 }
195
196 // DumpOptions
197 if (strcmp(arg, "-layout") == 0) {
198 exitOrGetState<DumpOptions>(options, arg).printLayout = true;
199 continue;
200 }
201 if (strcmp(arg, "-entrypoint") == 0) {
202 exitOrGetState<DumpOptions>(options, arg).printEntryPoint = true;
203 continue;
204 }
205 if (strcmp(arg, "-fixups") == 0) {
206 exitOrGetState<DumpOptions>(options, arg).printFixups = true;
207 continue;
208 }
209 if (strcmp(arg, "-symbols") == 0) {
210 exitOrGetState<DumpOptions>(options, arg).printSymbols = true;
211 continue;
212 }
213 if (strcmp(arg, "-uuid") == 0) {
214 exitOrGetState<DumpOptions>(options, arg).printUUID = true;
215 continue;
216 }
217 if (strcmp(arg, "-kmod") == 0) {
218 exitOrGetState<DumpOptions>(options, arg).printKModInfo = true;
219 continue;
220 }
221 if (strcmp(arg, "-fips") == 0) {
222 exitOrGetState<DumpOptions>(options, arg).printFIPS = true;
223 continue;
224 }
225
226 // ValidateOptions
227 if (strcmp(arg, "-validate") == 0) {
228 exitOrGetState<ValidateOptions>(options, arg).filePath = argv[++i];
229 continue;
230 }
231
232 // ListBundlesOptions
233 if (strcmp(arg, "-list-bundles") == 0) {
234 exitOrGetState<ListBundlesOptions>(options, arg).directoryPath = argv[++i];
235 continue;
236 }
237
238 // CreateKernelCollectionOptions
239 if (strcmp(arg, "-create-kernel-collection") == 0) {
240 exitOrGetState<CreateKernelCollectionOptions>(options, arg).outputCachePath = argv[++i];
241 exitOrGetState<CreateKernelCollectionOptions>(options, arg).collectionKind = baseKC;
242 continue;
243 }
244 if (strcmp(arg, "-kernel") == 0) {
245 exitOrGetState<CreateKernelCollectionOptions>(options, arg).kernelPath = argv[++i];
246 continue;
247 }
248 if (strcmp(arg, "-create-pageable-kernel-collection") == 0) {
249 exitOrGetState<CreateKernelCollectionOptions>(options, arg).outputCachePath = argv[++i];
250 exitOrGetState<CreateKernelCollectionOptions>(options, arg).collectionKind = pageableKC;
251 continue;
252 }
253 if (strcmp(arg, "-create-aux-kernel-collection") == 0) {
254 exitOrGetState<CreateKernelCollectionOptions>(options, arg).outputCachePath = argv[++i];
255 exitOrGetState<CreateKernelCollectionOptions>(options, arg).collectionKind = auxKC;
256 continue;
257 }
258 if (strcmp(arg, "-kernel-collection") == 0) {
259 exitOrGetState<CreateKernelCollectionOptions>(options, arg).kernelCollectionPath = argv[++i];
260 continue;
261 }
262 if (strcmp(arg, "-pageable-collection") == 0) {
263 exitOrGetState<CreateKernelCollectionOptions>(options, arg).pageableCollectionPath = argv[++i];
264 continue;
265 }
266 if (strcmp(arg, "-extensions") == 0) {
267 exitOrGetState<CreateKernelCollectionOptions>(options, arg).extensionsPath = argv[++i];
268 continue;
269 }
270 if (strcmp(arg, "-volume-root") == 0) {
271 exitOrGetState<CreateKernelCollectionOptions>(options, arg).volumeRoot = argv[++i];
272 continue;
273 }
274 if (strcmp(arg, "-bundle-id") == 0) {
275 exitOrGetState<CreateKernelCollectionOptions>(options, arg).bundleIDs.push_back(argv[++i]);
276 continue;
277 }
278 if (strcmp(arg, "-verbose") == 0) {
279 exitOrGetState<CreateKernelCollectionOptions>(options, arg).verbose = true;
280 continue;
281 }
282 if (strcmp(arg, "-json-errors") == 0) {
283 exitOrGetState<CreateKernelCollectionOptions>(options, arg).printJSONErrors = true;
284 continue;
285 }
286 if (strcmp(arg, "-strip-all") == 0) {
287 exitOrGetState<CreateKernelCollectionOptions>(options, arg).stripMode = stripAll;
288 continue;
289 }
290 if (strcmp(arg, "-strip-all-kexts") == 0) {
291 exitOrGetState<CreateKernelCollectionOptions>(options, arg).stripMode = stripAllKexts;
292 continue;
293 }
294 if (strcmp(arg, "-sectcreate") == 0) {
295 const char* segmentName = argv[++i];
296 const char* sectionName = argv[++i];
297 const char* payloadFilePath = argv[++i];
298 SectionData sectData = { segmentName, sectionName, payloadFilePath };
299 exitOrGetState<CreateKernelCollectionOptions>(options, arg).sections.push_back(sectData);
300 continue;
301 }
302 if (strcmp(arg, "-prelink-info-extra") == 0) {
303 const char* payloadFilePath = argv[++i];
304 exitOrGetState<CreateKernelCollectionOptions>(options, arg).prelinkInfoExtraData = payloadFilePath;
305 continue;
306 }
307
308
309 fprintf(stderr, "unknown option: %s\n", arg);
310 exit_usage();
311 }
312
313 return true;
314 }
315
316 static Platform stringToPlatform(const std::string& str) {
317 if (str == "unknown")
318 return Platform::unknown;
319 if (str == "macOS")
320 return Platform::macOS;
321 if (str == "iOS")
322 return Platform::iOS;
323 if (str == "tvOS")
324 return Platform::tvOS;
325 if (str == "watchOS")
326 return Platform::watchOS;
327 if (str == "bridgeOS")
328 return Platform::bridgeOS;
329 if (str == "iOSMac")
330 return Platform::iOSMac;
331 if (str == "UIKitForMac")
332 return Platform::iOSMac;
333 if (str == "iOS_simulator")
334 return Platform::iOS_simulator;
335 if (str == "tvOS_simulator")
336 return Platform::tvOS_simulator;
337 if (str == "watchOS_simulator")
338 return Platform::watchOS_simulator;
339 return Platform::unknown;
340 }
341
342 static int dumpAppCache(const DumpOptions& options) {
343 // Verify any required options
344 if (gOpts.archs.size() != 1)
345 exit_usage("-arch");
346 if (gOpts.platform == nullptr)
347 exit_usage("-platform");
348
349 if (gOpts.appCachePath == nullptr)
350 exit_usage();
351
352 FileSystemPhysical fileSystem;
353 if (!fileSystem.fileExists(gOpts.appCachePath)) {
354 fprintf(stderr, "App-cache path does not exist: %s\n", gOpts.appCachePath);
355 return 1;
356 }
357
358 const GradedArchs& archs = GradedArchs::forName(gOpts.archs[0]);
359 Platform platform = Platform::unknown;
360 bool isKernelCollection = false;
361
362 // HACK: Pass a real option for building a kernel app cache
363 if (!strcmp(gOpts.platform, "kernel")) {
364 isKernelCollection = true;
365 } else {
366 platform = stringToPlatform(gOpts.platform);
367 if (platform == Platform::unknown) {
368 fprintf(stderr, "Could not create app cache because: unknown platform '%s'\n", gOpts.platform);
369 return 1;
370 }
371 }
372
373 __block Diagnostics diag;
374 char appCacheRealPath[MAXPATHLEN];
375 LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, gOpts.appCachePath, archs, platform, appCacheRealPath);
376 if (diag.hasError()) {
377 fprintf(stderr, "Could not load app cache because: %s\n", diag.errorMessage().c_str());
378 return 1;
379 }
380
381 MachOAppCache* appCacheMA = (MachOAppCache*)loadedFileInfo.fileContent;
382 if (appCacheMA == nullptr) {
383 fprintf(stderr, "Could not load app cache: %s\n", gOpts.appCachePath);
384 return 1;
385 }
386
387 if (options.printLayout) {
388 __block Node topNode;
389
390 // Add the segments for the app cache
391 __block Node segmentsNode;
392 __block bool hasError = false;
393 appCacheMA->forEachSegment(^(const dyld3::MachOFile::SegmentInfo &info, bool &stop) {
394 Node segmentNode;
395 segmentNode.map["name"] = makeNode(info.segName);
396 segmentNode.map["vmAddr"] = makeNode(hex(info.vmAddr));
397 segmentNode.map["vmSize"] = makeNode(hex(info.vmSize));
398 segmentNode.map["vmEnd"] = makeNode(hex(info.vmAddr + info.vmSize));
399 switch (info.protections) {
400 case VM_PROT_READ:
401 segmentNode.map["permissions"] = makeNode("r--");
402 break;
403 case VM_PROT_WRITE:
404 segmentNode.map["permissions"] = makeNode("-w-");
405 break;
406 case VM_PROT_EXECUTE:
407 segmentNode.map["permissions"] = makeNode("--x");
408 break;
409 case VM_PROT_READ | VM_PROT_WRITE:
410 segmentNode.map["permissions"] = makeNode("rw-");
411 break;
412 case VM_PROT_READ | VM_PROT_EXECUTE:
413 segmentNode.map["permissions"] = makeNode("r-x");
414 break;
415 case VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE:
416 segmentNode.map["permissions"] = makeNode("rwx");
417 break;
418 default:
419 fprintf(stderr, "Unknown permissions on segment '%s'\n", info.segName);
420 hasError = true;
421 stop = true;
422 }
423
424 __block Node sectionsNode;
425 appCacheMA->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo &sectInfo, bool malformedSectionRange, bool &stop) {
426 if ( strncmp(sectInfo.segInfo.segName, info.segName, 16) != 0 )
427 return;
428
429 Node sectionNode;
430 sectionNode.map["name"] = makeNode(sectInfo.sectName);
431 sectionNode.map["vmAddr"] = makeNode(hex(sectInfo.sectAddr));
432 sectionNode.map["vmSize"] = makeNode(hex(sectInfo.sectSize));
433 sectionNode.map["vmEnd"] = makeNode(hex(sectInfo.sectAddr + sectInfo.sectSize));
434
435 sectionsNode.array.push_back(sectionNode);
436 });
437
438 if ( !sectionsNode.array.empty() ) {
439 segmentNode.map["sections"] = sectionsNode;
440 }
441
442 segmentsNode.array.push_back(segmentNode);
443 });
444
445 if (hasError)
446 return 1;
447
448 topNode.map["cache-segments"] = segmentsNode;
449
450 // Map from name to relative path
451 __block std::unordered_map<std::string, std::string> relativePaths;
452 appCacheMA->forEachPrelinkInfoLibrary(diag, ^(const char *bundleName, const char* relativePath,
453 const std::vector<const char *> &deps) {
454 if ( relativePath != nullptr )
455 relativePaths[bundleName] = relativePath;
456 });
457
458 __block Node dylibsNode;
459 appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
460 __block Node segmentsNode;
461 ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo &info, bool &stop) {
462 Node segmentNode;
463 segmentNode.map["name"] = makeNode(info.segName);
464 segmentNode.map["vmAddr"] = makeNode(hex(info.vmAddr));
465 segmentNode.map["vmSize"] = makeNode(hex(info.vmSize));
466 segmentNode.map["vmEnd"] = makeNode(hex(info.vmAddr + info.vmSize));
467
468 switch (info.protections) {
469 case VM_PROT_READ:
470 segmentNode.map["permissions"] = makeNode("r--");
471 break;
472 case VM_PROT_WRITE:
473 segmentNode.map["permissions"] = makeNode("-w-");
474 break;
475 case VM_PROT_EXECUTE:
476 segmentNode.map["permissions"] = makeNode("--x");
477 break;
478 case VM_PROT_READ | VM_PROT_WRITE:
479 segmentNode.map["permissions"] = makeNode("rw-");
480 break;
481 case VM_PROT_READ | VM_PROT_EXECUTE:
482 segmentNode.map["permissions"] = makeNode("r-x");
483 break;
484 default:
485 fprintf(stderr, "Unknown permissions on segment '%s'\n", info.segName);
486 hasError = true;
487 stop = true;
488 }
489
490 __block Node sectionsNode;
491 ma->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo &sectInfo, bool malformedSectionRange, bool &stop) {
492 if ( strncmp(sectInfo.segInfo.segName, info.segName, 16) != 0 )
493 return;
494
495 Node sectionNode;
496 sectionNode.map["name"] = makeNode(sectInfo.sectName);
497 sectionNode.map["vmAddr"] = makeNode(hex(sectInfo.sectAddr));
498 sectionNode.map["vmSize"] = makeNode(hex(sectInfo.sectSize));
499 sectionNode.map["vmEnd"] = makeNode(hex(sectInfo.sectAddr + sectInfo.sectSize));
500
501 sectionsNode.array.push_back(sectionNode);
502 });
503
504 if ( !sectionsNode.array.empty() ) {
505 segmentNode.map["sections"] = sectionsNode;
506 }
507
508 segmentsNode.array.push_back(segmentNode);
509 });
510
511 Node dylibNode;
512 dylibNode.map["name"] = makeNode(name);
513 dylibNode.map["segments"] = segmentsNode;
514
515 auto relativePathIt = relativePaths.find(name);
516 if ( relativePathIt != relativePaths.end() )
517 dylibNode.map["relativePath"] = makeNode(relativePathIt->second);
518
519 dylibsNode.array.push_back(dylibNode);
520 });
521
522 topNode.map["dylibs"] = dylibsNode;
523
524 printJSON(topNode, 0, std::cout);
525 }
526
527 if (options.printEntryPoint) {
528 __block Node topNode;
529
530 // add entry
531 uint64_t entryOffset;
532 bool usesCRT;
533 Node entryPointNode;
534 if ( appCacheMA->getEntry(entryOffset, usesCRT) ) {
535 entryPointNode.value = hex(appCacheMA->preferredLoadAddress() + entryOffset);
536 }
537
538 topNode.map["entrypoint"] = entryPointNode;
539
540 printJSON(topNode, 0, std::cout);
541 }
542
543 if (options.printFixups) {
544 __block Node topNode;
545
546 __block uint64_t baseAddress = ~0ULL;
547 appCacheMA->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
548 baseAddress = std::min(baseAddress, info.vmAddr);
549 });
550 uint64_t cacheBaseAddress = baseAddress;
551 uint64_t textSegVMAddr = appCacheMA->preferredLoadAddress();
552
553 auto getFixupsNode = [cacheBaseAddress, textSegVMAddr](const dyld3::MachOAnalyzer* ma) {
554 __block Node fixupsNode;
555
556 if (!ma->hasChainedFixups()) {
557 return makeNode("none");
558 }
559
560 // Keep track of the fixups seen by chained fixups. The remainder might be
561 // classic relocs if we are the x86_64 kernel collection
562 __block std::set<uint64_t> seenFixupVMOffsets;
563
564 __block Diagnostics diag;
565 ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
566 ma->forEachFixupInAllChains(diag, starts, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
567 uint64_t vmOffset = (uint8_t*)fixupLoc - (uint8_t*)ma;
568 seenFixupVMOffsets.insert(vmOffset);
569
570 // Correct for __DATA being before __TEXT, in which case the offset
571 // is from __DATA, not a mach header offset
572 vmOffset += (textSegVMAddr - cacheBaseAddress);
573
574 fixupsNode.map[hex(vmOffset)] = makeNode("fixup");
575 switch (segInfo->pointer_format) {
576 case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
577 case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE: {
578 uint64_t targetVMOffset = fixupLoc->kernel64.target;
579 uint64_t targetVMAddr = targetVMOffset + cacheBaseAddress;
580 std::string level = "kc(" + decimal(fixupLoc->kernel64.cacheLevel) + ")";
581 std::string fixup = level + " + " + hex(targetVMAddr);
582 if (fixupLoc->kernel64.isAuth) {
583 fixup += " auth(";
584 fixup += fixupLoc->kernel64.keyName();
585 fixup += " ";
586 fixup += fixupLoc->kernel64.addrDiv ? "addr" : "!addr";
587 fixup += " ";
588 fixup += decimal(fixupLoc->kernel64.diversity);
589 fixup += ")";
590 }
591 fixupsNode.map[hex(vmOffset)] = makeNode(fixup);
592 break;
593 }
594 default:
595 diag.error("unknown pointer type %d", segInfo->pointer_format);
596 break;
597 }
598 });
599 });
600 diag.assertNoError();
601
602 ma->forEachRebase(diag, ^(const char *opcodeName, const dyld3::MachOAnalyzer::LinkEditInfo &leInfo,
603 const dyld3::MachOAnalyzer::SegmentInfo *segments,
604 bool segIndexSet, uint32_t pointerSize, uint8_t segmentIndex,
605 uint64_t segmentOffset, dyld3::MachOAnalyzer::Rebase kind, bool &stop) {
606 uint64_t rebaseVmAddr = segments[segmentIndex].vmAddr + segmentOffset;
607 uint64_t runtimeOffset = rebaseVmAddr - textSegVMAddr;
608 const uint8_t* fixupLoc = (const uint8_t*)ma + runtimeOffset;
609
610 // Correct for __DATA being before __TEXT, in which case the offset
611 // is from __DATA, not a mach header offset
612 runtimeOffset += (textSegVMAddr - cacheBaseAddress);
613
614 std::string fixup = "kc(0) + ";
615 switch ( kind ) {
616 case dyld3::MachOAnalyzer::Rebase::unknown:
617 fixup += " : unhandled";
618 break;
619 case dyld3::MachOAnalyzer::Rebase::pointer32: {
620 uint32_t value = *(uint32_t*)(fixupLoc);
621 fixup += hex(value) + " : pointer32";
622 break;
623 }
624 case dyld3::MachOAnalyzer::Rebase::pointer64: {
625 uint64_t value = *(uint64_t*)(fixupLoc);
626 fixup += hex(value) + " : pointer64";
627 break;
628 }
629 case dyld3::MachOAnalyzer::Rebase::textPCrel32:
630 fixup += " : pcrel32";
631 break;
632 case dyld3::MachOAnalyzer::Rebase::textAbsolute32:
633 fixup += " : absolute32";
634 break;
635 }
636 fixupsNode.map[hex(runtimeOffset)] = makeNode(fixup);
637 });
638 diag.assertNoError();
639
640 return fixupsNode;
641 };
642
643 topNode.map["fixups"] = getFixupsNode(appCacheMA);
644
645 __block Node dylibsNode;
646 appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
647 Node dylibNode;
648 dylibNode.map["name"] = makeNode(name);
649 dylibNode.map["fixups"] = getFixupsNode(ma);
650
651 dylibsNode.array.push_back(dylibNode);
652 });
653
654 topNode.map["dylibs"] = dylibsNode;
655
656 printJSON(topNode, 0, std::cout);
657 }
658
659 if (options.printSymbols) {
660 __block Node topNode;
661
662 __block Node dylibsNode;
663 appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
664 __block Node globalSymbolsNode;
665 ma->forEachGlobalSymbol(diag, ^(const char *symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
666 Node symbolNode;
667 symbolNode.map["name"] = makeNode(symbolName);
668 symbolNode.map["vmAddr"] = makeNode(hex(n_value));
669
670 globalSymbolsNode.array.push_back(symbolNode);
671 });
672
673 __block Node localSymbolsNode;
674 ma->forEachLocalSymbol(diag, ^(const char *symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
675 Node symbolNode;
676 symbolNode.map["name"] = makeNode(symbolName);
677 symbolNode.map["vmAddr"] = makeNode(hex(n_value));
678
679 localSymbolsNode.array.push_back(symbolNode);
680 });
681
682 if (globalSymbolsNode.array.empty())
683 globalSymbolsNode = makeNode("none");
684 if (localSymbolsNode.array.empty())
685 localSymbolsNode = makeNode("none");
686
687 Node dylibNode;
688 dylibNode.map["name"] = makeNode(name);
689 dylibNode.map["global-symbols"] = globalSymbolsNode;
690 dylibNode.map["local-symbols"] = localSymbolsNode;
691
692 dylibsNode.array.push_back(dylibNode);
693 });
694
695 topNode.map["dylibs"] = dylibsNode;
696
697 printJSON(topNode, 0, std::cout);
698 }
699
700 if (options.printUUID) {
701 __block Node topNode;
702
703 // add uuid
704 Node uuidNode;
705 uuid_t uuid = {};
706 if ( appCacheMA->getUuid(uuid) ) {
707 uuid_string_t uuidString;
708 uuid_unparse_upper(uuid, uuidString);
709 uuidNode.value = uuidString;
710 }
711
712 topNode.map["uuid"] = uuidNode;
713
714 auto getPlistUUID = ^(const char* jsonNodeName, CFStringRef keyName) {
715 uuid_t uuid = {};
716 const uint8_t* prelinkInfoBuffer = nullptr;
717 uint64_t prelinkInfoBufferSize = 0;
718 prelinkInfoBuffer = (const uint8_t*)appCacheMA->findSectionContent("__PRELINK_INFO", "__info", prelinkInfoBufferSize);
719 if ( prelinkInfoBuffer != nullptr ) {
720 CFReadStreamRef readStreamRef = CFReadStreamCreateWithBytesNoCopy(kCFAllocatorDefault, prelinkInfoBuffer, prelinkInfoBufferSize, kCFAllocatorNull);
721 if ( !CFReadStreamOpen(readStreamRef) ) {
722 fprintf(stderr, "Could not open plist stream\n");
723 exit(1);
724 }
725 CFErrorRef errorRef = nullptr;
726 CFPropertyListRef plistRef = CFPropertyListCreateWithStream(kCFAllocatorDefault, readStreamRef, prelinkInfoBufferSize, kCFPropertyListImmutable, nullptr, &errorRef);
727 if ( errorRef != nullptr ) {
728 CFStringRef stringRef = CFErrorCopyFailureReason(errorRef);
729 fprintf(stderr, "Could not read plist because: %s\n", CFStringGetCStringPtr(stringRef, kCFStringEncodingASCII));
730 CFRelease(stringRef);
731 exit(1);
732 }
733 assert(CFGetTypeID(plistRef) == CFDictionaryGetTypeID());
734 CFDataRef uuidDataRef = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)plistRef, keyName);
735 if ( uuidDataRef != nullptr ) {
736 CFDataGetBytes(uuidDataRef, CFRangeMake(0, CFDataGetLength(uuidDataRef)), uuid);
737
738 Node uuidNode;
739 uuid_string_t uuidString;
740 uuid_unparse_upper(uuid, uuidString);
741 uuidNode.value = uuidString;
742 topNode.map[jsonNodeName] = uuidNode;
743 }
744 CFRelease(plistRef);
745 CFRelease(readStreamRef);
746 }
747 };
748
749 getPlistUUID("prelink-info-uuid", CFSTR("_PrelinkKCID"));
750
751 // If we are an auxKC, then we should also have a reference to the baseKC UUID
752 getPlistUUID("prelink-info-base-uuid", CFSTR("_BootKCID"));
753
754 // If we are an pageableKC, then we should also have a reference to the pageableKC UUID
755 getPlistUUID("prelink-info-pageable-uuid", CFSTR("_PageableKCID"));
756
757 printJSON(topNode, 0, std::cout);
758 }
759
760 if (options.printKModInfo) {
761 __block Node topNode;
762
763 appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
764 Node dylibNode;
765 dylibNode.map["name"] = makeNode(name);
766
767 // Check for a global first
768 __block uint64_t kmodInfoVMOffset = 0;
769 __block bool found = false;
770 {
771 dyld3::MachOAnalyzer::FoundSymbol foundInfo;
772 found = ma->findExportedSymbol(diag, "_kmod_info", true, foundInfo, nullptr);
773 if ( found ) {
774 kmodInfoVMOffset = foundInfo.value;
775 }
776 }
777 // And fall back to a local if we need to
778 if ( !found ) {
779 ma->forEachLocalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type,
780 uint8_t n_sect, uint16_t n_desc, bool& stop) {
781 if ( strcmp(aSymbolName, "_kmod_info") == 0 ) {
782 kmodInfoVMOffset = n_value - ma->preferredLoadAddress();
783 found = true;
784 stop = true;
785 }
786 });
787 }
788
789 if ( found ) {
790 dyld3::MachOAppCache::KModInfo64_v1* kmodInfo = (dyld3::MachOAppCache::KModInfo64_v1*)((uint8_t*)ma + kmodInfoVMOffset);
791 Node kmodInfoNode;
792 kmodInfoNode.map["info-version"] = makeNode(decimal(kmodInfo->info_version));
793 kmodInfoNode.map["name"] = makeNode((const char*)&kmodInfo->name[0]);
794 kmodInfoNode.map["version"] = makeNode((const char*)&kmodInfo->version[0]);
795 kmodInfoNode.map["address"] = makeNode(hex(kmodInfo->address));
796 kmodInfoNode.map["size"] = makeNode(hex(kmodInfo->size));
797
798 dylibNode.map["kmod_info"] = kmodInfoNode;
799 } else {
800 dylibNode.map["kmod_info"] = makeNode("none");
801 }
802
803 topNode.array.push_back(dylibNode);
804 });
805
806 printJSON(topNode, 0, std::cout);
807 }
808
809 if (options.printFIPS) {
810 __block Node topNode;
811
812 appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
813 if ( strcmp(name, "com.apple.kec.corecrypto") != 0 )
814 return;
815
816 uint64_t hashStoreSize;
817 const void* hashStoreLocation = ma->findSectionContent("__TEXT", "__fips_hmacs", hashStoreSize);
818 assert(hashStoreLocation != nullptr);
819
820 const uint8_t* hashBuffer = (const uint8_t*)hashStoreLocation;
821 std::string hashString;
822
823 for (int i = 0; i < hashStoreSize; ++i) {
824 uint8_t byte = hashBuffer[i];
825 uint8_t nibbleL = byte & 0x0F;
826 uint8_t nibbleH = byte >> 4;
827 if ( nibbleH < 10 ) {
828 hashString += '0' + nibbleH;
829 } else {
830 hashString += 'a' + (nibbleH-10);
831 }
832 if ( nibbleL < 10 ) {
833 hashString += '0' + nibbleL;
834 } else {
835 hashString += 'a' + (nibbleL-10);
836 }
837 }
838
839 stop = true;
840
841 topNode.map["fips"] = makeNode(hashString);
842 });
843
844 printJSON(topNode, 0, std::cout);
845 }
846
847 return 0;
848 }
849
850 static int validateFile(const ValidateOptions& options) {
851 // Verify any required options
852 if (gOpts.archs.size() != 1)
853 exit_usage("-arch");
854 if (gOpts.platform == nullptr)
855 exit_usage("-platform");
856 if (options.filePath == nullptr)
857 exit_usage();
858
859 const GradedArchs& archs = GradedArchs::forName(gOpts.archs[0]);
860 Platform platform = Platform::unknown;
861
862 // HACK: Pass a real option for building a kernel app cache
863 if (strcmp(gOpts.platform, "kernel")) {
864 fprintf(stderr, "Could not create app cache because: unknown platform '%s'\n", gOpts.platform);
865 return 1;
866 }
867
868 __block Diagnostics diag;
869
870 std::string file = options.filePath;
871 {
872 FileSystemPhysical fileSystem;
873 char fileRealPath[MAXPATHLEN];
874 LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, file.c_str(), archs, platform, fileRealPath);
875 if (diag.hasError()) {
876 fprintf(stderr, "Could not load file '%s' because: %s\n", file.c_str(), diag.errorMessage().c_str());
877 diag.clearError();
878 return 1;
879 }
880
881 MachOAnalyzer* ma = (MachOAnalyzer*)loadedFileInfo.fileContent;
882 if (ma == nullptr) {
883 fprintf(stderr, "Could not load file: %s\n", file.c_str());
884 return 1;
885 }
886
887 auto errorHandler = ^(const char* msg) {
888 diag.warning("File '%s' cannot be placed in kernel collection because: %s", file.c_str(), msg);
889 };
890 if (ma->canBePlacedInKernelCollection(file.c_str(), errorHandler)) {
891 return 0;
892 } else {
893 fileSystem.unloadFile(loadedFileInfo);
894 }
895 }
896
897 {
898 // Since we found no files, print warnings for the ones we tried
899 if (diag.warnings().empty()) {
900 fprintf(stderr, "File '%s' was not valid for app-cache\n", file.c_str());
901 } else {
902 for (const std::string& msg : diag.warnings()) {
903 fprintf(stderr, " %s\n", msg.c_str());
904 }
905 }
906 return 1;
907 }
908
909 return 0;
910 }
911
912 static void forEachBundle(const char* bundlesDirectoryPath,
913 void (^callback)(CFBundleRef bundleRef, const char* bundleName)) {
914 CFStringRef sourcePath = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, bundlesDirectoryPath,
915 kCFStringEncodingASCII, kCFAllocatorNull);
916 CFURLRef sourceURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, sourcePath,
917 kCFURLPOSIXPathStyle, true);
918 CFArrayRef bundles = CFBundleCreateBundlesFromDirectory(kCFAllocatorDefault, sourceURL, nullptr);
919
920 for (CFIndex i = 0, e = CFArrayGetCount(bundles); i != e; ++i) {
921 CFBundleRef bundleRef = (CFBundleRef)CFArrayGetValueAtIndex(bundles, i);
922 CFStringRef bundleID = CFBundleGetIdentifier(bundleRef);
923 if (!bundleID)
924 continue;
925 const char* bundleName = CFStringGetCStringPtr(bundleID, kCFStringEncodingASCII);
926 callback(bundleRef, bundleName);
927 }
928
929 CFRelease(sourcePath);
930 CFRelease(sourceURL);
931 CFRelease(bundles);
932 }
933
934 static int listBundles(const ListBundlesOptions& options) {
935 // Verify any required options
936 if (options.directoryPath == nullptr)
937 exit_usage();
938
939 forEachBundle(options.directoryPath, ^(CFBundleRef bundleRef, const char* bundleName){
940 printf("Bundle: %s\n", bundleName);
941 });
942
943 return 0;
944 }
945
946 static CFDataRef
947 createKernelCollectionForArch(const CreateKernelCollectionOptions& options, const char* arch,
948 Diagnostics& diag) {
949 const GradedArchs& archs = GradedArchs::forName(arch);
950 Platform platform = Platform::unknown;
951
952
953 KernelCollectionBuilder* kcb = nullptr;
954 {
955 CFStringRef archStringRef = CFStringCreateWithCString(kCFAllocatorDefault, arch, kCFStringEncodingASCII);
956 BuildOptions_v1 buildOptions = { 1, options.collectionKind, options.stripMode, archStringRef, options.verbose };
957 kcb = createKernelCollectionBuilder(&buildOptions);
958 CFRelease(archStringRef);
959 }
960
961 FileSystemPhysical fileSystem;
962 LoadedFileInfo kernelCollectionLoadedFileInfo;
963
964 auto loadKernelCollection = ^(const char* kernelCollectionPath, CollectionKind collectionKind) {
965 if (!fileSystem.fileExists(kernelCollectionPath)) {
966 fprintf(stderr, "kernel collection path does not exist: %s\n", options.kernelPath);
967 return false;
968 }
969 LoadedFileInfo info;
970 char realerPath[MAXPATHLEN];
971 bool loadedFile = fileSystem.loadFile(kernelCollectionPath, info, realerPath, ^(const char *format, ...) {
972 va_list list;
973 va_start(list, format);
974 diag.error(format, list);
975 va_end(list);
976 });
977 if ( !loadedFile )
978 return false;
979 CFStringRef pathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, kernelCollectionPath, kCFStringEncodingASCII);
980 CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)info.fileContent, info.fileContentLen, kCFAllocatorNull);
981 if ( !addCollectionFile(kcb, pathStringRef, dataRef, collectionKind) ) {
982 diag.error("Could not load kernel collection file");
983 return false;
984 }
985 CFRelease(dataRef);
986 CFRelease(pathStringRef);
987 return true;
988 };
989
990 switch (options.collectionKind) {
991 case unknownKC:
992 fprintf(stderr, "Invalid kernel collection kind\n");
993 exit(1);
994 case baseKC: {
995 if (!fileSystem.fileExists(options.kernelPath)) {
996 fprintf(stderr, "Kernel path does not exist: %s\n", options.kernelPath);
997 return {};
998 }
999 LoadedFileInfo info;
1000 char realerPath[MAXPATHLEN];
1001 bool loadedFile = fileSystem.loadFile(options.kernelPath, info, realerPath, ^(const char *format, ...) {
1002 va_list list;
1003 va_start(list, format);
1004 diag.error(format, list);
1005 va_end(list);
1006 });
1007 if ( !loadedFile )
1008 return {};
1009 CFStringRef pathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, options.kernelPath, kCFStringEncodingASCII);
1010 CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)info.fileContent, info.fileContentLen, kCFAllocatorNull);
1011 if ( !addKernelFile(kcb, pathStringRef, dataRef) ) {
1012 uint64_t errorCount = 0;
1013 const char* const* errors = getErrors(kcb, &errorCount);
1014 for (uint64_t i = 0; i != errorCount; ++i)
1015 diag.error("Could not load kernel file because: '%s'", errors[i]);
1016 return {};
1017 }
1018 CFRelease(dataRef);
1019 CFRelease(pathStringRef);
1020 break;
1021 }
1022 case pageableKC:
1023 if ( !loadKernelCollection(options.kernelCollectionPath, baseKC) )
1024 return {};
1025 break;
1026 case auxKC:
1027 if ( !loadKernelCollection(options.kernelCollectionPath, baseKC) )
1028 return {};
1029 // Pageable is optional
1030 if ( options.pageableCollectionPath != nullptr ) {
1031 if ( !loadKernelCollection(options.pageableCollectionPath, pageableKC) )
1032 return {};
1033 }
1034 break;
1035 }
1036
1037 if ( !options.bundleIDs.empty() ) {
1038 struct BundleData {
1039 std::string executablePath;
1040 std::string bundlePath;
1041 std::vector<std::string> dependencies;
1042 CFDictionaryRef infoPlist = nullptr;
1043 };
1044 __block std::map<std::string, BundleData> foundBundles;
1045
1046 // Look for bundles in the extensions directory and any PlugIns directories its kext's contain
1047 __block std::list<std::pair<std::string, bool>> kextDirectoriesToProcess;
1048 kextDirectoriesToProcess.push_back({ options.extensionsPath, true });
1049 while ( !kextDirectoriesToProcess.empty() ) {
1050 std::string kextDir = kextDirectoriesToProcess.front().first;
1051 bool lookForPlugins = kextDirectoriesToProcess.front().second;
1052 kextDirectoriesToProcess.pop_front();
1053
1054 __block bool foundError = false;
1055 forEachBundle(kextDir.c_str(), ^(CFBundleRef bundleRef, const char* bundleName) {
1056
1057 if (foundError)
1058 return;
1059
1060 // If the directory contains a PlugIns directory, then add it to the list to seach for kexts
1061 if (lookForPlugins) {
1062 CFURLRef pluginsRelativeURL = CFBundleCopyBuiltInPlugInsURL(bundleRef);
1063 if ( pluginsRelativeURL != nullptr ) {
1064 CFURLRef pluginsAbsoluteURL = CFURLCopyAbsoluteURL(pluginsRelativeURL);
1065 CFStringRef pluginString = CFURLCopyFileSystemPath(pluginsAbsoluteURL, kCFURLPOSIXPathStyle);
1066 const char* pluginPath = CFStringGetCStringPtr(pluginString, kCFStringEncodingASCII);
1067 kextDirectoriesToProcess.push_back({ pluginPath, false });
1068
1069 CFRelease(pluginString);
1070 CFRelease(pluginsAbsoluteURL);
1071 CFRelease(pluginsRelativeURL);
1072 }
1073 }
1074
1075 #if 0
1076 // For now always load bundles as we don't require every bundle to be listed on the command line
1077 // but can instead bring them in on demand.
1078
1079 // Once we've looked for plugins, if we don't want this bundle then we can skip validating it.
1080 if ( foundBundles.count(bundleName) == 0 )
1081 return;
1082 #endif
1083
1084 BundleData bundleData;
1085 bundleData.infoPlist = CFBundleGetInfoDictionary(bundleRef);
1086
1087 CFURLRef bundleExecutableRelativeURL = CFBundleCopyExecutableURL(bundleRef);
1088
1089 // Its ok to be missing an executable. We'll just skip this bundle
1090 if ( bundleExecutableRelativeURL == nullptr ) {
1091 // FIXME: Its possibly not ok to be missing the executable if its actually listed
1092 // as a CFBundleExecutable path in the plist
1093 foundBundles[bundleName] = bundleData;
1094 return;
1095 }
1096
1097 CFURLRef bundleExecutableAbsoluteURL = CFURLCopyAbsoluteURL(bundleExecutableRelativeURL);
1098 CFStringRef bundleExecutableString = CFURLCopyFileSystemPath(bundleExecutableAbsoluteURL, kCFURLPOSIXPathStyle);
1099 const char* bundleExecutablePath = CFStringGetCStringPtr(bundleExecutableString, kCFStringEncodingASCII);
1100
1101 // Check for an arch specific dependency list first
1102 std::string archBundleLibraries = std::string("OSBundleLibraries") + "_" + arch;
1103 CFStringRef archBundleLibrariesStringRef = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, archBundleLibraries.c_str(),
1104 kCFStringEncodingASCII, kCFAllocatorNull);
1105 CFTypeRef depsRef = CFBundleGetValueForInfoDictionaryKey(bundleRef, archBundleLibrariesStringRef);
1106 if ( depsRef == nullptr ) {
1107 // No arch specific deps, so try the defaults
1108 depsRef = CFBundleGetValueForInfoDictionaryKey(bundleRef, CFSTR("OSBundleLibraries"));
1109 }
1110 if (depsRef != nullptr) {
1111 if (CFGetTypeID(depsRef) != CFDictionaryGetTypeID()) {
1112 fprintf(stderr, "Bad bundle '%s' (\"OSBundleLibraries\" is not a dictionary)\n", bundleName);
1113 foundError = true;
1114 return;
1115 }
1116
1117 CFDictionaryRef dictRef = (CFDictionaryRef)depsRef;
1118 CFDictionaryApplyFunction(dictRef, [](const void *key, const void *value, void *context) {
1119 BundleData* bundleData = (BundleData*)context;
1120 CFStringRef keyRef = (CFStringRef)key;
1121 //CFStringRef valueRef = (CFStringRef)value;
1122 bundleData->dependencies.push_back(CFStringGetCStringPtr(keyRef, kCFStringEncodingASCII));
1123 }, &bundleData);
1124 }
1125
1126 // Make sure no-one tries to link the kernel directly. They must do so via symbol sets
1127 if ( !bundleData.dependencies.empty() ) {
1128 for (const std::string& dep : bundleData.dependencies) {
1129 if (dep == "com.apple.kernel") {
1130 fprintf(stderr, "Rejecting bundle '%s' as it is trying to link directly to the kernel\n",
1131 bundleName);
1132 foundError = true;
1133 return;
1134 }
1135 }
1136 }
1137
1138 bundleData.executablePath = bundleExecutablePath;
1139
1140 CFURLRef bundleURLRef = CFBundleCopyBundleURL(bundleRef);
1141 CFStringRef bundleURLString = CFURLCopyFileSystemPath(bundleURLRef, kCFURLPOSIXPathStyle);
1142 const char* bundleURLPath = CFStringGetCStringPtr(bundleURLString, kCFStringEncodingASCII);
1143 if (strncmp(bundleURLPath, options.extensionsPath, strlen(options.extensionsPath)) != 0) {
1144 fprintf(stderr, "Bundle path '%s' is not within extensions directory '%s'\n",
1145 bundleURLPath, options.extensionsPath);
1146 }
1147 // Don't remove the whole extensions prefix, but instead the volume root, if we have one
1148 bundleData.bundlePath = bundleURLPath + strlen(options.volumeRoot);
1149 foundBundles[bundleName] = bundleData;
1150
1151 CFRelease(bundleExecutableString);
1152 CFRelease(bundleExecutableAbsoluteURL);
1153 CFRelease(bundleExecutableRelativeURL);
1154 });
1155
1156 if (foundError)
1157 return {};
1158 }
1159
1160 __block std::set<std::string> existingBundles;
1161 auto addSymbolSetsBundleIDs = ^(const dyld3::MachOAnalyzer* kernelMA) {
1162 assert(kernelMA != nullptr);
1163
1164 __block std::list<std::string> nonASCIIStrings;
1165 auto getString = ^(Diagnostics& diags, CFStringRef symbolNameRef) {
1166 const char* symbolName = CFStringGetCStringPtr(symbolNameRef, kCFStringEncodingUTF8);
1167 if ( symbolName != nullptr )
1168 return symbolName;
1169
1170 CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef), kCFStringEncodingUTF8);
1171 char buffer[len + 1];
1172 if ( !CFStringGetCString(symbolNameRef, buffer, len, kCFStringEncodingUTF8) ) {
1173 diags.error("Could not convert string to ASCII");
1174 return (const char*)nullptr;
1175 }
1176 buffer[len] = '\0';
1177 nonASCIIStrings.push_back(buffer);
1178 return nonASCIIStrings.back().c_str();
1179 };
1180
1181 uint64_t symbolSetsSize = 0;
1182 const void* symbolSetsContent = kernelMA->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize);
1183 if ( symbolSetsContent != nullptr ) {
1184
1185 // A helper to automatically call CFRelease when we go out of scope
1186 struct AutoReleaseTypeRef {
1187 AutoReleaseTypeRef() = default;
1188 ~AutoReleaseTypeRef() {
1189 if ( ref != nullptr ) {
1190 CFRelease(ref);
1191 }
1192 }
1193 void setRef(CFTypeRef typeRef) {
1194 assert(ref == nullptr);
1195 ref = typeRef;
1196 }
1197
1198 CFTypeRef ref = nullptr;
1199 };
1200
1201 AutoReleaseTypeRef dataRefReleaser;
1202 AutoReleaseTypeRef plistRefReleaser;
1203
1204 CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)symbolSetsContent, symbolSetsSize, kCFAllocatorNull);
1205 if ( dataRef == nullptr ) {
1206 diag.error("Could not create data ref for symbol sets");
1207 return false;
1208 }
1209 dataRefReleaser.setRef(dataRef);
1210
1211 CFErrorRef errorRef = nullptr;
1212 CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, dataRef, kCFPropertyListImmutable, nullptr, &errorRef);
1213 if (errorRef != nullptr) {
1214 CFStringRef errorString = CFErrorCopyDescription(errorRef);
1215 diag.error("Could not load plist because :%s",
1216 CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
1217 CFRelease(errorRef);
1218 return false;
1219 }
1220 if ( plistRef == nullptr ) {
1221 diag.error("Could not create plist ref for symbol sets");
1222 return false;
1223 }
1224 plistRefReleaser.setRef(plistRef);
1225
1226 if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) {
1227 diag.error("Symbol set plist should be a dictionary");
1228 return false;
1229 }
1230 CFDictionaryRef symbolSetsDictRef = (CFDictionaryRef)plistRef;
1231 CFArrayRef symbolSetArrayRef = (CFArrayRef)CFDictionaryGetValue(symbolSetsDictRef, CFSTR("SymbolsSets"));
1232 if ( symbolSetArrayRef != nullptr ) {
1233 if ( CFGetTypeID(symbolSetArrayRef) != CFArrayGetTypeID() ) {
1234 diag.error("SymbolsSets value should be an array");
1235 return false;
1236 }
1237 for (CFIndex symbolSetIndex = 0; symbolSetIndex != CFArrayGetCount(symbolSetArrayRef); ++symbolSetIndex) {
1238 CFDictionaryRef symbolSetDictRef = (CFDictionaryRef)CFArrayGetValueAtIndex(symbolSetArrayRef, symbolSetIndex);
1239 if ( CFGetTypeID(symbolSetDictRef) != CFDictionaryGetTypeID() ) {
1240 diag.error("Symbol set element should be a dictionary");
1241 return false;
1242 }
1243
1244 // CFBundleIdentifier
1245 CFStringRef bundleIDRef = (CFStringRef)CFDictionaryGetValue(symbolSetDictRef, CFSTR("CFBundleIdentifier"));
1246 if ( (bundleIDRef == nullptr) || (CFGetTypeID(bundleIDRef) != CFStringGetTypeID()) ) {
1247 diag.error("Symbol set bundle ID should be a string");
1248 return false;
1249 }
1250
1251 const char* dylibID = getString(diag, bundleIDRef);
1252 if ( dylibID == nullptr )
1253 return false;
1254 existingBundles.insert(dylibID);
1255 }
1256 }
1257 }
1258 return true;
1259 };
1260
1261 auto addExistingBundleIDs = ^(const char* path, bool isBaseKC) {
1262 char fileRealPath[MAXPATHLEN];
1263 auto kernelCollectionLoadedFileInfo = MachOAnalyzer::load(diag, fileSystem, path, archs, platform, fileRealPath);
1264 if ( diag.hasError() ) {
1265 fprintf(stderr, "Could not load file '%s' because: %s\n", path, diag.errorMessage().c_str());
1266 return false;
1267 }
1268 const MachOAppCache* appCacheMA = (const MachOAppCache*)kernelCollectionLoadedFileInfo.fileContent;
1269 if (appCacheMA == nullptr) {
1270 fprintf(stderr, "Could not load file: %s\n", path);
1271 return false;
1272 }
1273 if ( !appCacheMA->isFileSet() ) {
1274 fprintf(stderr, "kernel collection is not a cache file: %s\n", path);
1275 return false;
1276 }
1277 __block const dyld3::MachOAnalyzer* kernelMA = nullptr;
1278 appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
1279 if ( ma->isStaticExecutable() ) {
1280 kernelMA = ma;
1281 }
1282 existingBundles.insert(name);
1283 });
1284
1285 if ( isBaseKC ) {
1286 if ( !addSymbolSetsBundleIDs(kernelMA) )
1287 return false;
1288 }
1289 fileSystem.unloadFile(kernelCollectionLoadedFileInfo);
1290 return true;
1291 };
1292 if ( options.collectionKind == baseKC ) {
1293 char fileRealPath[MAXPATHLEN];
1294 auto kernelLoadedFileInfo = MachOAnalyzer::load(diag, fileSystem, options.kernelPath, archs, platform, fileRealPath);
1295 if ( diag.hasError() ) {
1296 fprintf(stderr, "Could not load file '%s' because: %s\n", options.kernelPath, diag.errorMessage().c_str());
1297 return {};
1298 }
1299 const MachOAppCache* kernelMA = (const MachOAppCache*)kernelLoadedFileInfo.fileContent;
1300 if (kernelMA == nullptr) {
1301 fprintf(stderr, "Could not load file: %s\n", options.kernelPath);
1302 return {};
1303 }
1304 if ( !kernelMA->isStaticExecutable() ) {
1305 fprintf(stderr, "kernel is not a static executable: %s\n", options.kernelPath);
1306 return {};
1307 }
1308
1309 if ( !addSymbolSetsBundleIDs(kernelMA) )
1310 return {};
1311 fileSystem.unloadFile(kernelLoadedFileInfo);
1312 }
1313 if ( (options.collectionKind == auxKC) || (options.collectionKind == pageableKC) ) {
1314 // Work out which bundle-ids are already in the base KC
1315 if ( !addExistingBundleIDs(options.kernelCollectionPath, true) )
1316 return {};
1317 }
1318 if ( options.pageableCollectionPath != nullptr ) {
1319 // Work out which bundle-ids are already in the pageable KC
1320 if ( !addExistingBundleIDs(options.pageableCollectionPath, false) )
1321 return {};
1322 }
1323
1324 std::set<std::string> processedBundleIDs;
1325 std::list<const char*> bundleIDsToLoad;
1326 bundleIDsToLoad.insert(bundleIDsToLoad.end(), options.bundleIDs.begin(), options.bundleIDs.end());
1327 while (!bundleIDsToLoad.empty()) {
1328 std::string bundleID = bundleIDsToLoad.front();
1329 bundleIDsToLoad.pop_front();
1330
1331 std::string stripModeString;
1332 if ( const char* colonPos = strchr(bundleID.c_str(), ':') ) {
1333 stripModeString = colonPos + 1;
1334 bundleID.erase(colonPos - bundleID.data());
1335 }
1336
1337 // If we've seen this one already then skip it
1338 if (!processedBundleIDs.insert(bundleID).second)
1339 continue;
1340
1341 // Find the bundle for this ID
1342 auto it = foundBundles.find(bundleID);
1343 if (it == foundBundles.end()) {
1344 fprintf(stderr, "[WARNING]: Could not find bundle with ID '%s'\n", bundleID.c_str());
1345 continue;
1346 }
1347
1348 BundleData& bundleData = it->second;
1349
1350 LoadedFileInfo info;
1351
1352 // Codeless kexts don't have an executable path, but we still want to put their
1353 // plist in the prelink info
1354 bool isCodeless = bundleData.executablePath.empty();
1355 if ( !isCodeless ) {
1356 char realerPath[MAXPATHLEN];
1357 bool loadedFile = fileSystem.loadFile(bundleData.executablePath.c_str(), info, realerPath,
1358 ^(const char *format, ...) {
1359 va_list list;
1360 va_start(list, format);
1361 diag.error(format, list);
1362 va_end(list);
1363 });
1364 if ( !loadedFile )
1365 return {};
1366 }
1367
1368 std::vector<const char*> deps;
1369 for (const std::string& dependency : bundleData.dependencies)
1370 deps.push_back(dependency.c_str());
1371
1372 CFStringRef kextPathStringRef = nullptr;
1373 CFDataRef kextDataRef = nullptr;
1374 if ( !isCodeless) {
1375 kextPathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, bundleData.executablePath.c_str(), kCFStringEncodingASCII);
1376 kextDataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)info.fileContent, info.fileContentLen, kCFAllocatorNull);
1377 }
1378
1379 CFMutableArrayRef kextDepsArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, bundleData.dependencies.size(), &kCFTypeArrayCallBacks);
1380 for (const std::string& dependency : bundleData.dependencies) {
1381 CFStringRef depStringRef = CFStringCreateWithCString(kCFAllocatorDefault, dependency.c_str(), kCFStringEncodingASCII);
1382 CFArrayAppendValue(kextDepsArrayRef, depStringRef);
1383 CFRelease(depStringRef);
1384 }
1385 CFStringRef kextBundleIDStringRef = CFStringCreateWithCString(kCFAllocatorDefault, bundleID.c_str(), kCFStringEncodingASCII);
1386 CFStringRef kextBundlePathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, bundleData.bundlePath.c_str(), kCFStringEncodingASCII);
1387
1388 BinaryStripMode stripMode = binaryStripNone;
1389 if ( !stripModeString.empty() ) {
1390 if ( stripModeString == "locals" ) {
1391 stripMode = binaryStripLocals;
1392 } else if ( stripModeString == "exports" ) {
1393 stripMode = binaryStripExports;
1394 } else if ( stripModeString == "all" ) {
1395 stripMode = binaryStripAll;
1396 } else {
1397 diag.error("Unknown strip mode: '%s'", stripModeString.c_str());
1398 return {};
1399 }
1400 }
1401
1402 KextFileData_v1 fileData = { 1, kextPathStringRef, kextDataRef,
1403 kextDepsArrayRef, kextBundleIDStringRef, kextBundlePathStringRef,
1404 bundleData.infoPlist, stripMode };
1405
1406 if ( !addKextDataFile(kcb, &fileData) ) {
1407 uint64_t errorCount = 0;
1408 const char* const* errors = getErrors(kcb, &errorCount);
1409 for (uint64_t i = 0; i != errorCount; ++i)
1410 diag.error("Could not load kext file because: '%s'", errors[i]);
1411 return {};
1412 }
1413
1414 // Walk the dependencies and add any new ones to the list
1415 for (const std::string& dependency : bundleData.dependencies) {
1416 if ( existingBundles.find(dependency) == existingBundles.end() )
1417 bundleIDsToLoad.push_back(dependency.c_str());
1418 }
1419 }
1420
1421 // Filter dependencies to kext's with binaries
1422 #if 0
1423 const std::map<std::string, BundleData>* foundBundlesPtr = &foundBundles;
1424 for (AppCacheBuilder::InputDylib& file : loadedFiles) {
1425 file.dylibDeps.erase(std::remove_if(file.dylibDeps.begin(), file.dylibDeps.end(),
1426 [&](const std::string& depName) {
1427 auto it = foundBundlesPtr->find(depName);
1428 assert(it != foundBundlesPtr->end());
1429 return it->second.executablePath.empty();
1430 }),file.dylibDeps.end());
1431 }
1432 #endif
1433 }
1434
1435 #if 0
1436 for (AppCacheBuilder::InputDylib& file : loadedFiles) {
1437 char fileRealPath[MAXPATHLEN];
1438 const char* path = file.dylib.loadedFileInfo.path;
1439 LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, path, archs, platform, fileRealPath);
1440 if ( diag.hasError() ) {
1441 fprintf(stderr, "Could not load file '%s' because: %s\n", path, diag.errorMessage().c_str());
1442 return {};
1443 }
1444
1445 MachOAnalyzer* ma = (MachOAnalyzer*)loadedFileInfo.fileContent;
1446 if (ma == nullptr) {
1447 fprintf(stderr, "Could not load file: %s\n", path);
1448 return {};
1449 }
1450
1451 auto errorHandler = ^(const char* msg) {
1452 diag.error("Binary located at '%s' cannot be placed in kernel collection because: %s", path, msg);
1453 };
1454 if (ma->canBePlacedInKernelCollection(path, errorHandler)) {
1455 DyldSharedCache::MappedMachO mappedFile(path, ma, loadedFileInfo.sliceLen, false, false,
1456 loadedFileInfo.sliceOffset, loadedFileInfo.mtime,
1457 loadedFileInfo.inode);
1458 CacheBuilder::LoadedMachO loadedMachO = { mappedFile, loadedFileInfo, nullptr };
1459 file.dylib = loadedMachO;
1460 } else {
1461 fileSystem.unloadFile(loadedFileInfo);
1462 }
1463 if ( diag.hasError() ) {
1464 fprintf(stderr, "%s\n", diag.errorMessage().c_str());
1465 return {};
1466 }
1467 }
1468 #endif
1469
1470 #if 0
1471 if (loadedFiles.empty()) {
1472 fprintf(stderr, "Could not find any valid files to create kernel collection\n");
1473
1474 // Since we found no files, print warnings for the ones we tried
1475 if (!diag.warnings().empty()) {
1476 fprintf(stderr, "Failed to use the following files:\n");
1477 for (const std::string& msg : diag.warnings()) {
1478 fprintf(stderr, " %s\n", msg.c_str());
1479 }
1480 }
1481 return {};
1482 }
1483
1484 if (options.verbose) {
1485 for (const AppCacheBuilder::InputDylib& loadedFile : loadedFiles)
1486 fprintf(stderr, "Building cache with file: %s\n", loadedFile.dylib.loadedFileInfo.path);
1487 }
1488 #endif
1489
1490 for (const SectionData& sectData : options.sections) {
1491 CFStringRef segmentName = CFStringCreateWithCString(kCFAllocatorDefault, sectData.segmentName, kCFStringEncodingASCII);
1492 CFStringRef sectionName = nullptr;
1493 if ( sectData.sectionName != nullptr )
1494 sectionName = CFStringCreateWithCString(kCFAllocatorDefault, sectData.sectionName, kCFStringEncodingASCII);
1495
1496 CFDataRef sectionData = nullptr;
1497 {
1498 struct stat stat_buf;
1499 int fd = ::open(sectData.payloadFilePath, O_RDONLY, 0);
1500 if (fd == -1) {
1501 diag.error("can't open file '%s', errno=%d\n", sectData.payloadFilePath, errno);
1502 return {};
1503 }
1504
1505 if (fstat(fd, &stat_buf) == -1) {
1506 diag.error("can't stat open file '%s', errno=%d\n", sectData.payloadFilePath, errno);
1507 ::close(fd);
1508 return {};
1509 }
1510
1511 const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
1512 if (buffer == MAP_FAILED) {
1513 diag.error("mmap() for file at %s failed, errno=%d\n", sectData.payloadFilePath, errno);
1514 ::close(fd);
1515 return {};
1516 }
1517 ::close(fd);
1518
1519 sectionData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)buffer, stat_buf.st_size, kCFAllocatorNull);
1520 }
1521
1522 if ( !addSegmentData(kcb, segmentName, sectionName, sectionData) ) {
1523 uint64_t errorCount = 0;
1524 const char* const* errors = getErrors(kcb, &errorCount);
1525 for (uint64_t i = 0; i != errorCount; ++i)
1526 diag.error("Could not load section data file because: '%s'", errors[i]);
1527 return {};
1528 }
1529 }
1530
1531 if ( options.prelinkInfoExtraData != nullptr ) {
1532 struct stat stat_buf;
1533 int fd = ::open(options.prelinkInfoExtraData, O_RDONLY, 0);
1534 if (fd == -1) {
1535 diag.error("can't open file '%s', errno=%d\n", options.prelinkInfoExtraData, errno);
1536 return {};
1537 }
1538
1539 if (fstat(fd, &stat_buf) == -1) {
1540 diag.error("can't stat open file '%s', errno=%d\n", options.prelinkInfoExtraData, errno);
1541 ::close(fd);
1542 return {};
1543 }
1544
1545 const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
1546 if (buffer == MAP_FAILED) {
1547 diag.error("mmap() for file at %s failed, errno=%d\n", options.prelinkInfoExtraData, errno);
1548 ::close(fd);
1549 return {};
1550 }
1551 ::close(fd);
1552
1553 CFDataRef prelinkInfoData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)buffer, stat_buf.st_size, kCFAllocatorNull);
1554
1555 CFErrorRef errorRef = nullptr;
1556 CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, prelinkInfoData, kCFPropertyListImmutable, nullptr, &errorRef);
1557 if (errorRef != nullptr) {
1558 CFStringRef errorString = CFErrorCopyDescription(errorRef);
1559 diag.error("Could not load prelink info plist because :%s",
1560 CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
1561 CFRelease(errorRef);
1562 return {};
1563 }
1564 if ( plistRef == nullptr ) {
1565 diag.error("Could not create plist ref for prelink info");
1566 return {};
1567 }
1568 if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) {
1569 diag.error("Prelink info plist should be a dictionary");
1570 return {};
1571 }
1572
1573 if ( !addPrelinkInfo(kcb, (CFDictionaryRef)plistRef) ) {
1574 uint64_t errorCount = 0;
1575 const char* const* errors = getErrors(kcb, &errorCount);
1576 for (uint64_t i = 0; i != errorCount; ++i)
1577 diag.error("Could not prelink data file because: '%s'", errors[i]);
1578 return {};
1579 }
1580 }
1581
1582 bool success = runKernelCollectionBuilder(kcb);
1583 uint64_t errorCount = 0;
1584 const char* const* errors = getErrors(kcb, &errorCount);
1585 if ( errors != nullptr ) {
1586 if ( !options.printJSONErrors ) {
1587 for (uint64_t i = 0; i != errorCount; ++i) {
1588 fprintf(stderr, "Could not build kernel collection because '%s'\n", errors[i]);
1589 }
1590 }
1591 CFDictionaryRef errorDictRef = getKextErrors(kcb);
1592 if ( errorDictRef != nullptr ) {
1593 Node rootNode;
1594
1595 CFDictionaryApplyFunction(errorDictRef, [](const void *key, const void *value, void *context) {
1596 Node* rootNode = (Node*)context;
1597 CFStringRef keyRef = (CFStringRef)key;
1598 CFArrayRef valueRef = (CFArrayRef)value;
1599
1600 Node bundleNode;
1601 bundleNode.map["id"] = Node(CFStringGetCStringPtr(keyRef, kCFStringEncodingASCII));
1602
1603 Node errorsNode;
1604 CFArrayApplyFunction(valueRef, CFRangeMake(0, CFArrayGetCount(valueRef)), [](const void *value, void *context) {
1605 Node* errorsNode = (Node*)context;
1606 CFStringRef valueRef = (CFStringRef)value;
1607
1608 errorsNode->array.push_back(Node(CFStringGetCStringPtr(valueRef, kCFStringEncodingASCII)));
1609 }, &errorsNode);
1610
1611 bundleNode.map["errors"] = errorsNode;
1612
1613 rootNode->array.push_back(bundleNode);
1614 }, &rootNode);
1615
1616 // sort the nodes so that the output is reproducible
1617 std::sort(rootNode.array.begin(), rootNode.array.end(),
1618 [](const Node& a, const Node&b) {
1619 return a.map.find("id")->second.value < b.map.find("id")->second.value;
1620 });
1621
1622 printJSON(rootNode);
1623 } else {
1624 Node rootNode;
1625 for (uint64_t i = 0; i != errorCount; ++i) {
1626 rootNode.array.push_back(Node(errors[i]));
1627 }
1628 printJSON(rootNode);
1629 }
1630 return {};
1631 }
1632
1633 if ( !success )
1634 return {};
1635
1636 uint64_t fileResultCount = 0;
1637 const auto* fileResults = getCollectionFileResults(kcb, &fileResultCount);
1638 if ( fileResults == nullptr ) {
1639 fprintf(stderr, "Could not get file results\n");
1640 return {};
1641 }
1642 if ( fileResultCount != 1 ) {
1643 fprintf(stderr, "Unsupported file result count: %lld\n", fileResultCount);
1644 return {};
1645 }
1646
1647 CFDataRef dataRef = fileResults[0]->data;
1648 CFRetain(dataRef);
1649
1650 destroyKernelCollectionBuilder(kcb);
1651
1652 return dataRef;
1653 }
1654
1655 static int createKernelCollection(const CreateKernelCollectionOptions& options) {
1656 // Verify any required options
1657 if (gOpts.archs.empty()) {
1658 exit_usage("-arch");
1659 } else {
1660 std::set<std::string_view> archs(gOpts.archs.begin(), gOpts.archs.end());
1661 if (archs.size() != gOpts.archs.size()) {
1662 fprintf(stderr, "Duplicate -arch specified\n");
1663 exit(1);
1664 }
1665 }
1666 if (options.outputCachePath == nullptr)
1667 exit_usage();
1668
1669 switch (options.stripMode) {
1670 case unknownStripMode:
1671 case stripNone:
1672 break;
1673 case stripAll:
1674 case stripAllKexts:
1675 if ( options.collectionKind != baseKC ) {
1676 fprintf(stderr, "Cannot use -strip-all-kexts with auxKC. Use strip-all instead\n");
1677 exit(1);
1678 }
1679 break;
1680 }
1681
1682 switch (options.collectionKind) {
1683 case unknownKC:
1684 fprintf(stderr, "Invalid kernel collection kind\n");
1685 exit(1);
1686 case baseKC:
1687 if (options.kernelPath == nullptr)
1688 exit_usage("-kernel");
1689 break;
1690 case pageableKC:
1691 case auxKC:
1692 if (options.kernelCollectionPath == nullptr)
1693 exit_usage("-kernel-collection");
1694 break;
1695 }
1696
1697 if ( !options.bundleIDs.empty() ) {
1698 if (options.extensionsPath == nullptr)
1699 exit_usage("-extensions");
1700 }
1701
1702 // Volume root should be a prefix of extensions path
1703 if ( options.extensionsPath != nullptr ) {
1704 if ( strncmp(options.extensionsPath, options.volumeRoot, strlen(options.volumeRoot)) != 0 ) {
1705 fprintf(stderr, "Volume root '%s' is not a prefix of extensions path '%s'\n",
1706 options.volumeRoot, options.extensionsPath);
1707 }
1708 }
1709
1710 std::vector<CFDataRef> buffers;
1711 for (const char* arch : gOpts.archs) {
1712 Diagnostics diag;
1713 CFDataRef bufferRef = createKernelCollectionForArch(options, arch, diag);
1714 if ( diag.hasError() ) {
1715 fprintf(stderr, "%s\n", diag.errorMessage().c_str());
1716 return 1;
1717 }
1718 if ( bufferRef == nullptr ) {
1719 // If we want errors then return 0
1720 if ( options.printJSONErrors )
1721 return 0;
1722 return 1;
1723 }
1724 buffers.push_back(bufferRef);
1725 }
1726
1727 if (buffers.size() == 1) {
1728 // Single arch. Just write the file directly
1729 CFDataRef bufferRef = buffers.front();
1730 if ( !safeSave(CFDataGetBytePtr(bufferRef), CFDataGetLength(bufferRef), options.outputCachePath) ) {
1731 fprintf(stderr, "Could not write app cache\n");
1732 return 1;
1733 }
1734 CFRelease(bufferRef);
1735 } else {
1736 // Multiple buffers. Create a FAT file
1737 std::vector<uint8_t> fatBuffer;
1738
1739 // Add the FAT header to the start of the buffer
1740 fatBuffer.resize(0x4000, 0);
1741 fat_header* header = (fat_header*)&fatBuffer.front();
1742 header->magic = OSSwapHostToBigInt32(FAT_MAGIC);
1743 header->nfat_arch = OSSwapHostToBigInt32((uint32_t)buffers.size());
1744
1745 for (uint32_t i = 0; i != buffers.size(); ++i) {
1746 CFDataRef bufferRef = buffers[i];
1747 mach_header* mh = (mach_header*)CFDataGetBytePtr(bufferRef);
1748
1749 uint32_t offsetInBuffer = (uint32_t)fatBuffer.size();
1750
1751 fat_arch* archBuffer = (fat_arch*)(&fatBuffer.front() + sizeof(fat_header));
1752 archBuffer[i].cputype = OSSwapHostToBigInt32(mh->cputype);
1753 archBuffer[i].cpusubtype = OSSwapHostToBigInt32(mh->cpusubtype);
1754 archBuffer[i].offset = OSSwapHostToBigInt32(offsetInBuffer);
1755 archBuffer[i].size = OSSwapHostToBigInt32((uint32_t)CFDataGetLength(bufferRef));
1756 archBuffer[i].align = OSSwapHostToBigInt32(14);
1757
1758 auto align = [](uint64_t addr, uint8_t p2) {
1759 uint64_t mask = (1 << p2);
1760 return (addr + mask - 1) & (-mask);
1761 };
1762
1763 uint32_t alignedSize = (uint32_t)align((uint32_t)CFDataGetLength(bufferRef), 14);
1764 fatBuffer.resize(fatBuffer.size() + alignedSize, 0);
1765 memcpy(&fatBuffer.front() + offsetInBuffer, CFDataGetBytePtr(bufferRef), CFDataGetLength(bufferRef));
1766 }
1767
1768 if ( !safeSave(&fatBuffer.front(), fatBuffer.size(), options.outputCachePath) ) {
1769 fprintf(stderr, "Could not write app cache\n");
1770 return 1;
1771 }
1772 }
1773
1774 return 0;
1775 }
1776
1777 int main(int argc, const char* argv[]) {
1778 OptionsVariants options;
1779 if (!parseArgs(argc, argv, options))
1780 return 1;
1781
1782 if (std::holds_alternative<DumpOptions>(options)) {
1783 return dumpAppCache(std::get<DumpOptions>(options));
1784 }
1785
1786 if (std::holds_alternative<ValidateOptions>(options)) {
1787 return validateFile(std::get<ValidateOptions>(options));
1788 }
1789
1790 if (std::holds_alternative<ListBundlesOptions>(options)) {
1791 return listBundles(std::get<ListBundlesOptions>(options));
1792 }
1793
1794 if (std::holds_alternative<CreateKernelCollectionOptions>(options)) {
1795 return createKernelCollection(std::get<CreateKernelCollectionOptions>(options));
1796 }
1797
1798 assert(std::holds_alternative<std::monostate>(options));
1799
1800 exit_usage();
1801 }