Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | #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; // base pointer is the same as 'ma' when the binary has __TEXT first, // but will point at where we mapped __DATA if building a reverse auxKC. 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); } // __DATA_CONST is read-only when we actually run live, but this test runner fixes up __DATA_CONST after this vm_protect // For now just don't make __DATA_CONST read only 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 ) { // __DATA is first. ma should still point to __TEXT 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 }; } // __TEXT is first, so ma and base address are the same 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 // HACK: Watch archs are not supported right now, so just return 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); //kill(getpid(), SIGSTOP); int returnCode = entryFunc(&testFuncs); if ( returnCode != 0 ) { fprintf(stderr, "Binary '%s' returned non-zero value %d\n", argv[1], returnCode); return returnCode; } return 0; } |