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 | // BUILD: $CC foo.c -bundle -o $BUILD_DIR/test.bundle // BUILD: $CC main.c -DRUN_DIR="$RUN_DIR" -o $BUILD_DIR/dlopen-fastpath.exe // RUN: ./dlopen-fastpath.exe #include <dlfcn.h> #include <mach-o/dyld_priv.h> #include <mach-o/dyld_process_info.h> #include <pthread.h> #include <stdio.h> #include "test_support.h" bool sDoneLaunchImageNotifiers = false; // This method runs on another thread static void* work(void* arg) { void* handle = dlopen("/usr/lib/system/libsystem_platform.dylib", RTLD_NOW); if ( handle == NULL ) { FAIL("Couldn't dlopen libsystem_platform.dylib because: %s", dlerror()); } return 0; } static void notify(const struct mach_header* mh, intptr_t vmaddr_slide) { if ( !sDoneLaunchImageNotifiers ) return; // If we hit here, then we are doing the notifier for test.bundle, which is being dlopen()ed from the main thread // Spawn a thread to do another dlopen static pthread_t worker; int result = pthread_create(&worker, NULL, work, NULL); if ( result != 0 ) { FAIL("work pthread_create failed because %d\n", result); return; } void* dummy; pthread_join(worker, &dummy); } static void* timeoutWork(void* arg) { sleep(5); FAIL("Timeout, probably because dyld hash doesn't match\n"); exit(1); return 0; } // The dlopen fast path only works if dyld can use the shared cache prebuilt loaders. // That is only possible if dyld and the shared cache prebuilt loader hash matches, so in the // case of a new dyld, we might have a mismatch. Detect this and bail out static void exitOnMismatchedHash() { size_t sharedCacheLength; uintptr_t sharedCacheBaseAddress = (uintptr_t)_dyld_get_shared_cache_range(&sharedCacheLength); if ( sharedCacheBaseAddress == 0 ) PASS("Success"); kern_return_t result; dyld_process_info info = _dyld_process_info_create(mach_task_self(), 0, &result); if (result != KERN_SUCCESS) { FAIL("dyld_process_info() should succeed, get return code %d", result); } if (info == NULL) { FAIL("dyld_process_info(task, 0) always returns a value"); } __block bool foundDyld = false; __block uintptr_t dyldLoadAddress = 0; _dyld_process_info_for_each_image(info, ^(uint64_t machHeaderAddress, const uuid_t uuid, const char* path) { if ( strstr(path, "/dyld") != NULL ) { foundDyld = true; dyldLoadAddress = (uintptr_t)machHeaderAddress; } }); _dyld_process_info_release(info); if (!foundDyld) { FAIL("dyld should always be in the image list"); } if ( dyldLoadAddress == 0 ) { FAIL("dyld __TEXT not found"); } // This is horrible. We're going to interpret the handle to work out if its in the shared cache // The handle is: // void* handle = (void*)(((uintptr_t)ldr ^ dyldStart) | flags); // handle = ptrauth_sign_unauthenticated(handle, ptrauth_key_process_dependent_data, ptrauth_string_discriminator("dlopen")); void* handle = dlopen("/usr/lib/system/libsystem_platform.dylib", RTLD_NOW); if ( handle == NULL ) { FAIL("Couldn't dlopen libsystem_platform.dylib because: %s", dlerror()); } // Stolen from dyld // static const Loader* loaderFromHandle(void* h, bool& firstOnly) // { // uintptr_t dyldStart = (uintptr_t)&__dso_handle; // #if __has_feature(ptrauth_calls) // if ( h != nullptr ) { // // Note we don't use ptrauth_auth_data, as we don't want to crash on bad handles // void* strippedHandle = ptrauth_strip(h, ptrauth_key_process_dependent_data); // void* validHandle = ptrauth_sign_unauthenticated(strippedHandle, ptrauth_key_process_dependent_data, ptrauth_string_discriminator("dlopen")); // if ( h == validHandle ) // h = strippedHandle; // } // #endif // firstOnly = (((uintptr_t)h) & 1); // return (Loader*)((((uintptr_t)h) & ~1) ^ dyldStart); // } #if __has_feature(ptrauth_calls) handle = ptrauth_strip(handle, ptrauth_key_process_dependent_data); #endif uintptr_t loaderAddress = (uintptr_t)((((uintptr_t)handle) & ~1) ^ dyldLoadAddress); if ( loaderAddress < sharedCacheBaseAddress ) PASS("Success"); if ( loaderAddress >= (sharedCacheBaseAddress + sharedCacheLength) ) PASS("Success"); } int main(int argc, const char* argv[], const char* envp[], const char* apple[]) { exitOnMismatchedHash(); // Get notified about all initial images _dyld_register_func_for_add_image(¬ify); sDoneLaunchImageNotifiers = true; // Spawn a thread quit if we take too long static pthread_t worker; int result = pthread_create(&worker, NULL, timeoutWork, NULL); if ( result != 0 ) { FAIL("timeoutWork pthread_create failed because %d\n", result); return 1; } // dlopen something new on the main thread. The worker thread will run the notifier for this void* handle = dlopen(RUN_DIR "/test.bundle", RTLD_NOW); // If we got here then we didn't deadlock, so kill the timeout thread pthread_cancel(worker); PASS("Success"); } |