#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <mach-o/loader.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <algorithm>
#include <TargetConditionals.h>
#include "ClosureFileSystemPhysical.h"
#include "MachOAnalyzer.h"
#include "MachOFile.h"
#include "../testing/test-cases/kernel-test-runner.h"
const bool isLoggingEnabled = false;
int entryFunc(const TestRunnerFunctions* funcs);
typedef __typeof(&entryFunc) EntryFuncTy;
TestRunnerFunctions testFuncs = {
.version = 1,
.mhs = { nullptr, nullptr, nullptr, nullptr },
.basePointers = { nullptr, nullptr, nullptr, nullptr },
.printf = &::printf,
.exit = &::exit,
.testPass = &_PASS,
.testFail = &_FAIL,
.testLog = &_LOG,
.testTimeout = &_TIMEOUT,
};
struct LoadedMachO {
const dyld3::MachOAnalyzer* ma = nullptr;
const void* basePointer = nullptr;
};
LoadedMachO loadPath(const char* binaryPath) {
__block Diagnostics diag;
dyld3::closure::FileSystemPhysical fileSystem;
dyld3::closure::LoadedFileInfo info;
char realerPath[MAXPATHLEN];
__block bool printedError = false;
if (!fileSystem.loadFile(binaryPath, info, realerPath, ^(const char* format, ...) {
fprintf(stderr, "run-static: ");
va_list list;
va_start(list, format);
vfprintf(stderr, format, list);
va_end(list);
printedError = true;
})) {
if (!printedError )
fprintf(stderr, "run-static: %s: file not found\n", binaryPath);
exit(1);
}
const char* currentArchName = dyld3::MachOFile::currentArchName();
const dyld3::GradedArchs& currentArchs = dyld3::GradedArchs::forName(currentArchName);
__block const dyld3::MachOFile* mf = nullptr;
__block uint64_t sliceOffset = 0;
if ( dyld3::FatFile::isFatFile(info.fileContent) ) {
const dyld3::FatFile* ff = (dyld3::FatFile*)info.fileContent;
ff->forEachSlice(diag, info.fileContentLen, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType,
const void* sliceStart, uint64_t sliceSize, bool& stop) {
const dyld3::MachOFile* sliceMF = (dyld3::MachOFile*)sliceStart;
if ( currentArchs.grade(sliceMF->cputype, sliceMF->cpusubtype, false) != 0 ) {
mf = sliceMF;
sliceOffset = (uint64_t)mf - (uint64_t)ff;
stop = true;
return;
}
});
if ( diag.hasError() ) {
fprintf(stderr, "Error: %s\n", diag.errorMessage());
return { nullptr, nullptr };
}
if ( mf == nullptr ) {
fprintf(stderr, "Could not use binary '%s' because it does not contain a slice compatible with host '%s'\n",
binaryPath, currentArchName);
return { nullptr, nullptr };
}
} else {
mf = (dyld3::MachOFile*)info.fileContent;
if ( !mf->isMachO(diag, info.sliceLen) ) {
fprintf(stderr, "Could not use binary '%s' because '%s'\n", binaryPath, diag.errorMessage());
return { nullptr, nullptr };
}
if ( currentArchs.grade(mf->cputype, mf->cpusubtype, false) == 0 ) {
fprintf(stderr, "Could not use binary '%s' because 'incompatible arch'\n", binaryPath);
return { nullptr, nullptr };
}
}
if ( !mf->isFileSet() ) {
fprintf(stderr, "Could not use binary '%s' because 'it is not a static executable'\n", binaryPath);
return { nullptr, nullptr };
}
uint64_t mappedSize = ((dyld3::MachOAnalyzer*)mf)->mappedSize();
vm_address_t mappedAddr;
if ( ::vm_allocate(mach_task_self(), &mappedAddr, (size_t)mappedSize, VM_FLAGS_ANYWHERE) != 0 ) {
fprintf(stderr, "Could not use binary '%s' because 'vm allocation failure'\n", binaryPath);
return { nullptr, nullptr };
}
int fd = open(binaryPath, O_RDONLY);
if ( fd == 0 ) {
fprintf(stderr, "Could not open binary '%s' because '%s'\n", binaryPath, strerror(errno));
return { nullptr, nullptr };
}
__block uint64_t baseAddress = ~0ULL;
__block uint64_t textSegVMAddr = ~0ULL;
mf->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
baseAddress = std::min(baseAddress, info.vmAddr);
if ( strcmp(info.segName, "__TEXT") == 0 ) {
textSegVMAddr = info.vmAddr;
}
});
uint64_t loadAddress = (uint64_t)mappedAddr;
if ( isLoggingEnabled ) {
fprintf(stderr, "Mapping binary built at 0x%llx to 0x%llx\n", baseAddress, loadAddress);
}
mf->forEachSegment(^(const dyld3::MachOFile::SegmentInfo &info, bool &stop) {
uint64_t requestedLoadAddress = info.vmAddr - baseAddress + loadAddress;
if ( isLoggingEnabled )
fprintf(stderr, "Mapping %p: %s with perms %d\n", (void*)requestedLoadAddress, info.segName, info.protections);
if ( info.vmSize == 0 )
return;
size_t readBytes = pread(fd, (void*)requestedLoadAddress, (uintptr_t)info.fileSize, sliceOffset + info.fileOffset);
if ( readBytes != info.fileSize ) {
fprintf(stderr, "Didn't read enough bytes\n");
exit(1);
}
uint32_t protections = info.protections;
if ( !strcmp(info.segName, "__DATA_CONST") )
protections = VM_PROT_READ | VM_PROT_WRITE;
const bool setCurrentPermissions = false;
kern_return_t r = vm_protect(mach_task_self(), (vm_address_t)requestedLoadAddress, (uintptr_t)info.vmSize, setCurrentPermissions, protections);
if ( r != KERN_SUCCESS ) {
diag.error("vm_protect didn't work because %d", r);
stop = true;
return;
}
});
if ( diag.hasError() ) {
fprintf(stderr, "Error: %s\n", diag.errorMessage());
return { nullptr, nullptr };
}
if ( textSegVMAddr != baseAddress ) {
const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)(mappedAddr + textSegVMAddr - baseAddress);
if ( !ma->validMachOForArchAndPlatform(diag, (size_t)mappedSize, binaryPath, currentArchs, dyld3::Platform::unknown, false) ) {
fprintf(stderr, "Error: %s\n", diag.errorMessage());
exit(1);
}
return { ma, (const void*)mappedAddr };
}
const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)mappedAddr;
if ( !ma->validMachOForArchAndPlatform(diag, (size_t)mappedSize, binaryPath, currentArchs, dyld3::Platform::unknown, false) ) {
fprintf(stderr, "Error: %s\n", diag.errorMessage());
exit(1);
}
return { ma, (const void*)mappedAddr };
}
int main(int argc, const char * argv[]) {
bool unsupported = false;
#if TARGET_OS_WATCH
unsupported = true;
#endif
if ( unsupported ) {
funcs = &testFuncs;
PASS("Success");
}
if ( (argc < 2) || (argc > 5) ) {
fprintf(stderr, "Usage: run-static *path to static binary* [- - *path to auc kc*]\n");
return 1;
}
for (unsigned i = 1; i != argc; ++i) {
if ( !strcmp(argv[i], "-") )
continue;
LoadedMachO macho = loadPath(argv[i]);
if ( macho.ma == nullptr )
return 1;
testFuncs.mhs[i - 1] = macho.ma;
testFuncs.basePointers[i - 1] = macho.basePointer;
}
uint64_t entryOffset = 0;
bool usesCRT = false;
const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)testFuncs.mhs[0];
if ( !ma->getEntry(entryOffset, usesCRT) ) {
fprintf(stderr, "Could not use binary '%s' because 'no entry defined'\n", argv[1]);
return 1;
}
EntryFuncTy entryFunc = (EntryFuncTy)((uint8_t*)testFuncs.mhs[0] + entryOffset);
#if __has_feature(ptrauth_calls)
entryFunc = (EntryFuncTy)__builtin_ptrauth_sign_unauthenticated((void*)entryFunc, 0, 0);
#endif
fprintf(stderr, "Entering static binary at %p\n", entryFunc);
int returnCode = entryFunc(&testFuncs);
if ( returnCode != 0 ) {
fprintf(stderr, "Binary '%s' returned non-zero value %d\n", argv[1], returnCode);
return returnCode;
}
return 0;
}