X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/d3f1e533acc7f70659b8bde9b6c040974f05e03b..bc3b7c8cda49ed8598284a489c0bb9694c67c6a4:/dyld3/dyld_app_cache_util.cpp diff --git a/dyld3/dyld_app_cache_util.cpp b/dyld3/dyld_app_cache_util.cpp new file mode 100644 index 0000000..0fe8495 --- /dev/null +++ b/dyld3/dyld_app_cache_util.cpp @@ -0,0 +1,1801 @@ +/* + * Copyright (c) 2017 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "kernel_collection_builder.h" +#include "ClosureFileSystemPhysical.h" +#include "FileUtils.h" +#include "JSONWriter.h" +#include "MachOAppCache.h" + +using namespace dyld3::json; + +using dyld3::closure::FileSystemPhysical; +using dyld3::closure::LoadedFileInfo; +using dyld3::GradedArchs; +using dyld3::MachOAnalyzer; +using dyld3::MachOAppCache; +using dyld3::Platform; +using dyld3::json::Node; + +__attribute__((noreturn)) +static void exit_usage(const char* missingOption = nullptr) { + if ( missingOption != nullptr ) { + fprintf(stderr, "Missing option '%s'\n", missingOption); + fprintf(stderr, "\n"); + } + + fprintf(stderr, "Usage: dyld_app_cache_util [-layout] [-entrypoint] [-fixups] [-symbols] [-kmod] [-uuid] [-fips] -arch arch -platform platform -app-cache app-cache-path\n"); + fprintf(stderr, " -layout print the layout of an existing app cache\n"); + fprintf(stderr, " -entrypoint print the entrypoint of an existing app cache\n"); + fprintf(stderr, " -fixups print the fixups of an existing app cache\n"); + fprintf(stderr, " -symbols print the symbols of an existing app cache\n"); + fprintf(stderr, " -kmod print the kmod_info of an existing app cache\n"); + fprintf(stderr, " -uuid print the UUID of an existing app cache\n"); + fprintf(stderr, " -fips print the FIPS section of an existing app cache\n"); + fprintf(stderr, "\n"); + + fprintf(stderr, "Usage: dyld_app_cache_util -validate file-path -arch arch -platform platform\n"); + fprintf(stderr, " -validate the path to check is valid for inserting in to an app cache\n"); + fprintf(stderr, "\n"); + + fprintf(stderr, "Usage: dyld_app_cache_util -list-bundles directory-path\n"); + fprintf(stderr, " -list-bundles the directory to index for bundles\n"); + fprintf(stderr, "\n"); + + fprintf(stderr, "Usage: dyld_app_cache_util -create-kernel-collection kernel-collection -kernel kernel-path [-extensions path-to-extensions] [-bundle-id bundle-id]*\n"); + fprintf(stderr, " -create-kernel-collection create a kernel collection and write to the given path\n"); + fprintf(stderr, " -kernel path to the kernel static executable\n"); + fprintf(stderr, " -extensions path to the kernel extensions directory\n"); + fprintf(stderr, " -bundle-id zero or more bundle-ids to link in to the kernel collection\n"); + fprintf(stderr, " -sectcreate segment name, section name, and payload file path for more data to embed in the kernel\n"); + fprintf(stderr, "\n"); + + 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"); + fprintf(stderr, " -create-pageable-kernel-collection create a pageable kernel collection and write to the given path\n"); + fprintf(stderr, " -kernel-collection path to the kernel collection collection\n"); + fprintf(stderr, " -extensions path to the kernel extensions directory\n"); + fprintf(stderr, " -bundle-id zero or more bundle-ids to link in to the kernel collection\n"); + fprintf(stderr, "\n"); + + 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"); + fprintf(stderr, " -create-aux-kernel-collection create an aux kernel collection and write to the given path\n"); + fprintf(stderr, " -kernel-collection path to the kernel collection\n"); + fprintf(stderr, " -pageable-collection path to the pageable collection\n"); + fprintf(stderr, " -extensions path to the kernel extensions directory\n"); + fprintf(stderr, " -bundle-id zero or more bundle-ids to link in to the kernel collection\n"); + fprintf(stderr, "\n"); + + fprintf(stderr, "Common options:\n"); + fprintf(stderr, " -arch xxx the arch to use to create the app cache\n"); + fprintf(stderr, " -platform xxx the platform to use to create the app cache\n"); + + exit(1); +} + +struct DumpOptions { + bool printLayout = false; + bool printEntryPoint = false; + bool printFixups = false; + bool printSymbols = false; + bool printUUID = false; + bool printKModInfo = false; + bool printFIPS = false; +}; + +struct ValidateOptions { + const char* filePath = nullptr; +}; + +struct ListBundlesOptions { + const char* directoryPath = nullptr; +}; + +// The payload of -sectcreate +struct SectionData { + const char* segmentName = nullptr; + const char* sectionName = nullptr; + const char* payloadFilePath = nullptr; +}; + +struct CreateKernelCollectionOptions { + const char* outputCachePath = nullptr; + const char* kernelPath = nullptr; + const char* kernelCollectionPath = nullptr; + const char* pageableCollectionPath = nullptr; + const char* extensionsPath = nullptr; + const char* volumeRoot = ""; + std::vector bundleIDs; + bool verbose = false; + bool printJSONErrors = false; + CollectionKind collectionKind = unknownKC; + StripMode stripMode = unknownStripMode; + std::vector sections; + const char* prelinkInfoExtraData = nullptr; +}; + +typedef std::variant OptionsVariants; + +struct CommonOptions { + const char* appCachePath = nullptr; + std::vector archs; + const char* platform = nullptr; +}; + +CommonOptions gOpts; + +template +static T& exitOrGetState(OptionsVariants& options, const char* argv) { + if (std::holds_alternative(options)) { + return options.emplace(); + } + if (std::holds_alternative(options)) + return std::get(options); + exit_usage(); +} + +static bool parseArgs(int argc, const char* argv[], OptionsVariants& options) { + for (int i = 1; i < argc; ++i) { + const char* arg = argv[i]; + if (arg[0] != '-') { + fprintf(stderr, "unknown option: %s\n", arg); + exit_usage(); + } + + // Common options + if (strcmp(arg, "-app-cache") == 0) { + if (gOpts.appCachePath != nullptr) + exit_usage(); + gOpts.appCachePath = argv[++i]; + continue; + } + if (strcmp(arg, "-arch") == 0) { + gOpts.archs.push_back(argv[++i]); + continue; + } + if (strcmp(arg, "-platform") == 0) { + if (gOpts.platform != nullptr) + exit_usage(); + gOpts.platform = argv[++i]; + continue; + } + + // DumpOptions + if (strcmp(arg, "-layout") == 0) { + exitOrGetState(options, arg).printLayout = true; + continue; + } + if (strcmp(arg, "-entrypoint") == 0) { + exitOrGetState(options, arg).printEntryPoint = true; + continue; + } + if (strcmp(arg, "-fixups") == 0) { + exitOrGetState(options, arg).printFixups = true; + continue; + } + if (strcmp(arg, "-symbols") == 0) { + exitOrGetState(options, arg).printSymbols = true; + continue; + } + if (strcmp(arg, "-uuid") == 0) { + exitOrGetState(options, arg).printUUID = true; + continue; + } + if (strcmp(arg, "-kmod") == 0) { + exitOrGetState(options, arg).printKModInfo = true; + continue; + } + if (strcmp(arg, "-fips") == 0) { + exitOrGetState(options, arg).printFIPS = true; + continue; + } + + // ValidateOptions + if (strcmp(arg, "-validate") == 0) { + exitOrGetState(options, arg).filePath = argv[++i]; + continue; + } + + // ListBundlesOptions + if (strcmp(arg, "-list-bundles") == 0) { + exitOrGetState(options, arg).directoryPath = argv[++i]; + continue; + } + + // CreateKernelCollectionOptions + if (strcmp(arg, "-create-kernel-collection") == 0) { + exitOrGetState(options, arg).outputCachePath = argv[++i]; + exitOrGetState(options, arg).collectionKind = baseKC; + continue; + } + if (strcmp(arg, "-kernel") == 0) { + exitOrGetState(options, arg).kernelPath = argv[++i]; + continue; + } + if (strcmp(arg, "-create-pageable-kernel-collection") == 0) { + exitOrGetState(options, arg).outputCachePath = argv[++i]; + exitOrGetState(options, arg).collectionKind = pageableKC; + continue; + } + if (strcmp(arg, "-create-aux-kernel-collection") == 0) { + exitOrGetState(options, arg).outputCachePath = argv[++i]; + exitOrGetState(options, arg).collectionKind = auxKC; + continue; + } + if (strcmp(arg, "-kernel-collection") == 0) { + exitOrGetState(options, arg).kernelCollectionPath = argv[++i]; + continue; + } + if (strcmp(arg, "-pageable-collection") == 0) { + exitOrGetState(options, arg).pageableCollectionPath = argv[++i]; + continue; + } + if (strcmp(arg, "-extensions") == 0) { + exitOrGetState(options, arg).extensionsPath = argv[++i]; + continue; + } + if (strcmp(arg, "-volume-root") == 0) { + exitOrGetState(options, arg).volumeRoot = argv[++i]; + continue; + } + if (strcmp(arg, "-bundle-id") == 0) { + exitOrGetState(options, arg).bundleIDs.push_back(argv[++i]); + continue; + } + if (strcmp(arg, "-verbose") == 0) { + exitOrGetState(options, arg).verbose = true; + continue; + } + if (strcmp(arg, "-json-errors") == 0) { + exitOrGetState(options, arg).printJSONErrors = true; + continue; + } + if (strcmp(arg, "-strip-all") == 0) { + exitOrGetState(options, arg).stripMode = stripAll; + continue; + } + if (strcmp(arg, "-strip-all-kexts") == 0) { + exitOrGetState(options, arg).stripMode = stripAllKexts; + continue; + } + if (strcmp(arg, "-sectcreate") == 0) { + const char* segmentName = argv[++i]; + const char* sectionName = argv[++i]; + const char* payloadFilePath = argv[++i]; + SectionData sectData = { segmentName, sectionName, payloadFilePath }; + exitOrGetState(options, arg).sections.push_back(sectData); + continue; + } + if (strcmp(arg, "-prelink-info-extra") == 0) { + const char* payloadFilePath = argv[++i]; + exitOrGetState(options, arg).prelinkInfoExtraData = payloadFilePath; + continue; + } + + + fprintf(stderr, "unknown option: %s\n", arg); + exit_usage(); + } + + return true; +} + +static Platform stringToPlatform(const std::string& str) { + if (str == "unknown") + return Platform::unknown; + if (str == "macOS") + return Platform::macOS; + if (str == "iOS") + return Platform::iOS; + if (str == "tvOS") + return Platform::tvOS; + if (str == "watchOS") + return Platform::watchOS; + if (str == "bridgeOS") + return Platform::bridgeOS; + if (str == "iOSMac") + return Platform::iOSMac; + if (str == "UIKitForMac") + return Platform::iOSMac; + if (str == "iOS_simulator") + return Platform::iOS_simulator; + if (str == "tvOS_simulator") + return Platform::tvOS_simulator; + if (str == "watchOS_simulator") + return Platform::watchOS_simulator; + return Platform::unknown; +} + +static int dumpAppCache(const DumpOptions& options) { + // Verify any required options + if (gOpts.archs.size() != 1) + exit_usage("-arch"); + if (gOpts.platform == nullptr) + exit_usage("-platform"); + + if (gOpts.appCachePath == nullptr) + exit_usage(); + + FileSystemPhysical fileSystem; + if (!fileSystem.fileExists(gOpts.appCachePath)) { + fprintf(stderr, "App-cache path does not exist: %s\n", gOpts.appCachePath); + return 1; + } + + const GradedArchs& archs = GradedArchs::forName(gOpts.archs[0]); + Platform platform = Platform::unknown; + bool isKernelCollection = false; + + // HACK: Pass a real option for building a kernel app cache + if (!strcmp(gOpts.platform, "kernel")) { + isKernelCollection = true; + } else { + platform = stringToPlatform(gOpts.platform); + if (platform == Platform::unknown) { + fprintf(stderr, "Could not create app cache because: unknown platform '%s'\n", gOpts.platform); + return 1; + } + } + + __block Diagnostics diag; + char appCacheRealPath[MAXPATHLEN]; + LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, gOpts.appCachePath, archs, platform, appCacheRealPath); + if (diag.hasError()) { + fprintf(stderr, "Could not load app cache because: %s\n", diag.errorMessage().c_str()); + return 1; + } + + MachOAppCache* appCacheMA = (MachOAppCache*)loadedFileInfo.fileContent; + if (appCacheMA == nullptr) { + fprintf(stderr, "Could not load app cache: %s\n", gOpts.appCachePath); + return 1; + } + + if (options.printLayout) { + __block Node topNode; + + // Add the segments for the app cache + __block Node segmentsNode; + __block bool hasError = false; + appCacheMA->forEachSegment(^(const dyld3::MachOFile::SegmentInfo &info, bool &stop) { + Node segmentNode; + segmentNode.map["name"] = makeNode(info.segName); + segmentNode.map["vmAddr"] = makeNode(hex(info.vmAddr)); + segmentNode.map["vmSize"] = makeNode(hex(info.vmSize)); + segmentNode.map["vmEnd"] = makeNode(hex(info.vmAddr + info.vmSize)); + switch (info.protections) { + case VM_PROT_READ: + segmentNode.map["permissions"] = makeNode("r--"); + break; + case VM_PROT_WRITE: + segmentNode.map["permissions"] = makeNode("-w-"); + break; + case VM_PROT_EXECUTE: + segmentNode.map["permissions"] = makeNode("--x"); + break; + case VM_PROT_READ | VM_PROT_WRITE: + segmentNode.map["permissions"] = makeNode("rw-"); + break; + case VM_PROT_READ | VM_PROT_EXECUTE: + segmentNode.map["permissions"] = makeNode("r-x"); + break; + case VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE: + segmentNode.map["permissions"] = makeNode("rwx"); + break; + default: + fprintf(stderr, "Unknown permissions on segment '%s'\n", info.segName); + hasError = true; + stop = true; + } + + __block Node sectionsNode; + appCacheMA->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo §Info, bool malformedSectionRange, bool &stop) { + if ( strncmp(sectInfo.segInfo.segName, info.segName, 16) != 0 ) + return; + + Node sectionNode; + sectionNode.map["name"] = makeNode(sectInfo.sectName); + sectionNode.map["vmAddr"] = makeNode(hex(sectInfo.sectAddr)); + sectionNode.map["vmSize"] = makeNode(hex(sectInfo.sectSize)); + sectionNode.map["vmEnd"] = makeNode(hex(sectInfo.sectAddr + sectInfo.sectSize)); + + sectionsNode.array.push_back(sectionNode); + }); + + if ( !sectionsNode.array.empty() ) { + segmentNode.map["sections"] = sectionsNode; + } + + segmentsNode.array.push_back(segmentNode); + }); + + if (hasError) + return 1; + + topNode.map["cache-segments"] = segmentsNode; + + // Map from name to relative path + __block std::unordered_map relativePaths; + appCacheMA->forEachPrelinkInfoLibrary(diag, ^(const char *bundleName, const char* relativePath, + const std::vector &deps) { + if ( relativePath != nullptr ) + relativePaths[bundleName] = relativePath; + }); + + __block Node dylibsNode; + appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) { + __block Node segmentsNode; + ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo &info, bool &stop) { + Node segmentNode; + segmentNode.map["name"] = makeNode(info.segName); + segmentNode.map["vmAddr"] = makeNode(hex(info.vmAddr)); + segmentNode.map["vmSize"] = makeNode(hex(info.vmSize)); + segmentNode.map["vmEnd"] = makeNode(hex(info.vmAddr + info.vmSize)); + + switch (info.protections) { + case VM_PROT_READ: + segmentNode.map["permissions"] = makeNode("r--"); + break; + case VM_PROT_WRITE: + segmentNode.map["permissions"] = makeNode("-w-"); + break; + case VM_PROT_EXECUTE: + segmentNode.map["permissions"] = makeNode("--x"); + break; + case VM_PROT_READ | VM_PROT_WRITE: + segmentNode.map["permissions"] = makeNode("rw-"); + break; + case VM_PROT_READ | VM_PROT_EXECUTE: + segmentNode.map["permissions"] = makeNode("r-x"); + break; + default: + fprintf(stderr, "Unknown permissions on segment '%s'\n", info.segName); + hasError = true; + stop = true; + } + + __block Node sectionsNode; + ma->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo §Info, bool malformedSectionRange, bool &stop) { + if ( strncmp(sectInfo.segInfo.segName, info.segName, 16) != 0 ) + return; + + Node sectionNode; + sectionNode.map["name"] = makeNode(sectInfo.sectName); + sectionNode.map["vmAddr"] = makeNode(hex(sectInfo.sectAddr)); + sectionNode.map["vmSize"] = makeNode(hex(sectInfo.sectSize)); + sectionNode.map["vmEnd"] = makeNode(hex(sectInfo.sectAddr + sectInfo.sectSize)); + + sectionsNode.array.push_back(sectionNode); + }); + + if ( !sectionsNode.array.empty() ) { + segmentNode.map["sections"] = sectionsNode; + } + + segmentsNode.array.push_back(segmentNode); + }); + + Node dylibNode; + dylibNode.map["name"] = makeNode(name); + dylibNode.map["segments"] = segmentsNode; + + auto relativePathIt = relativePaths.find(name); + if ( relativePathIt != relativePaths.end() ) + dylibNode.map["relativePath"] = makeNode(relativePathIt->second); + + dylibsNode.array.push_back(dylibNode); + }); + + topNode.map["dylibs"] = dylibsNode; + + printJSON(topNode, 0, std::cout); + } + + if (options.printEntryPoint) { + __block Node topNode; + + // add entry + uint64_t entryOffset; + bool usesCRT; + Node entryPointNode; + if ( appCacheMA->getEntry(entryOffset, usesCRT) ) { + entryPointNode.value = hex(appCacheMA->preferredLoadAddress() + entryOffset); + } + + topNode.map["entrypoint"] = entryPointNode; + + printJSON(topNode, 0, std::cout); + } + + if (options.printFixups) { + __block Node topNode; + + __block uint64_t baseAddress = ~0ULL; + appCacheMA->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) { + baseAddress = std::min(baseAddress, info.vmAddr); + }); + uint64_t cacheBaseAddress = baseAddress; + uint64_t textSegVMAddr = appCacheMA->preferredLoadAddress(); + + auto getFixupsNode = [cacheBaseAddress, textSegVMAddr](const dyld3::MachOAnalyzer* ma) { + __block Node fixupsNode; + + if (!ma->hasChainedFixups()) { + return makeNode("none"); + } + + // Keep track of the fixups seen by chained fixups. The remainder might be + // classic relocs if we are the x86_64 kernel collection + __block std::set seenFixupVMOffsets; + + __block Diagnostics diag; + ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) { + ma->forEachFixupInAllChains(diag, starts, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) { + uint64_t vmOffset = (uint8_t*)fixupLoc - (uint8_t*)ma; + seenFixupVMOffsets.insert(vmOffset); + + // Correct for __DATA being before __TEXT, in which case the offset + // is from __DATA, not a mach header offset + vmOffset += (textSegVMAddr - cacheBaseAddress); + + fixupsNode.map[hex(vmOffset)] = makeNode("fixup"); + switch (segInfo->pointer_format) { + case DYLD_CHAINED_PTR_64_KERNEL_CACHE: + case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE: { + uint64_t targetVMOffset = fixupLoc->kernel64.target; + uint64_t targetVMAddr = targetVMOffset + cacheBaseAddress; + std::string level = "kc(" + decimal(fixupLoc->kernel64.cacheLevel) + ")"; + std::string fixup = level + " + " + hex(targetVMAddr); + if (fixupLoc->kernel64.isAuth) { + fixup += " auth("; + fixup += fixupLoc->kernel64.keyName(); + fixup += " "; + fixup += fixupLoc->kernel64.addrDiv ? "addr" : "!addr"; + fixup += " "; + fixup += decimal(fixupLoc->kernel64.diversity); + fixup += ")"; + } + fixupsNode.map[hex(vmOffset)] = makeNode(fixup); + break; + } + default: + diag.error("unknown pointer type %d", segInfo->pointer_format); + break; + } + }); + }); + diag.assertNoError(); + + ma->forEachRebase(diag, ^(const char *opcodeName, const dyld3::MachOAnalyzer::LinkEditInfo &leInfo, + const dyld3::MachOAnalyzer::SegmentInfo *segments, + bool segIndexSet, uint32_t pointerSize, uint8_t segmentIndex, + uint64_t segmentOffset, dyld3::MachOAnalyzer::Rebase kind, bool &stop) { + uint64_t rebaseVmAddr = segments[segmentIndex].vmAddr + segmentOffset; + uint64_t runtimeOffset = rebaseVmAddr - textSegVMAddr; + const uint8_t* fixupLoc = (const uint8_t*)ma + runtimeOffset; + + // Correct for __DATA being before __TEXT, in which case the offset + // is from __DATA, not a mach header offset + runtimeOffset += (textSegVMAddr - cacheBaseAddress); + + std::string fixup = "kc(0) + "; + switch ( kind ) { + case dyld3::MachOAnalyzer::Rebase::unknown: + fixup += " : unhandled"; + break; + case dyld3::MachOAnalyzer::Rebase::pointer32: { + uint32_t value = *(uint32_t*)(fixupLoc); + fixup += hex(value) + " : pointer32"; + break; + } + case dyld3::MachOAnalyzer::Rebase::pointer64: { + uint64_t value = *(uint64_t*)(fixupLoc); + fixup += hex(value) + " : pointer64"; + break; + } + case dyld3::MachOAnalyzer::Rebase::textPCrel32: + fixup += " : pcrel32"; + break; + case dyld3::MachOAnalyzer::Rebase::textAbsolute32: + fixup += " : absolute32"; + break; + } + fixupsNode.map[hex(runtimeOffset)] = makeNode(fixup); + }); + diag.assertNoError(); + + return fixupsNode; + }; + + topNode.map["fixups"] = getFixupsNode(appCacheMA); + + __block Node dylibsNode; + appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) { + Node dylibNode; + dylibNode.map["name"] = makeNode(name); + dylibNode.map["fixups"] = getFixupsNode(ma); + + dylibsNode.array.push_back(dylibNode); + }); + + topNode.map["dylibs"] = dylibsNode; + + printJSON(topNode, 0, std::cout); + } + + if (options.printSymbols) { + __block Node topNode; + + __block Node dylibsNode; + appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) { + __block Node globalSymbolsNode; + ma->forEachGlobalSymbol(diag, ^(const char *symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) { + Node symbolNode; + symbolNode.map["name"] = makeNode(symbolName); + symbolNode.map["vmAddr"] = makeNode(hex(n_value)); + + globalSymbolsNode.array.push_back(symbolNode); + }); + + __block Node localSymbolsNode; + ma->forEachLocalSymbol(diag, ^(const char *symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) { + Node symbolNode; + symbolNode.map["name"] = makeNode(symbolName); + symbolNode.map["vmAddr"] = makeNode(hex(n_value)); + + localSymbolsNode.array.push_back(symbolNode); + }); + + if (globalSymbolsNode.array.empty()) + globalSymbolsNode = makeNode("none"); + if (localSymbolsNode.array.empty()) + localSymbolsNode = makeNode("none"); + + Node dylibNode; + dylibNode.map["name"] = makeNode(name); + dylibNode.map["global-symbols"] = globalSymbolsNode; + dylibNode.map["local-symbols"] = localSymbolsNode; + + dylibsNode.array.push_back(dylibNode); + }); + + topNode.map["dylibs"] = dylibsNode; + + printJSON(topNode, 0, std::cout); + } + + if (options.printUUID) { + __block Node topNode; + + // add uuid + Node uuidNode; + uuid_t uuid = {}; + if ( appCacheMA->getUuid(uuid) ) { + uuid_string_t uuidString; + uuid_unparse_upper(uuid, uuidString); + uuidNode.value = uuidString; + } + + topNode.map["uuid"] = uuidNode; + + auto getPlistUUID = ^(const char* jsonNodeName, CFStringRef keyName) { + uuid_t uuid = {}; + const uint8_t* prelinkInfoBuffer = nullptr; + uint64_t prelinkInfoBufferSize = 0; + prelinkInfoBuffer = (const uint8_t*)appCacheMA->findSectionContent("__PRELINK_INFO", "__info", prelinkInfoBufferSize); + if ( prelinkInfoBuffer != nullptr ) { + CFReadStreamRef readStreamRef = CFReadStreamCreateWithBytesNoCopy(kCFAllocatorDefault, prelinkInfoBuffer, prelinkInfoBufferSize, kCFAllocatorNull); + if ( !CFReadStreamOpen(readStreamRef) ) { + fprintf(stderr, "Could not open plist stream\n"); + exit(1); + } + CFErrorRef errorRef = nullptr; + CFPropertyListRef plistRef = CFPropertyListCreateWithStream(kCFAllocatorDefault, readStreamRef, prelinkInfoBufferSize, kCFPropertyListImmutable, nullptr, &errorRef); + if ( errorRef != nullptr ) { + CFStringRef stringRef = CFErrorCopyFailureReason(errorRef); + fprintf(stderr, "Could not read plist because: %s\n", CFStringGetCStringPtr(stringRef, kCFStringEncodingASCII)); + CFRelease(stringRef); + exit(1); + } + assert(CFGetTypeID(plistRef) == CFDictionaryGetTypeID()); + CFDataRef uuidDataRef = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)plistRef, keyName); + if ( uuidDataRef != nullptr ) { + CFDataGetBytes(uuidDataRef, CFRangeMake(0, CFDataGetLength(uuidDataRef)), uuid); + + Node uuidNode; + uuid_string_t uuidString; + uuid_unparse_upper(uuid, uuidString); + uuidNode.value = uuidString; + topNode.map[jsonNodeName] = uuidNode; + } + CFRelease(plistRef); + CFRelease(readStreamRef); + } + }; + + getPlistUUID("prelink-info-uuid", CFSTR("_PrelinkKCID")); + + // If we are an auxKC, then we should also have a reference to the baseKC UUID + getPlistUUID("prelink-info-base-uuid", CFSTR("_BootKCID")); + + // If we are an pageableKC, then we should also have a reference to the pageableKC UUID + getPlistUUID("prelink-info-pageable-uuid", CFSTR("_PageableKCID")); + + printJSON(topNode, 0, std::cout); + } + + if (options.printKModInfo) { + __block Node topNode; + + appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) { + Node dylibNode; + dylibNode.map["name"] = makeNode(name); + + // Check for a global first + __block uint64_t kmodInfoVMOffset = 0; + __block bool found = false; + { + dyld3::MachOAnalyzer::FoundSymbol foundInfo; + found = ma->findExportedSymbol(diag, "_kmod_info", true, foundInfo, nullptr); + if ( found ) { + kmodInfoVMOffset = foundInfo.value; + } + } + // And fall back to a local if we need to + if ( !found ) { + ma->forEachLocalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type, + uint8_t n_sect, uint16_t n_desc, bool& stop) { + if ( strcmp(aSymbolName, "_kmod_info") == 0 ) { + kmodInfoVMOffset = n_value - ma->preferredLoadAddress(); + found = true; + stop = true; + } + }); + } + + if ( found ) { + dyld3::MachOAppCache::KModInfo64_v1* kmodInfo = (dyld3::MachOAppCache::KModInfo64_v1*)((uint8_t*)ma + kmodInfoVMOffset); + Node kmodInfoNode; + kmodInfoNode.map["info-version"] = makeNode(decimal(kmodInfo->info_version)); + kmodInfoNode.map["name"] = makeNode((const char*)&kmodInfo->name[0]); + kmodInfoNode.map["version"] = makeNode((const char*)&kmodInfo->version[0]); + kmodInfoNode.map["address"] = makeNode(hex(kmodInfo->address)); + kmodInfoNode.map["size"] = makeNode(hex(kmodInfo->size)); + + dylibNode.map["kmod_info"] = kmodInfoNode; + } else { + dylibNode.map["kmod_info"] = makeNode("none"); + } + + topNode.array.push_back(dylibNode); + }); + + printJSON(topNode, 0, std::cout); + } + + if (options.printFIPS) { + __block Node topNode; + + appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) { + if ( strcmp(name, "com.apple.kec.corecrypto") != 0 ) + return; + + uint64_t hashStoreSize; + const void* hashStoreLocation = ma->findSectionContent("__TEXT", "__fips_hmacs", hashStoreSize); + assert(hashStoreLocation != nullptr); + + const uint8_t* hashBuffer = (const uint8_t*)hashStoreLocation; + std::string hashString; + + for (int i = 0; i < hashStoreSize; ++i) { + uint8_t byte = hashBuffer[i]; + uint8_t nibbleL = byte & 0x0F; + uint8_t nibbleH = byte >> 4; + if ( nibbleH < 10 ) { + hashString += '0' + nibbleH; + } else { + hashString += 'a' + (nibbleH-10); + } + if ( nibbleL < 10 ) { + hashString += '0' + nibbleL; + } else { + hashString += 'a' + (nibbleL-10); + } + } + + stop = true; + + topNode.map["fips"] = makeNode(hashString); + }); + + printJSON(topNode, 0, std::cout); + } + + return 0; +} + +static int validateFile(const ValidateOptions& options) { + // Verify any required options + if (gOpts.archs.size() != 1) + exit_usage("-arch"); + if (gOpts.platform == nullptr) + exit_usage("-platform"); + if (options.filePath == nullptr) + exit_usage(); + + const GradedArchs& archs = GradedArchs::forName(gOpts.archs[0]); + Platform platform = Platform::unknown; + + // HACK: Pass a real option for building a kernel app cache + if (strcmp(gOpts.platform, "kernel")) { + fprintf(stderr, "Could not create app cache because: unknown platform '%s'\n", gOpts.platform); + return 1; + } + + __block Diagnostics diag; + + std::string file = options.filePath; + { + FileSystemPhysical fileSystem; + char fileRealPath[MAXPATHLEN]; + LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, file.c_str(), archs, platform, fileRealPath); + if (diag.hasError()) { + fprintf(stderr, "Could not load file '%s' because: %s\n", file.c_str(), diag.errorMessage().c_str()); + diag.clearError(); + return 1; + } + + MachOAnalyzer* ma = (MachOAnalyzer*)loadedFileInfo.fileContent; + if (ma == nullptr) { + fprintf(stderr, "Could not load file: %s\n", file.c_str()); + return 1; + } + + auto errorHandler = ^(const char* msg) { + diag.warning("File '%s' cannot be placed in kernel collection because: %s", file.c_str(), msg); + }; + if (ma->canBePlacedInKernelCollection(file.c_str(), errorHandler)) { + return 0; + } else { + fileSystem.unloadFile(loadedFileInfo); + } + } + + { + // Since we found no files, print warnings for the ones we tried + if (diag.warnings().empty()) { + fprintf(stderr, "File '%s' was not valid for app-cache\n", file.c_str()); + } else { + for (const std::string& msg : diag.warnings()) { + fprintf(stderr, " %s\n", msg.c_str()); + } + } + return 1; + } + + return 0; +} + +static void forEachBundle(const char* bundlesDirectoryPath, + void (^callback)(CFBundleRef bundleRef, const char* bundleName)) { + CFStringRef sourcePath = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, bundlesDirectoryPath, + kCFStringEncodingASCII, kCFAllocatorNull); + CFURLRef sourceURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, sourcePath, + kCFURLPOSIXPathStyle, true); + CFArrayRef bundles = CFBundleCreateBundlesFromDirectory(kCFAllocatorDefault, sourceURL, nullptr); + + for (CFIndex i = 0, e = CFArrayGetCount(bundles); i != e; ++i) { + CFBundleRef bundleRef = (CFBundleRef)CFArrayGetValueAtIndex(bundles, i); + CFStringRef bundleID = CFBundleGetIdentifier(bundleRef); + if (!bundleID) + continue; + const char* bundleName = CFStringGetCStringPtr(bundleID, kCFStringEncodingASCII); + callback(bundleRef, bundleName); + } + + CFRelease(sourcePath); + CFRelease(sourceURL); + CFRelease(bundles); +} + +static int listBundles(const ListBundlesOptions& options) { + // Verify any required options + if (options.directoryPath == nullptr) + exit_usage(); + + forEachBundle(options.directoryPath, ^(CFBundleRef bundleRef, const char* bundleName){ + printf("Bundle: %s\n", bundleName); + }); + + return 0; +} + +static CFDataRef +createKernelCollectionForArch(const CreateKernelCollectionOptions& options, const char* arch, + Diagnostics& diag) { + const GradedArchs& archs = GradedArchs::forName(arch); + Platform platform = Platform::unknown; + + + KernelCollectionBuilder* kcb = nullptr; + { + CFStringRef archStringRef = CFStringCreateWithCString(kCFAllocatorDefault, arch, kCFStringEncodingASCII); + BuildOptions_v1 buildOptions = { 1, options.collectionKind, options.stripMode, archStringRef, options.verbose }; + kcb = createKernelCollectionBuilder(&buildOptions); + CFRelease(archStringRef); + } + + FileSystemPhysical fileSystem; + LoadedFileInfo kernelCollectionLoadedFileInfo; + + auto loadKernelCollection = ^(const char* kernelCollectionPath, CollectionKind collectionKind) { + if (!fileSystem.fileExists(kernelCollectionPath)) { + fprintf(stderr, "kernel collection path does not exist: %s\n", options.kernelPath); + return false; + } + LoadedFileInfo info; + char realerPath[MAXPATHLEN]; + bool loadedFile = fileSystem.loadFile(kernelCollectionPath, info, realerPath, ^(const char *format, ...) { + va_list list; + va_start(list, format); + diag.error(format, list); + va_end(list); + }); + if ( !loadedFile ) + return false; + CFStringRef pathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, kernelCollectionPath, kCFStringEncodingASCII); + CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)info.fileContent, info.fileContentLen, kCFAllocatorNull); + if ( !addCollectionFile(kcb, pathStringRef, dataRef, collectionKind) ) { + diag.error("Could not load kernel collection file"); + return false; + } + CFRelease(dataRef); + CFRelease(pathStringRef); + return true; + }; + + switch (options.collectionKind) { + case unknownKC: + fprintf(stderr, "Invalid kernel collection kind\n"); + exit(1); + case baseKC: { + if (!fileSystem.fileExists(options.kernelPath)) { + fprintf(stderr, "Kernel path does not exist: %s\n", options.kernelPath); + return {}; + } + LoadedFileInfo info; + char realerPath[MAXPATHLEN]; + bool loadedFile = fileSystem.loadFile(options.kernelPath, info, realerPath, ^(const char *format, ...) { + va_list list; + va_start(list, format); + diag.error(format, list); + va_end(list); + }); + if ( !loadedFile ) + return {}; + CFStringRef pathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, options.kernelPath, kCFStringEncodingASCII); + CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)info.fileContent, info.fileContentLen, kCFAllocatorNull); + if ( !addKernelFile(kcb, pathStringRef, dataRef) ) { + uint64_t errorCount = 0; + const char* const* errors = getErrors(kcb, &errorCount); + for (uint64_t i = 0; i != errorCount; ++i) + diag.error("Could not load kernel file because: '%s'", errors[i]); + return {}; + } + CFRelease(dataRef); + CFRelease(pathStringRef); + break; + } + case pageableKC: + if ( !loadKernelCollection(options.kernelCollectionPath, baseKC) ) + return {}; + break; + case auxKC: + if ( !loadKernelCollection(options.kernelCollectionPath, baseKC) ) + return {}; + // Pageable is optional + if ( options.pageableCollectionPath != nullptr ) { + if ( !loadKernelCollection(options.pageableCollectionPath, pageableKC) ) + return {}; + } + break; + } + + if ( !options.bundleIDs.empty() ) { + struct BundleData { + std::string executablePath; + std::string bundlePath; + std::vector dependencies; + CFDictionaryRef infoPlist = nullptr; + }; + __block std::map foundBundles; + + // Look for bundles in the extensions directory and any PlugIns directories its kext's contain + __block std::list> kextDirectoriesToProcess; + kextDirectoriesToProcess.push_back({ options.extensionsPath, true }); + while ( !kextDirectoriesToProcess.empty() ) { + std::string kextDir = kextDirectoriesToProcess.front().first; + bool lookForPlugins = kextDirectoriesToProcess.front().second; + kextDirectoriesToProcess.pop_front(); + + __block bool foundError = false; + forEachBundle(kextDir.c_str(), ^(CFBundleRef bundleRef, const char* bundleName) { + + if (foundError) + return; + + // If the directory contains a PlugIns directory, then add it to the list to seach for kexts + if (lookForPlugins) { + CFURLRef pluginsRelativeURL = CFBundleCopyBuiltInPlugInsURL(bundleRef); + if ( pluginsRelativeURL != nullptr ) { + CFURLRef pluginsAbsoluteURL = CFURLCopyAbsoluteURL(pluginsRelativeURL); + CFStringRef pluginString = CFURLCopyFileSystemPath(pluginsAbsoluteURL, kCFURLPOSIXPathStyle); + const char* pluginPath = CFStringGetCStringPtr(pluginString, kCFStringEncodingASCII); + kextDirectoriesToProcess.push_back({ pluginPath, false }); + + CFRelease(pluginString); + CFRelease(pluginsAbsoluteURL); + CFRelease(pluginsRelativeURL); + } + } + +#if 0 + // For now always load bundles as we don't require every bundle to be listed on the command line + // but can instead bring them in on demand. + + // Once we've looked for plugins, if we don't want this bundle then we can skip validating it. + if ( foundBundles.count(bundleName) == 0 ) + return; +#endif + + BundleData bundleData; + bundleData.infoPlist = CFBundleGetInfoDictionary(bundleRef); + + CFURLRef bundleExecutableRelativeURL = CFBundleCopyExecutableURL(bundleRef); + + // Its ok to be missing an executable. We'll just skip this bundle + if ( bundleExecutableRelativeURL == nullptr ) { + // FIXME: Its possibly not ok to be missing the executable if its actually listed + // as a CFBundleExecutable path in the plist + foundBundles[bundleName] = bundleData; + return; + } + + CFURLRef bundleExecutableAbsoluteURL = CFURLCopyAbsoluteURL(bundleExecutableRelativeURL); + CFStringRef bundleExecutableString = CFURLCopyFileSystemPath(bundleExecutableAbsoluteURL, kCFURLPOSIXPathStyle); + const char* bundleExecutablePath = CFStringGetCStringPtr(bundleExecutableString, kCFStringEncodingASCII); + + // Check for an arch specific dependency list first + std::string archBundleLibraries = std::string("OSBundleLibraries") + "_" + arch; + CFStringRef archBundleLibrariesStringRef = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, archBundleLibraries.c_str(), + kCFStringEncodingASCII, kCFAllocatorNull); + CFTypeRef depsRef = CFBundleGetValueForInfoDictionaryKey(bundleRef, archBundleLibrariesStringRef); + if ( depsRef == nullptr ) { + // No arch specific deps, so try the defaults + depsRef = CFBundleGetValueForInfoDictionaryKey(bundleRef, CFSTR("OSBundleLibraries")); + } + if (depsRef != nullptr) { + if (CFGetTypeID(depsRef) != CFDictionaryGetTypeID()) { + fprintf(stderr, "Bad bundle '%s' (\"OSBundleLibraries\" is not a dictionary)\n", bundleName); + foundError = true; + return; + } + + CFDictionaryRef dictRef = (CFDictionaryRef)depsRef; + CFDictionaryApplyFunction(dictRef, [](const void *key, const void *value, void *context) { + BundleData* bundleData = (BundleData*)context; + CFStringRef keyRef = (CFStringRef)key; + //CFStringRef valueRef = (CFStringRef)value; + bundleData->dependencies.push_back(CFStringGetCStringPtr(keyRef, kCFStringEncodingASCII)); + }, &bundleData); + } + + // Make sure no-one tries to link the kernel directly. They must do so via symbol sets + if ( !bundleData.dependencies.empty() ) { + for (const std::string& dep : bundleData.dependencies) { + if (dep == "com.apple.kernel") { + fprintf(stderr, "Rejecting bundle '%s' as it is trying to link directly to the kernel\n", + bundleName); + foundError = true; + return; + } + } + } + + bundleData.executablePath = bundleExecutablePath; + + CFURLRef bundleURLRef = CFBundleCopyBundleURL(bundleRef); + CFStringRef bundleURLString = CFURLCopyFileSystemPath(bundleURLRef, kCFURLPOSIXPathStyle); + const char* bundleURLPath = CFStringGetCStringPtr(bundleURLString, kCFStringEncodingASCII); + if (strncmp(bundleURLPath, options.extensionsPath, strlen(options.extensionsPath)) != 0) { + fprintf(stderr, "Bundle path '%s' is not within extensions directory '%s'\n", + bundleURLPath, options.extensionsPath); + } + // Don't remove the whole extensions prefix, but instead the volume root, if we have one + bundleData.bundlePath = bundleURLPath + strlen(options.volumeRoot); + foundBundles[bundleName] = bundleData; + + CFRelease(bundleExecutableString); + CFRelease(bundleExecutableAbsoluteURL); + CFRelease(bundleExecutableRelativeURL); + }); + + if (foundError) + return {}; + } + + __block std::set existingBundles; + auto addSymbolSetsBundleIDs = ^(const dyld3::MachOAnalyzer* kernelMA) { + assert(kernelMA != nullptr); + + __block std::list nonASCIIStrings; + auto getString = ^(Diagnostics& diags, CFStringRef symbolNameRef) { + const char* symbolName = CFStringGetCStringPtr(symbolNameRef, kCFStringEncodingUTF8); + if ( symbolName != nullptr ) + return symbolName; + + CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef), kCFStringEncodingUTF8); + char buffer[len + 1]; + if ( !CFStringGetCString(symbolNameRef, buffer, len, kCFStringEncodingUTF8) ) { + diags.error("Could not convert string to ASCII"); + return (const char*)nullptr; + } + buffer[len] = '\0'; + nonASCIIStrings.push_back(buffer); + return nonASCIIStrings.back().c_str(); + }; + + uint64_t symbolSetsSize = 0; + const void* symbolSetsContent = kernelMA->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize); + if ( symbolSetsContent != nullptr ) { + + // A helper to automatically call CFRelease when we go out of scope + struct AutoReleaseTypeRef { + AutoReleaseTypeRef() = default; + ~AutoReleaseTypeRef() { + if ( ref != nullptr ) { + CFRelease(ref); + } + } + void setRef(CFTypeRef typeRef) { + assert(ref == nullptr); + ref = typeRef; + } + + CFTypeRef ref = nullptr; + }; + + AutoReleaseTypeRef dataRefReleaser; + AutoReleaseTypeRef plistRefReleaser; + + CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)symbolSetsContent, symbolSetsSize, kCFAllocatorNull); + if ( dataRef == nullptr ) { + diag.error("Could not create data ref for symbol sets"); + return false; + } + dataRefReleaser.setRef(dataRef); + + CFErrorRef errorRef = nullptr; + CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, dataRef, kCFPropertyListImmutable, nullptr, &errorRef); + if (errorRef != nullptr) { + CFStringRef errorString = CFErrorCopyDescription(errorRef); + diag.error("Could not load plist because :%s", + CFStringGetCStringPtr(errorString, kCFStringEncodingASCII)); + CFRelease(errorRef); + return false; + } + if ( plistRef == nullptr ) { + diag.error("Could not create plist ref for symbol sets"); + return false; + } + plistRefReleaser.setRef(plistRef); + + if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) { + diag.error("Symbol set plist should be a dictionary"); + return false; + } + CFDictionaryRef symbolSetsDictRef = (CFDictionaryRef)plistRef; + CFArrayRef symbolSetArrayRef = (CFArrayRef)CFDictionaryGetValue(symbolSetsDictRef, CFSTR("SymbolsSets")); + if ( symbolSetArrayRef != nullptr ) { + if ( CFGetTypeID(symbolSetArrayRef) != CFArrayGetTypeID() ) { + diag.error("SymbolsSets value should be an array"); + return false; + } + for (CFIndex symbolSetIndex = 0; symbolSetIndex != CFArrayGetCount(symbolSetArrayRef); ++symbolSetIndex) { + CFDictionaryRef symbolSetDictRef = (CFDictionaryRef)CFArrayGetValueAtIndex(symbolSetArrayRef, symbolSetIndex); + if ( CFGetTypeID(symbolSetDictRef) != CFDictionaryGetTypeID() ) { + diag.error("Symbol set element should be a dictionary"); + return false; + } + + // CFBundleIdentifier + CFStringRef bundleIDRef = (CFStringRef)CFDictionaryGetValue(symbolSetDictRef, CFSTR("CFBundleIdentifier")); + if ( (bundleIDRef == nullptr) || (CFGetTypeID(bundleIDRef) != CFStringGetTypeID()) ) { + diag.error("Symbol set bundle ID should be a string"); + return false; + } + + const char* dylibID = getString(diag, bundleIDRef); + if ( dylibID == nullptr ) + return false; + existingBundles.insert(dylibID); + } + } + } + return true; + }; + + auto addExistingBundleIDs = ^(const char* path, bool isBaseKC) { + char fileRealPath[MAXPATHLEN]; + auto kernelCollectionLoadedFileInfo = MachOAnalyzer::load(diag, fileSystem, path, archs, platform, fileRealPath); + if ( diag.hasError() ) { + fprintf(stderr, "Could not load file '%s' because: %s\n", path, diag.errorMessage().c_str()); + return false; + } + const MachOAppCache* appCacheMA = (const MachOAppCache*)kernelCollectionLoadedFileInfo.fileContent; + if (appCacheMA == nullptr) { + fprintf(stderr, "Could not load file: %s\n", path); + return false; + } + if ( !appCacheMA->isFileSet() ) { + fprintf(stderr, "kernel collection is not a cache file: %s\n", path); + return false; + } + __block const dyld3::MachOAnalyzer* kernelMA = nullptr; + appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) { + if ( ma->isStaticExecutable() ) { + kernelMA = ma; + } + existingBundles.insert(name); + }); + + if ( isBaseKC ) { + if ( !addSymbolSetsBundleIDs(kernelMA) ) + return false; + } + fileSystem.unloadFile(kernelCollectionLoadedFileInfo); + return true; + }; + if ( options.collectionKind == baseKC ) { + char fileRealPath[MAXPATHLEN]; + auto kernelLoadedFileInfo = MachOAnalyzer::load(diag, fileSystem, options.kernelPath, archs, platform, fileRealPath); + if ( diag.hasError() ) { + fprintf(stderr, "Could not load file '%s' because: %s\n", options.kernelPath, diag.errorMessage().c_str()); + return {}; + } + const MachOAppCache* kernelMA = (const MachOAppCache*)kernelLoadedFileInfo.fileContent; + if (kernelMA == nullptr) { + fprintf(stderr, "Could not load file: %s\n", options.kernelPath); + return {}; + } + if ( !kernelMA->isStaticExecutable() ) { + fprintf(stderr, "kernel is not a static executable: %s\n", options.kernelPath); + return {}; + } + + if ( !addSymbolSetsBundleIDs(kernelMA) ) + return {}; + fileSystem.unloadFile(kernelLoadedFileInfo); + } + if ( (options.collectionKind == auxKC) || (options.collectionKind == pageableKC) ) { + // Work out which bundle-ids are already in the base KC + if ( !addExistingBundleIDs(options.kernelCollectionPath, true) ) + return {}; + } + if ( options.pageableCollectionPath != nullptr ) { + // Work out which bundle-ids are already in the pageable KC + if ( !addExistingBundleIDs(options.pageableCollectionPath, false) ) + return {}; + } + + std::set processedBundleIDs; + std::list bundleIDsToLoad; + bundleIDsToLoad.insert(bundleIDsToLoad.end(), options.bundleIDs.begin(), options.bundleIDs.end()); + while (!bundleIDsToLoad.empty()) { + std::string bundleID = bundleIDsToLoad.front(); + bundleIDsToLoad.pop_front(); + + std::string stripModeString; + if ( const char* colonPos = strchr(bundleID.c_str(), ':') ) { + stripModeString = colonPos + 1; + bundleID.erase(colonPos - bundleID.data()); + } + + // If we've seen this one already then skip it + if (!processedBundleIDs.insert(bundleID).second) + continue; + + // Find the bundle for this ID + auto it = foundBundles.find(bundleID); + if (it == foundBundles.end()) { + fprintf(stderr, "[WARNING]: Could not find bundle with ID '%s'\n", bundleID.c_str()); + continue; + } + + BundleData& bundleData = it->second; + + LoadedFileInfo info; + + // Codeless kexts don't have an executable path, but we still want to put their + // plist in the prelink info + bool isCodeless = bundleData.executablePath.empty(); + if ( !isCodeless ) { + char realerPath[MAXPATHLEN]; + bool loadedFile = fileSystem.loadFile(bundleData.executablePath.c_str(), info, realerPath, + ^(const char *format, ...) { + va_list list; + va_start(list, format); + diag.error(format, list); + va_end(list); + }); + if ( !loadedFile ) + return {}; + } + + std::vector deps; + for (const std::string& dependency : bundleData.dependencies) + deps.push_back(dependency.c_str()); + + CFStringRef kextPathStringRef = nullptr; + CFDataRef kextDataRef = nullptr; + if ( !isCodeless) { + kextPathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, bundleData.executablePath.c_str(), kCFStringEncodingASCII); + kextDataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)info.fileContent, info.fileContentLen, kCFAllocatorNull); + } + + CFMutableArrayRef kextDepsArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, bundleData.dependencies.size(), &kCFTypeArrayCallBacks); + for (const std::string& dependency : bundleData.dependencies) { + CFStringRef depStringRef = CFStringCreateWithCString(kCFAllocatorDefault, dependency.c_str(), kCFStringEncodingASCII); + CFArrayAppendValue(kextDepsArrayRef, depStringRef); + CFRelease(depStringRef); + } + CFStringRef kextBundleIDStringRef = CFStringCreateWithCString(kCFAllocatorDefault, bundleID.c_str(), kCFStringEncodingASCII); + CFStringRef kextBundlePathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, bundleData.bundlePath.c_str(), kCFStringEncodingASCII); + + BinaryStripMode stripMode = binaryStripNone; + if ( !stripModeString.empty() ) { + if ( stripModeString == "locals" ) { + stripMode = binaryStripLocals; + } else if ( stripModeString == "exports" ) { + stripMode = binaryStripExports; + } else if ( stripModeString == "all" ) { + stripMode = binaryStripAll; + } else { + diag.error("Unknown strip mode: '%s'", stripModeString.c_str()); + return {}; + } + } + + KextFileData_v1 fileData = { 1, kextPathStringRef, kextDataRef, + kextDepsArrayRef, kextBundleIDStringRef, kextBundlePathStringRef, + bundleData.infoPlist, stripMode }; + + if ( !addKextDataFile(kcb, &fileData) ) { + uint64_t errorCount = 0; + const char* const* errors = getErrors(kcb, &errorCount); + for (uint64_t i = 0; i != errorCount; ++i) + diag.error("Could not load kext file because: '%s'", errors[i]); + return {}; + } + + // Walk the dependencies and add any new ones to the list + for (const std::string& dependency : bundleData.dependencies) { + if ( existingBundles.find(dependency) == existingBundles.end() ) + bundleIDsToLoad.push_back(dependency.c_str()); + } + } + + // Filter dependencies to kext's with binaries +#if 0 + const std::map* foundBundlesPtr = &foundBundles; + for (AppCacheBuilder::InputDylib& file : loadedFiles) { + file.dylibDeps.erase(std::remove_if(file.dylibDeps.begin(), file.dylibDeps.end(), + [&](const std::string& depName) { + auto it = foundBundlesPtr->find(depName); + assert(it != foundBundlesPtr->end()); + return it->second.executablePath.empty(); + }),file.dylibDeps.end()); + } +#endif + } + +#if 0 + for (AppCacheBuilder::InputDylib& file : loadedFiles) { + char fileRealPath[MAXPATHLEN]; + const char* path = file.dylib.loadedFileInfo.path; + LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, path, archs, platform, fileRealPath); + if ( diag.hasError() ) { + fprintf(stderr, "Could not load file '%s' because: %s\n", path, diag.errorMessage().c_str()); + return {}; + } + + MachOAnalyzer* ma = (MachOAnalyzer*)loadedFileInfo.fileContent; + if (ma == nullptr) { + fprintf(stderr, "Could not load file: %s\n", path); + return {}; + } + + auto errorHandler = ^(const char* msg) { + diag.error("Binary located at '%s' cannot be placed in kernel collection because: %s", path, msg); + }; + if (ma->canBePlacedInKernelCollection(path, errorHandler)) { + DyldSharedCache::MappedMachO mappedFile(path, ma, loadedFileInfo.sliceLen, false, false, + loadedFileInfo.sliceOffset, loadedFileInfo.mtime, + loadedFileInfo.inode); + CacheBuilder::LoadedMachO loadedMachO = { mappedFile, loadedFileInfo, nullptr }; + file.dylib = loadedMachO; + } else { + fileSystem.unloadFile(loadedFileInfo); + } + if ( diag.hasError() ) { + fprintf(stderr, "%s\n", diag.errorMessage().c_str()); + return {}; + } + } +#endif + +#if 0 + if (loadedFiles.empty()) { + fprintf(stderr, "Could not find any valid files to create kernel collection\n"); + + // Since we found no files, print warnings for the ones we tried + if (!diag.warnings().empty()) { + fprintf(stderr, "Failed to use the following files:\n"); + for (const std::string& msg : diag.warnings()) { + fprintf(stderr, " %s\n", msg.c_str()); + } + } + return {}; + } + + if (options.verbose) { + for (const AppCacheBuilder::InputDylib& loadedFile : loadedFiles) + fprintf(stderr, "Building cache with file: %s\n", loadedFile.dylib.loadedFileInfo.path); + } +#endif + + for (const SectionData& sectData : options.sections) { + CFStringRef segmentName = CFStringCreateWithCString(kCFAllocatorDefault, sectData.segmentName, kCFStringEncodingASCII); + CFStringRef sectionName = nullptr; + if ( sectData.sectionName != nullptr ) + sectionName = CFStringCreateWithCString(kCFAllocatorDefault, sectData.sectionName, kCFStringEncodingASCII); + + CFDataRef sectionData = nullptr; + { + struct stat stat_buf; + int fd = ::open(sectData.payloadFilePath, O_RDONLY, 0); + if (fd == -1) { + diag.error("can't open file '%s', errno=%d\n", sectData.payloadFilePath, errno); + return {}; + } + + if (fstat(fd, &stat_buf) == -1) { + diag.error("can't stat open file '%s', errno=%d\n", sectData.payloadFilePath, errno); + ::close(fd); + return {}; + } + + const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buffer == MAP_FAILED) { + diag.error("mmap() for file at %s failed, errno=%d\n", sectData.payloadFilePath, errno); + ::close(fd); + return {}; + } + ::close(fd); + + sectionData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)buffer, stat_buf.st_size, kCFAllocatorNull); + } + + if ( !addSegmentData(kcb, segmentName, sectionName, sectionData) ) { + uint64_t errorCount = 0; + const char* const* errors = getErrors(kcb, &errorCount); + for (uint64_t i = 0; i != errorCount; ++i) + diag.error("Could not load section data file because: '%s'", errors[i]); + return {}; + } + } + + if ( options.prelinkInfoExtraData != nullptr ) { + struct stat stat_buf; + int fd = ::open(options.prelinkInfoExtraData, O_RDONLY, 0); + if (fd == -1) { + diag.error("can't open file '%s', errno=%d\n", options.prelinkInfoExtraData, errno); + return {}; + } + + if (fstat(fd, &stat_buf) == -1) { + diag.error("can't stat open file '%s', errno=%d\n", options.prelinkInfoExtraData, errno); + ::close(fd); + return {}; + } + + const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buffer == MAP_FAILED) { + diag.error("mmap() for file at %s failed, errno=%d\n", options.prelinkInfoExtraData, errno); + ::close(fd); + return {}; + } + ::close(fd); + + CFDataRef prelinkInfoData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)buffer, stat_buf.st_size, kCFAllocatorNull); + + CFErrorRef errorRef = nullptr; + CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, prelinkInfoData, kCFPropertyListImmutable, nullptr, &errorRef); + if (errorRef != nullptr) { + CFStringRef errorString = CFErrorCopyDescription(errorRef); + diag.error("Could not load prelink info plist because :%s", + CFStringGetCStringPtr(errorString, kCFStringEncodingASCII)); + CFRelease(errorRef); + return {}; + } + if ( plistRef == nullptr ) { + diag.error("Could not create plist ref for prelink info"); + return {}; + } + if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) { + diag.error("Prelink info plist should be a dictionary"); + return {}; + } + + if ( !addPrelinkInfo(kcb, (CFDictionaryRef)plistRef) ) { + uint64_t errorCount = 0; + const char* const* errors = getErrors(kcb, &errorCount); + for (uint64_t i = 0; i != errorCount; ++i) + diag.error("Could not prelink data file because: '%s'", errors[i]); + return {}; + } + } + + bool success = runKernelCollectionBuilder(kcb); + uint64_t errorCount = 0; + const char* const* errors = getErrors(kcb, &errorCount); + if ( errors != nullptr ) { + if ( !options.printJSONErrors ) { + for (uint64_t i = 0; i != errorCount; ++i) { + fprintf(stderr, "Could not build kernel collection because '%s'\n", errors[i]); + } + } + CFDictionaryRef errorDictRef = getKextErrors(kcb); + if ( errorDictRef != nullptr ) { + Node rootNode; + + CFDictionaryApplyFunction(errorDictRef, [](const void *key, const void *value, void *context) { + Node* rootNode = (Node*)context; + CFStringRef keyRef = (CFStringRef)key; + CFArrayRef valueRef = (CFArrayRef)value; + + Node bundleNode; + bundleNode.map["id"] = Node(CFStringGetCStringPtr(keyRef, kCFStringEncodingASCII)); + + Node errorsNode; + CFArrayApplyFunction(valueRef, CFRangeMake(0, CFArrayGetCount(valueRef)), [](const void *value, void *context) { + Node* errorsNode = (Node*)context; + CFStringRef valueRef = (CFStringRef)value; + + errorsNode->array.push_back(Node(CFStringGetCStringPtr(valueRef, kCFStringEncodingASCII))); + }, &errorsNode); + + bundleNode.map["errors"] = errorsNode; + + rootNode->array.push_back(bundleNode); + }, &rootNode); + + // sort the nodes so that the output is reproducible + std::sort(rootNode.array.begin(), rootNode.array.end(), + [](const Node& a, const Node&b) { + return a.map.find("id")->second.value < b.map.find("id")->second.value; + }); + + printJSON(rootNode); + } else { + Node rootNode; + for (uint64_t i = 0; i != errorCount; ++i) { + rootNode.array.push_back(Node(errors[i])); + } + printJSON(rootNode); + } + return {}; + } + + if ( !success ) + return {}; + + uint64_t fileResultCount = 0; + const auto* fileResults = getCollectionFileResults(kcb, &fileResultCount); + if ( fileResults == nullptr ) { + fprintf(stderr, "Could not get file results\n"); + return {}; + } + if ( fileResultCount != 1 ) { + fprintf(stderr, "Unsupported file result count: %lld\n", fileResultCount); + return {}; + } + + CFDataRef dataRef = fileResults[0]->data; + CFRetain(dataRef); + + destroyKernelCollectionBuilder(kcb); + + return dataRef; +} + +static int createKernelCollection(const CreateKernelCollectionOptions& options) { + // Verify any required options + if (gOpts.archs.empty()) { + exit_usage("-arch"); + } else { + std::set archs(gOpts.archs.begin(), gOpts.archs.end()); + if (archs.size() != gOpts.archs.size()) { + fprintf(stderr, "Duplicate -arch specified\n"); + exit(1); + } + } + if (options.outputCachePath == nullptr) + exit_usage(); + + switch (options.stripMode) { + case unknownStripMode: + case stripNone: + break; + case stripAll: + case stripAllKexts: + if ( options.collectionKind != baseKC ) { + fprintf(stderr, "Cannot use -strip-all-kexts with auxKC. Use strip-all instead\n"); + exit(1); + } + break; + } + + switch (options.collectionKind) { + case unknownKC: + fprintf(stderr, "Invalid kernel collection kind\n"); + exit(1); + case baseKC: + if (options.kernelPath == nullptr) + exit_usage("-kernel"); + break; + case pageableKC: + case auxKC: + if (options.kernelCollectionPath == nullptr) + exit_usage("-kernel-collection"); + break; + } + + if ( !options.bundleIDs.empty() ) { + if (options.extensionsPath == nullptr) + exit_usage("-extensions"); + } + + // Volume root should be a prefix of extensions path + if ( options.extensionsPath != nullptr ) { + if ( strncmp(options.extensionsPath, options.volumeRoot, strlen(options.volumeRoot)) != 0 ) { + fprintf(stderr, "Volume root '%s' is not a prefix of extensions path '%s'\n", + options.volumeRoot, options.extensionsPath); + } + } + + std::vector buffers; + for (const char* arch : gOpts.archs) { + Diagnostics diag; + CFDataRef bufferRef = createKernelCollectionForArch(options, arch, diag); + if ( diag.hasError() ) { + fprintf(stderr, "%s\n", diag.errorMessage().c_str()); + return 1; + } + if ( bufferRef == nullptr ) { + // If we want errors then return 0 + if ( options.printJSONErrors ) + return 0; + return 1; + } + buffers.push_back(bufferRef); + } + + if (buffers.size() == 1) { + // Single arch. Just write the file directly + CFDataRef bufferRef = buffers.front(); + if ( !safeSave(CFDataGetBytePtr(bufferRef), CFDataGetLength(bufferRef), options.outputCachePath) ) { + fprintf(stderr, "Could not write app cache\n"); + return 1; + } + CFRelease(bufferRef); + } else { + // Multiple buffers. Create a FAT file + std::vector fatBuffer; + + // Add the FAT header to the start of the buffer + fatBuffer.resize(0x4000, 0); + fat_header* header = (fat_header*)&fatBuffer.front(); + header->magic = OSSwapHostToBigInt32(FAT_MAGIC); + header->nfat_arch = OSSwapHostToBigInt32((uint32_t)buffers.size()); + + for (uint32_t i = 0; i != buffers.size(); ++i) { + CFDataRef bufferRef = buffers[i]; + mach_header* mh = (mach_header*)CFDataGetBytePtr(bufferRef); + + uint32_t offsetInBuffer = (uint32_t)fatBuffer.size(); + + fat_arch* archBuffer = (fat_arch*)(&fatBuffer.front() + sizeof(fat_header)); + archBuffer[i].cputype = OSSwapHostToBigInt32(mh->cputype); + archBuffer[i].cpusubtype = OSSwapHostToBigInt32(mh->cpusubtype); + archBuffer[i].offset = OSSwapHostToBigInt32(offsetInBuffer); + archBuffer[i].size = OSSwapHostToBigInt32((uint32_t)CFDataGetLength(bufferRef)); + archBuffer[i].align = OSSwapHostToBigInt32(14); + + auto align = [](uint64_t addr, uint8_t p2) { + uint64_t mask = (1 << p2); + return (addr + mask - 1) & (-mask); + }; + + uint32_t alignedSize = (uint32_t)align((uint32_t)CFDataGetLength(bufferRef), 14); + fatBuffer.resize(fatBuffer.size() + alignedSize, 0); + memcpy(&fatBuffer.front() + offsetInBuffer, CFDataGetBytePtr(bufferRef), CFDataGetLength(bufferRef)); + } + + if ( !safeSave(&fatBuffer.front(), fatBuffer.size(), options.outputCachePath) ) { + fprintf(stderr, "Could not write app cache\n"); + return 1; + } + } + + return 0; +} + +int main(int argc, const char* argv[]) { + OptionsVariants options; + if (!parseArgs(argc, argv, options)) + return 1; + + if (std::holds_alternative(options)) { + return dumpAppCache(std::get(options)); + } + + if (std::holds_alternative(options)) { + return validateFile(std::get(options)); + } + + if (std::holds_alternative(options)) { + return listBundles(std::get(options)); + } + + if (std::holds_alternative(options)) { + return createKernelCollection(std::get(options)); + } + + assert(std::holds_alternative(options)); + + exit_usage(); +}