if ( li.overrideImageNum != 0 ) {
const Image* cacheImage = _dyldImageArray->imageForNum(li.overrideImageNum);
STACK_ALLOC_ARRAY(Closure::PatchEntry, patches, cacheImage->patchableExportCount());
+ MachOLoaded::DependentToMachOLoaded reexportFinder = ^(const MachOLoaded* mh, uint32_t depIndex) {
+ return (const MachOLoaded*)findDependent(mh, depIndex);
+ };
//fprintf(stderr, "'%s' overrides '%s'\n", li.loadedFileInfo.path, cacheImage->path());
cacheImage->forEachPatchableExport(^(uint32_t cacheOffsetOfImpl, const char* symbolName) {
dyld3::MachOAnalyzer::FoundSymbol foundInfo;
Closure::PatchEntry patch;
patch.overriddenDylibInCache = li.overrideImageNum;
patch.exportCacheOffset = cacheOffsetOfImpl;
- if ( li.loadAddress()->findExportedSymbol(patchDiag, symbolName, foundInfo, nullptr) ) {
+ if ( li.loadAddress()->findExportedSymbol(patchDiag, symbolName, foundInfo, reexportFinder) ) {
+ const MachOAnalyzer* impDylib = (const MachOAnalyzer*)foundInfo.foundInDylib;
patch.replacement.image.kind = Image::ResolvedSymbolTarget::kindImage;
- patch.replacement.image.imageNum = li.imageNum;
+ patch.replacement.image.imageNum = findLoadedImage(impDylib).imageNum;
patch.replacement.image.offset = foundInfo.value;
}
else {
if ( (li.overrideImageNum != 0) && (li.imageNum >= _startImageNum) ) {
const Image* cacheImage = _dyldImageArray->imageForNum(li.overrideImageNum);
STACK_ALLOC_ARRAY(Closure::PatchEntry, patches, cacheImage->patchableExportCount());
+ MachOLoaded::DependentToMachOLoaded reexportFinder = ^(const MachOLoaded* mh, uint32_t depIndex) {
+ return (const MachOLoaded*)findDependent(mh, depIndex);
+ };
//fprintf(stderr, "'%s' overrides '%s'\n", li.loadedFileInfo.path, cacheImage->path());
cacheImage->forEachPatchableExport(^(uint32_t cacheOffsetOfImpl, const char* symbolName) {
dyld3::MachOAnalyzer::FoundSymbol foundInfo;
Closure::PatchEntry patch;
patch.overriddenDylibInCache = li.overrideImageNum;
patch.exportCacheOffset = cacheOffsetOfImpl;
- if ( li.loadAddress()->findExportedSymbol(patchDiag, symbolName, foundInfo, nullptr) ) {
+ if ( li.loadAddress()->findExportedSymbol(patchDiag, symbolName, foundInfo, reexportFinder) ) {
+ const MachOAnalyzer* impDylib = (const MachOAnalyzer*)foundInfo.foundInDylib;
patch.replacement.image.kind = Image::ResolvedSymbolTarget::kindImage;
- patch.replacement.image.imageNum = li.imageNum;
+ patch.replacement.image.imageNum = findLoadedImage(impDylib).imageNum;
patch.replacement.image.offset = foundInfo.value;
}
else {
#include "ClosureFileSystem.h"
+#include <sys/stat.h>
+
namespace dyld3 {
namespace closure {
__block Node root;
root.map["images"] = buildImageArrayNode(closure->images(), imagesArrays, printFixups, printDependentsDetails);
+ closure->forEachPatchEntry(^(const Closure::PatchEntry& patchEntry) {
+ Node patchNode;
+ patchNode.map["func-dyld-cache-offset"].value = hex8(patchEntry.exportCacheOffset);
+ patchNode.map["func-image-num"].value = hex8(patchEntry.overriddenDylibInCache);
+ patchNode.map["replacement"].value = printTarget(imagesArrays, patchEntry.replacement);
+ root.map["dyld-cache-fixups"].array.push_back(patchNode);
+ });
+
Image::ResolvedSymbolTarget entry;
if ( closure->mainEntry(entry) )
root.map["main"].value = printTarget(imagesArrays, entry);
closure::LoadedFileInfo MachOAnalyzer::load(Diagnostics& diag, const closure::FileSystem& fileSystem, const char* path, const char* reqArchName, Platform reqPlatform)
{
+ // FIXME: This should probably be an assert, but if we happen to have a diagnostic here then something is wrong
+ // above us and we should quickly return instead of doing unnecessary work.
+ if (diag.hasError())
+ return closure::LoadedFileInfo();
+
closure::LoadedFileInfo info;
char realerPath[MAXPATHLEN];
if (!fileSystem.loadFile(path, info, realerPath, ^(const char *format, ...) {
return closure::LoadedFileInfo();
}
+ // If we now have an error, but succeeded, then we must have tried multiple paths, one of which errored, but
+ // then succeeded on a later path. So clear the error.
+ if (diag.hasError())
+ diag.clearError();
+
// if fat, remap just slice needed
bool fatButMissingSlice;
const FatFile* fh = (FatFile*)info.fileContent;
{
// must start with mach-o magic value
if ( (this->magic != MH_MAGIC) && (this->magic != MH_MAGIC_64) ) {
- diag.error("could not use '%s' because it is not a mach-o file, 0x%08X", path, this->magic);
+ diag.error("could not use '%s' because it is not a mach-o file: 0x%08X 0x%08X", path, this->magic, this->cputype);
return false;
}
case MH_BUNDLE:
break;
default:
- diag.error("could not use '%s' because it is not a dylib, bundle, or executable", path);
+ diag.error("could not use '%s' because it is not a dylib, bundle, or executable, filetype=0x%08X", path, this->filetype);
return false;
}
return false;
}
+ // <rdar://problem/45525884> to avoid heap smasher, don't load this dylib
+ if ( strcmp(path, "/usr/lib/libnetsnmp.5.2.1.dylib") == 0 )
+ return false;
+
// further validations done in validLinkedit()
return true;
return false;
// <rdar://problem/13622786> ignore code signatures in macOS binaries built with pre-10.9 tools
- __block bool goodSignature = true;
if ( (this->cputype == CPU_TYPE_X86_64) || (this->cputype == CPU_TYPE_I386) ) {
+ __block bool foundPlatform = false;
+ __block bool badSignature = false;
forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
+ foundPlatform = true;
if ( (platform == Platform::macOS) && (sdk < 0x000A0900) )
- goodSignature = false;
+ badSignature = true;
});
+ return foundPlatform && !badSignature;
}
- return goodSignature;
+ return true;
}
bool MachOAnalyzer::hasInitializer(Diagnostics& diag, bool contentRebased, const void* dyldCache) const
else if ( this->magic == MH_MAGIC )
startCmds = (load_command*)((char *)this + sizeof(mach_header));
else {
- diag.error("file does not start with MH_MAGIC[_64]");
+ const uint32_t* h = (uint32_t*)this;
+ diag.error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h [1]);
return; // not a mach-o file
}
const load_command* const cmdsEnd = (load_command*)((char*)startCmds + this->sizeofcmds);
for (uint32_t i = 0; i < this->ncmds; ++i) {
const load_command* nextCmd = (load_command*)((char *)cmd + cmd->cmdsize);
if ( cmd->cmdsize < 8 ) {
- diag.error("malformed load command #%d, size too small %d", i, cmd->cmdsize);
+ diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) too small", i, this->ncmds, cmd, this, cmd->cmdsize);
return;
}
if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) {
- diag.error("malformed load command #%d, size too large 0x%X", i, cmd->cmdsize);
+ diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, this->ncmds, cmd, this, cmd->cmdsize, cmdsEnd);
return;
}
callback(cmd, stop);
-static bool addIfMachO(const dyld3::closure::FileSystem& fileSystem, const std::string& runtimePath, const struct stat& statBuf, bool requireSIP, std::vector<MappedMachOsByCategory>& files)
+static bool addIfMachO(const dyld3::closure::FileSystem& fileSystem, const std::string& runtimePath, const struct stat& statBuf, bool requireSIP,
+ dev_t rootFS, std::vector<MappedMachOsByCategory>& files)
{
// don't precompute closure info for any debug or profile dylibs
if ( endsWith(runtimePath, "_profile.dylib") || endsWith(runtimePath, "_debug.dylib") || endsWith(runtimePath, "_profile") || endsWith(runtimePath, "_debug") )
if ( startsWith(runtimePath, "/usr/lib/system/introspection/") )
return false;
+ // Only use files on the same volume as the boot volume
+ if (statBuf.st_dev != rootFS) {
+ if ( verbose )
+ fprintf(stderr, "update_dyld_shared_cache: warning: skipping overlay file '%s' which is not on the root volume\n", runtimePath.c_str());
+ return false;
+ }
+
auto warningHandler = ^(const char* msg) {
if ( verbose )
fprintf(stderr, "update_dyld_shared_cache: warning: cannot build dlopen closure for '%s' because %s\n", runtimePath.c_str(), msg);
return result;
}
-static void findAllFiles(const std::vector<std::string>& pathPrefixes, bool requireSIP, std::vector<MappedMachOsByCategory>& files)
+static void findAllFiles(const std::vector<std::string>& pathPrefixes, bool requireSIP, dev_t rootFS, std::vector<MappedMachOsByCategory>& files)
{
std::unordered_set<std::string> skipDirs;
for (const char* s : sDontUsePrefixes)
return;
// if the file is mach-o, add to list
- if ( addIfMachO(fileSystem, path, statBuf, requireSIP, files) ) {
+ if ( addIfMachO(fileSystem, path, statBuf, requireSIP, rootFS, files) ) {
if ( multiplePrefixes )
alreadyUsed.insert(path);
}
}
-static void findOSFilesViaBOMS(const std::vector<std::string>& pathPrefixes, bool requireSIP, std::vector<MappedMachOsByCategory>& files)
+static void findOSFilesViaBOMS(const std::vector<std::string>& pathPrefixes, bool requireSIP, dev_t rootFS, std::vector<MappedMachOsByCategory>& files)
{
__block std::unordered_set<std::string> runtimePathsFound;
for (const std::string& prefix : pathPrefixes) {
std::string fullPath2 = prefix2 + runPath;
if ( stat(fullPath2.c_str(), &statBuf2) == 0 ) {
dyld3::closure::FileSystemPhysical fileSystem(prefix2.c_str());
- addIfMachO(fileSystem, runPath, statBuf2, requireSIP, files);
- runtimePathsFound.insert(runPath);
- break;
+ if ( addIfMachO(fileSystem, runPath, statBuf2, requireSIP, rootFS, files) ) {
+ runtimePathsFound.insert(runPath);
+ break;
+ }
}
}
}
}
}
+ // Find the boot volume so that we can ensure all overlays are on the same volume
+ struct stat rootStatBuf;
+ if ( stat(rootPath == "" ? "/" : rootPath.c_str(), &rootStatBuf) != 0 ) {
+ fprintf(stderr, "update_dyld_shared_cache: error: could not stat root file system because '%s'\n", strerror(errno));
+ return 1;
+ }
+
+ dev_t rootFS = rootStatBuf.st_dev;
+
//
// pathPrefixes for three modes:
// 1) no options: { "" } // search only boot volume
// 3) -root: { root } // search only -root volume
//
std::vector<std::string> pathPrefixes;
- if ( !overlayPath.empty() )
- pathPrefixes.push_back(overlayPath);
+ if ( !overlayPath.empty() ) {
+ // Only add the overlay path if it exists, and is the same volume as the root
+ struct stat overlayStatBuf;
+ if ( stat(overlayPath.c_str(), &overlayStatBuf) != 0 ) {
+ fprintf(stderr, "update_dyld_shared_cache: warning: ignoring overlay dir '%s' because '%s'\n", overlayPath.c_str(), strerror(errno));
+ overlayPath.clear();
+ } else if (overlayStatBuf.st_dev != rootFS) {
+ fprintf(stderr, "update_dyld_shared_cache: warning: ignoring overlay dir '%s' because it is not the boot volume\n", overlayPath.c_str());
+ overlayPath.clear();
+ } else {
+ pathPrefixes.push_back(overlayPath);
+ }
+ }
pathPrefixes.push_back(rootPath);
-
if ( cacheDir.empty() ) {
// if -cache_dir is not specified, then write() will eventually fail if we are not running as root
if ( geteuid() != 0 ) {
if ( archStrs.count("i386") )
allFileSets.push_back({"i386"});
if ( searchDisk )
- findAllFiles(pathPrefixes, requireDylibsBeRootlessProtected, allFileSets);
+ findAllFiles(pathPrefixes, requireDylibsBeRootlessProtected, rootFS, allFileSets);
else {
std::unordered_set<std::string> runtimePathsFound;
- findOSFilesViaBOMS(pathPrefixes, requireDylibsBeRootlessProtected, allFileSets);
+ findOSFilesViaBOMS(pathPrefixes, requireDylibsBeRootlessProtected, rootFS, allFileSets);
}
// nothing in OS uses i386 dylibs, so only dylibs used by third party apps need to be in cache
std::vector<MappedMachOsByCategory> mappedFiles;
mappedFiles.push_back({fileSet.archName});
- if ( addIfMachO(fileSystem, runtimePath, statBuf, requireDylibsBeRootlessProtected, mappedFiles) ) {
+ if ( addIfMachO(fileSystem, runtimePath, statBuf, requireDylibsBeRootlessProtected, rootFS, mappedFiles) ) {
if ( !mappedFiles.back().dylibsForCache.empty() ) {
if ( verbose )
fprintf(stderr, "verifySelfContained, add %s\n", mappedFiles.back().dylibsForCache.back().runtimePath.c_str());
// BUILD: mkdir -p $BUILD_DIR/override
+// BUILD: mkdir -p $BUILD_DIR/re-export-override
// BUILD: $CC myzlib.c -dynamiclib -o $BUILD_DIR/override/libz.1.dylib -install_name /usr/lib/libz.1.dylib -compatibility_version 1.0 -framework CoreFoundation
+// BUILD: $CC reexported-myzlib.c -dynamiclib -o $BUILD_DIR/re-export-override/reexported.dylib -compatibility_version 1.0 -framework CoreFoundation
+// BUILD: $CC reexporter.c -dynamiclib -o $BUILD_DIR/re-export-override/libz.1.dylib -install_name /usr/lib/libz.1.dylib -compatibility_version 1.0 -Wl,-reexport_library,$BUILD_DIR/re-export-override/reexported.dylib
// BUILD: $CC main.c -o $BUILD_DIR/main.exe -lz
// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main.exe
// RUN: ./main.exe
// RUN: DYLD_LIBRARY_PATH=$RUN_DIR/override/ ./main.exe
+// RUN: DYLD_LIBRARY_PATH=$RUN_DIR/re-export-override/ ./main.exe
#include <stdio.h>
#include <stdlib.h>
--- /dev/null
+const char* zlibVersion()
+{
+ return "my";
+}
--- /dev/null
+
+int foo() {
+ return 0;
+}