X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/10b92d3b56e938a5c1f5e708ddd1c5d0c2c807d1..ba4c3badc27ea8c4637b4e91a49725742a02a53c:/src/dyld_process_info_notify.cpp diff --git a/src/dyld_process_info_notify.cpp b/src/dyld_process_info_notify.cpp index 6d5eee3..571e677 100644 --- a/src/dyld_process_info_notify.cpp +++ b/src/dyld_process_info_notify.cpp @@ -29,291 +29,352 @@ #include #include #include +#include + #include "dyld_process_info.h" #include "dyld_process_info_internal.h" #include "dyld_images.h" #include "dyld_priv.h" -#include "LaunchCache.h" #include "Loading.h" #include "AllImages.h" +extern "C" int _dyld_func_lookup(const char* name, void** address); typedef void (^Notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path); typedef void (^NotifyExit)(); typedef void (^NotifyMain)(); - // // Object used for monitoring another processes dyld loads // struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base { - static dyld_process_info_notify_base* make(task_t task, dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, kern_return_t* kr); - ~dyld_process_info_notify_base(); + dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task, kern_return_t* kr); + ~dyld_process_info_notify_base(); + bool enabled() const; + void retain(); + void release(); - uint32_t& retainCount() const { return _retainCount; } void setNotifyMain(NotifyMain notifyMain) const { _notifyMain = notifyMain; } // override new and delete so we don't need to link with libc++ static void* operator new(size_t sz) { return malloc(sz); } - static void operator delete(void* p) { return free(p); } + static void operator delete(void* p) { free(p); } private: - dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task); - kern_return_t makePorts(); - kern_return_t pokeSendPortIntoTarget(); - kern_return_t unpokeSendPortInTarget(); - void setMachSourceOnQueue(); - - mutable uint32_t _retainCount; - dispatch_queue_t _queue; - Notify _notify; - NotifyExit _notifyExit; - mutable NotifyMain _notifyMain; - task_t _targetTask; - dispatch_source_t _machSource; - uint64_t _portAddressInTarget; - mach_port_t _sendPortInTarget; // target is process being watched for image loading/unloading - mach_port_t _receivePortInMonitor; // monitor is process being notified of image loading/unloading + void handleEvent(); + void teardown(); + void replyToMonitoredProcess(mach_msg_header_t& header); + + RemoteBuffer _remoteAllImageInfoBuffer; + uint32_t* _notifyMachPorts; + uint32_t _notifySlot; + mutable std::atomic _retainCount; + dispatch_queue_t _queue; + Notify _notify; + NotifyExit _notifyExit; + mutable NotifyMain _notifyMain; + task_t _targetTask; + dispatch_source_t _machSource; + mach_port_t _sendPortInTarget; // target is process being watched for image loading/unloading + mach_port_t _receivePortInMonitor; // monitor is process being notified of image loading/unloading + std::atomic _disabled; }; -dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task) - : _retainCount(1), _queue(queue), _notify(notify), _notifyExit(notifyExit), _notifyMain(NULL), _targetTask(task), _machSource(NULL), _portAddressInTarget(0), _sendPortInTarget(0), _receivePortInMonitor(0) +dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, + task_t task, kern_return_t* kr) : + _notifyMachPorts(nullptr), _notifySlot(0), _retainCount(1), _queue(queue), _notify(notify), _notifyExit(notifyExit), + _notifyMain(nullptr), _targetTask(task), _machSource(nullptr), _sendPortInTarget(0), _receivePortInMonitor(0), + _disabled(false) { + assert(_disabled == false); dispatch_retain(_queue); -} - -dyld_process_info_notify_base::~dyld_process_info_notify_base() -{ - if ( _machSource ) { - dispatch_release(_machSource); - _machSource = NULL; - } - if ( _portAddressInTarget ) { - unpokeSendPortInTarget(); - _portAddressInTarget = 0; - } - if ( _sendPortInTarget ) { - _sendPortInTarget = 0; - } - dispatch_release(_queue); - if ( _receivePortInMonitor != 0 ) { - mach_port_deallocate(mach_task_self(), _receivePortInMonitor); - _receivePortInMonitor = 0; - } -} + // Allocate a port to listen on in this monitoring task + mach_port_options_t options = { .flags = MPO_IMPORTANCE_RECEIVER | MPO_CONTEXT_AS_GUARD | MPO_STRICT, + .mpl = { MACH_PORT_QLIMIT_DEFAULT }}; + if ((*kr = mach_port_construct(mach_task_self(), &options, (mach_port_context_t)this, &_receivePortInMonitor))) { + teardown(); + return; + } + if (_targetTask == mach_task_self()) { + _sendPortInTarget = _receivePortInMonitor; + (void)mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND); + } else { + // Insert a deadname right into the port to trigger notifications + kern_return_t r = KERN_NAME_EXISTS; + while (r == KERN_NAME_EXISTS) { + _sendPortInTarget = MACH_PORT_NULL; + //FIXME file radar + r = mach_port_allocate(_targetTask, MACH_PORT_RIGHT_DEAD_NAME, &_sendPortInTarget); + if (r != KERN_SUCCESS) { + *kr = r; + return; + } + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + r = mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND); + } + if (r != KERN_SUCCESS) { + *kr = r; + return; + } + // Notify us if the target dies + mach_port_t previous = MACH_PORT_NULL; + if ((*kr = mach_port_request_notification(_targetTask, _sendPortInTarget, MACH_NOTIFY_DEAD_NAME, 0, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous))) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this); + teardown(); + return; + } + // This is a new port, if there is a previous notifier attached then something is wrong... abort. + if (previous != MACH_PORT_NULL) { + (void)mach_port_deallocate(mach_task_self(), previous); + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this); + teardown(); + return; + } + } -dyld_process_info_notify_base* dyld_process_info_notify_base::make(task_t task, dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, kern_return_t* kr) -{ - dyld_process_info_notify_base* obj = new dyld_process_info_notify_base(queue, notify, notifyExit, task); + // Setup the event handler for the port + _machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _receivePortInMonitor, 0, _queue); + if (_machSource == nullptr) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this); + teardown(); + return; + } + dispatch_source_set_event_handler(_machSource, ^{ + handleEvent(); + }); + dispatch_source_set_cancel_handler(_machSource, ^{ + if ( _receivePortInMonitor != 0 ) { + (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this); + _receivePortInMonitor = 0; + } + }); + dispatch_activate(_machSource); - if ( kern_return_t r = obj->makePorts() ) { - if ( kr != NULL ) - *kr = r; - goto fail; - } + // get location on all_image_infos in the target task + task_dyld_info_data_t taskDyldInfo; + mach_msg_type_number_t taskDyldInfoCount = TASK_DYLD_INFO_COUNT; + if ((*kr = task_info(_targetTask, TASK_DYLD_INFO, (task_info_t)&taskDyldInfo, &taskDyldInfoCount))) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + teardown(); + return; + } + // Poke the portname of our port into the target task + _remoteAllImageInfoBuffer = RemoteBuffer(_targetTask, taskDyldInfo.all_image_info_addr, taskDyldInfo.all_image_info_size, true, false); + *kr = _remoteAllImageInfoBuffer.getKernelReturn(); + if (*kr) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + teardown(); + return; + } - obj->setMachSourceOnQueue(); + static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits"); + if ( taskDyldInfo.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { + _notifyMachPorts = (uint32_t *)((uint8_t *)_remoteAllImageInfoBuffer.getLocalAddress() + offsetof(dyld_all_image_infos_32,notifyMachPorts)); + } else { + _notifyMachPorts = (uint32_t *)((uint8_t *)_remoteAllImageInfoBuffer.getLocalAddress() + offsetof(dyld_all_image_infos_64,notifyMachPorts)); + } - if ( kern_return_t r = obj->pokeSendPortIntoTarget() ) { - if ( kr != NULL ) - *kr = r; - goto fail; - } +#if 0 + //If all the slots are filled we will sleep and retry a few times before giving up + for (uint32_t i=0; i<10; ++i) { + for (_notifySlot=0; _notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++_notifySlot) { + if (OSAtomicCompareAndSwap32(0, _sendPortInTarget, (volatile int32_t*)&_notifyMachPorts[_notifySlot])) { + break; + } + } + if (_notifySlot == DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT) { + // all the slots are filled, sleep and try again + usleep(1000 * 50); // 50ms + } else { + // if _notifySlot is set we are done + break; + } + } +#else + for (_notifySlot=0; _notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++_notifySlot) { + if (OSAtomicCompareAndSwap32(0, _sendPortInTarget, (volatile int32_t*)&_notifyMachPorts[_notifySlot])) { + break; + } + } +#endif - if ( kr != NULL ) - *kr = KERN_SUCCESS; - return obj; + if (_notifySlot == DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + teardown(); + *kr = KERN_UREFS_OVERFLOW; + return; + } -fail: - delete obj; - return NULL; + *kr = KERN_SUCCESS; } - -kern_return_t dyld_process_info_notify_base::makePorts() -{ - // Allocate a port to listen on in this monitoring task - if ( kern_return_t r = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &_receivePortInMonitor) ) - return r; - - // Add send rights for replying - if ( kern_return_t r = mach_port_insert_right(mach_task_self(), _receivePortInMonitor, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND) ) - return r; - - // Allocate a name in the target. We need a new name to add send rights to - if ( kern_return_t r = mach_port_allocate(_targetTask, MACH_PORT_RIGHT_DEAD_NAME, &_sendPortInTarget) ) - return r; - - // Deallocate the dead name - if ( kern_return_t r = mach_port_mod_refs(_targetTask, _sendPortInTarget, MACH_PORT_RIGHT_DEAD_NAME, -1) ) - return r; - - // Make the dead name a send right to our listening port - if ( kern_return_t r = mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND) ) - return r; - - // Notify us if the target dies - mach_port_t previous = MACH_PORT_NULL; - if ( kern_return_t r = mach_port_request_notification(_targetTask, _sendPortInTarget, MACH_NOTIFY_DEAD_NAME, 0, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous)) - return r; - - //fprintf(stderr, "_sendPortInTarget=%d, _receivePortInMonitor=%d\n", _sendPortInTarget, _receivePortInMonitor); - return KERN_SUCCESS; +dyld_process_info_notify_base::~dyld_process_info_notify_base() { + if (!_disabled) { + fprintf(stderr, "dyld: ~dyld_process_info_notify_base called while still enabled\n"); + } + dispatch_release(_queue); } - - -void dyld_process_info_notify_base::setMachSourceOnQueue() -{ - NotifyExit exitHandler = _notifyExit; - _machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _receivePortInMonitor, 0, _queue); - dispatch_source_set_event_handler(_machSource, ^{ - uint8_t messageBuffer[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE]; - mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer; - - kern_return_t r = mach_msg(h, MACH_RCV_MSG, 0, sizeof(messageBuffer), _receivePortInMonitor, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); - if ( r == KERN_SUCCESS ) { - //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); - if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_LOAD_ID || h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID ) { - // run notifier block for each [un]load image - const dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)messageBuffer; - const dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&messageBuffer[header->imagesOffset]; - const char* const stringPool = (char*)&messageBuffer[header->stringsOffset]; - for (unsigned i=0; i < header->imageCount; ++i) { - bool isUnload = (h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID); - _notify(isUnload, header->timestamp, entries[i].loadAddress, entries[i].uuid, stringPool + entries[i].pathStringOffset); - } - // reply to dyld, so it can continue - mach_msg_header_t replyHeader; - replyHeader.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND); - replyHeader.msgh_id = 0; - replyHeader.msgh_local_port = MACH_PORT_NULL; - replyHeader.msgh_remote_port = h->msgh_remote_port; - replyHeader.msgh_reserved = 0; - replyHeader.msgh_size = sizeof(replyHeader); - mach_msg(&replyHeader, MACH_SEND_MSG | MACH_SEND_TIMEOUT, replyHeader.msgh_size, 0, MACH_PORT_NULL, 100, MACH_PORT_NULL); - } - else if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_MAIN_ID ) { - if ( _notifyMain != NULL ) { - _notifyMain(); - } - // reply to dyld, so it can continue - mach_msg_header_t replyHeader; - replyHeader.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND); - replyHeader.msgh_id = 0; - replyHeader.msgh_local_port = MACH_PORT_NULL; - replyHeader.msgh_remote_port = h->msgh_remote_port; - replyHeader.msgh_reserved = 0; - replyHeader.msgh_size = sizeof(replyHeader); - mach_msg(&replyHeader, MACH_SEND_MSG | MACH_SEND_TIMEOUT, replyHeader.msgh_size, 0, MACH_PORT_NULL, 100, MACH_PORT_NULL); - } - else if ( h->msgh_id == MACH_NOTIFY_PORT_DELETED ) { - mach_port_t deadPort = ((mach_port_deleted_notification_t *)h)->not_port; - //fprintf(stderr, "received message id=MACH_NOTIFY_PORT_DELETED, size=%d, deadPort=%d\n", h->msgh_size, deadPort); - if ( deadPort == _sendPortInTarget ) { - // target process died. Clean up ports - _sendPortInTarget = 0; - mach_port_deallocate(mach_task_self(), _receivePortInMonitor); - _receivePortInMonitor = 0; - _portAddressInTarget = 0; - // notify that target is gone - exitHandler(); - } - } - else { - fprintf(stderr, "received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); - } +void dyld_process_info_notify_base::teardown() { + if (!_disabled) { + _disabled = true; + // The connection to the target is dead. Clean up ports + if ( _remoteAllImageInfoBuffer.getLocalAddress() != 0 && _notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT) { + mach_port_t extractedPort = MACH_PORT_NULL; + mach_msg_type_name_t extractedPortType; + kern_return_t kr = mach_port_extract_right(_targetTask, _sendPortInTarget, MACH_MSG_TYPE_COPY_SEND, &extractedPort, &extractedPortType); + if (kr == KERN_SUCCESS) { + if (extractedPort == _receivePortInMonitor) { + if (OSAtomicCompareAndSwap32(_sendPortInTarget, 0, (volatile int32_t*)&_notifyMachPorts[_notifySlot])) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + } + } + (void)mach_port_deallocate(mach_task_self(), extractedPort); + } } - }); - dispatch_resume(_machSource); + _sendPortInTarget = 0; + if ( _machSource ) { + dispatch_source_cancel(_machSource); + dispatch_release(_machSource); + _machSource = NULL; + } + if (_notifyExit) { + dispatch_async(_queue, ^{ + _notifyExit(); + }); + } + } } - -kern_return_t dyld_process_info_notify_base::pokeSendPortIntoTarget() +bool dyld_process_info_notify_base::enabled() const { - // get location on all_image_infos in target task - task_dyld_info_data_t taskDyldInfo; - mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; - kern_return_t r = task_info(_targetTask, TASK_DYLD_INFO, (task_info_t)&taskDyldInfo, &count); - if ( r ) - return r; - - // remap the page containing all_image_infos into this process r/w - mach_vm_address_t mappedAddress = 0; - mach_vm_size_t mappedSize = taskDyldInfo.all_image_info_size; - vm_prot_t curProt = VM_PROT_NONE; - vm_prot_t maxProt = VM_PROT_NONE; - r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, - _targetTask, taskDyldInfo.all_image_info_addr, false, &curProt, &maxProt, VM_INHERIT_NONE); - if ( r ) - return r; - if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) ) - return KERN_PROTECTION_FAILURE; - - // atomically set port into all_image_info_struct - static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits"); - - mach_vm_address_t mappedAddressToPokePort = 0; - if ( taskDyldInfo.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) - mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_32,notifyMachPorts); - else - mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_64,notifyMachPorts); - - // use first available slot - bool slotFound = false; - for (int slotIndex=0; slotIndex < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slotIndex) { - if ( OSAtomicCompareAndSwap32Barrier(0, _sendPortInTarget, (volatile int32_t*)mappedAddressToPokePort) ) { - slotFound = true; - break; - } - mappedAddressToPokePort += sizeof(uint32_t); - } - if ( !slotFound ) { - mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize); - return KERN_UREFS_OVERFLOW; - } - _portAddressInTarget = taskDyldInfo.all_image_info_addr + mappedAddressToPokePort - mappedAddress; - //fprintf(stderr, "poked port %d into target at address 0x%llX\n", _sendPortInTarget, _portAddressInTarget); - mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize); - return r; + return !_disabled; } +void dyld_process_info_notify_base::retain() +{ + _retainCount++; +} - -kern_return_t dyld_process_info_notify_base::unpokeSendPortInTarget() +void dyld_process_info_notify_base::release() { - // remap the page containing all_image_infos into this process r/w - mach_vm_address_t mappedAddress = 0; - mach_vm_size_t mappedSize = sizeof(mach_port_t); - vm_prot_t curProt = VM_PROT_NONE; - vm_prot_t maxProt = VM_PROT_NONE; - kern_return_t r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, - _targetTask, _portAddressInTarget, false, &curProt, &maxProt, VM_INHERIT_NONE); - if ( r ) - return r; - if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) ) - return KERN_PROTECTION_FAILURE; - - OSAtomicCompareAndSwap32Barrier(_sendPortInTarget, 0, (volatile int32_t*)mappedAddress); - - //fprintf(stderr, "cleared port %d from target\n", _sendPortInTarget); - mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize); - return r; + uint32_t newCount = --_retainCount; + + if ( newCount == 0 ) { + teardown(); + } + dispatch_async(_queue, ^{ + delete this; + }); } +void dyld_process_info_notify_base::replyToMonitoredProcess(mach_msg_header_t& header) { + mach_msg_header_t replyHeader; + replyHeader.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSGH_BITS_REMOTE(header.msgh_bits), 0, 0, 0); + replyHeader.msgh_id = 0; + replyHeader.msgh_local_port = MACH_PORT_NULL; + replyHeader.msgh_remote_port = header.msgh_remote_port; + replyHeader.msgh_reserved = 0; + replyHeader.msgh_size = sizeof(replyHeader); + kern_return_t r = mach_msg(&replyHeader, MACH_SEND_MSG, replyHeader.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); + if (r == KERN_SUCCESS) { + header.msgh_remote_port = MACH_PORT_NULL; + } else { + teardown(); + } +} +void dyld_process_info_notify_base::handleEvent() { + // References object may still exist even after the ports are dead. Disable event dispatching + // if the ports have been torn down. + if (_disabled) { + return; + } + // This event handler block has an implicit reference to "this" + // if incrementing the count goes to one, that means the object may have already been destroyed + uint8_t messageBuffer[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE] = {}; + mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer; + + kern_return_t r = mach_msg(h, MACH_RCV_MSG | MACH_RCV_VOUCHER| MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0), 0, sizeof(messageBuffer)-sizeof(mach_msg_audit_trailer_t), _receivePortInMonitor, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if ( r == KERN_SUCCESS && !(h->msgh_bits & MACH_MSGH_BITS_COMPLEX)) { + //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); + + if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_LOAD_ID || h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID ) { + // run notifier block for each [un]load image + const dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)messageBuffer; + if (sizeof(*header) <= h->msgh_size + && header->imagesOffset <= h->msgh_size + && header->stringsOffset <= h->msgh_size + && (header->imageCount * sizeof(dyld_process_info_image_entry)) <= (h->msgh_size - header->imagesOffset)) { + const dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&messageBuffer[header->imagesOffset]; + const char* const stringPool = (char*)&messageBuffer[header->stringsOffset]; + for (unsigned i=0; i < header->imageCount; ++i) { + bool isUnload = (h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID); + if (entries[i].pathStringOffset <= h->msgh_size - header->stringsOffset) { + //fprintf(stderr, "Notifying about: %s\n", stringPool + entries[i].pathStringOffset); + _notify(isUnload, header->timestamp, entries[i].loadAddress, entries[i].uuid, stringPool + entries[i].pathStringOffset); + } else { + teardown(); + break; + } + } + // reply to dyld, so it can continue + replyToMonitoredProcess(*h); + } else { + teardown(); + } + } + else if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_MAIN_ID ) { + if (h->msgh_size != sizeof(mach_msg_header_t)) { + teardown(); + } else if ( _notifyMain != NULL ) { + _notifyMain(); + } + replyToMonitoredProcess(*h); + } else if ( h->msgh_id == MACH_NOTIFY_PORT_DELETED ) { + mach_port_t deadPort = ((mach_port_deleted_notification_t *)h)->not_port; + // Validate this notification came from the kernel + const mach_msg_audit_trailer_t *audit_tlr = (mach_msg_audit_trailer_t *)((uint8_t *)h + round_msg(h->msgh_size)); + if (audit_tlr->msgh_trailer_type == MACH_MSG_TRAILER_FORMAT_0 + && audit_tlr->msgh_trailer_size >= sizeof(mach_msg_audit_trailer_t) + // We cannot link to libbsm, so we are hardcoding the audit token offset (5) + // And the value the represents the kernel (0) + && audit_tlr->msgh_audit.val[5] == 0 + && deadPort == _sendPortInTarget ) { + teardown(); + } + } + else { + fprintf(stderr, "dyld: received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); + } + } + mach_msg_destroy(h); +} dyld_process_info_notify _dyld_process_info_notify(task_t task, dispatch_queue_t queue, void (^notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path), void (^notifyExit)(), kern_return_t* kr) { - return dyld_process_info_notify_base::make(task, queue, notify, notifyExit, kr); + kern_return_t krSink = KERN_SUCCESS; + if (kr == nullptr) { + kr = &krSink; + } + *kr = KERN_SUCCESS; + + dyld_process_info_notify result = new dyld_process_info_notify_base(queue, notify, notifyExit, task, kr); + if (result->enabled()) + return result; + const_cast(result)->release(); + return nullptr; } void _dyld_process_info_notify_main(dyld_process_info_notify object, void (^notifyMain)()) @@ -323,180 +384,60 @@ void _dyld_process_info_notify_main(dyld_process_info_notify object, void (^noti void _dyld_process_info_notify_retain(dyld_process_info_notify object) { - object->retainCount() += 1; + const_cast(object)->retain(); } void _dyld_process_info_notify_release(dyld_process_info_notify object) { - object->retainCount() -= 1; - if ( object->retainCount() == 0 ) - delete object; + const_cast(object)->release(); } +static void (*sNotifyMonitoringDyldMain)() = nullptr; +static void (*sNotifyMonitoringDyld)(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[], + const char* imagePaths[]) = nullptr; - - - - - -namespace dyld3 { - - -static mach_port_t sNotifyReplyPorts[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; -static bool sZombieNotifiers[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; - -static void notifyMonitoringDyld(bool unloading, unsigned portSlot, const launch_cache::DynArray& imageInfos) +void setNotifyMonitoringDyldMain(void (*func)()) { - if ( sZombieNotifiers[portSlot] ) - return; + sNotifyMonitoringDyldMain = func; +} - unsigned entriesSize = (unsigned)imageInfos.count()*sizeof(dyld_process_info_image_entry); - unsigned pathsSize = 0; - for (uintptr_t i=0; i < imageInfos.count(); ++i) { - launch_cache::Image image(imageInfos[i].imageData); - pathsSize += (strlen(image.path()) + 1); - } - unsigned totalSize = (sizeof(dyld_process_info_notify_header) + MAX_TRAILER_SIZE + entriesSize + pathsSize + 127) & -128; // align - if ( totalSize > DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE ) { - // Putting all image paths into one message would make buffer too big. - // Instead split into two messages. Recurse as needed until paths fit in buffer. - unsigned imageHalfCount = (unsigned)imageInfos.count()/2; - const launch_cache::DynArray firstHalf(imageHalfCount, (loader::ImageInfo*)&imageInfos[0]); - const launch_cache::DynArray secondHalf(imageInfos.count() - imageHalfCount, (loader::ImageInfo*)&imageInfos[imageHalfCount]); - notifyMonitoringDyld(unloading, portSlot, firstHalf); - notifyMonitoringDyld(unloading, portSlot, secondHalf); - return; - } - // build buffer to send - dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo(); - uint8_t buffer[totalSize]; - dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)buffer; - header->version = 1; - header->imageCount = (uint32_t)imageInfos.count(); - header->imagesOffset = sizeof(dyld_process_info_notify_header); - header->stringsOffset = sizeof(dyld_process_info_notify_header) + entriesSize; - header->timestamp = allImageInfo->infoArrayChangeTimestamp; - dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&buffer[header->imagesOffset]; - char* const pathPoolStart = (char*)&buffer[header->stringsOffset]; - char* pathPool = pathPoolStart; - for (uintptr_t i=0; i < imageInfos.count(); ++i) { - launch_cache::Image image(imageInfos[i].imageData); - strcpy(pathPool, image.path()); - uint32_t len = (uint32_t)strlen(pathPool); - memcpy(entries->uuid, image.uuid(), sizeof(uuid_t)); - entries->loadAddress = (uint64_t)imageInfos[i].loadAddress; - entries->pathStringOffset = (uint32_t)(pathPool - pathPoolStart); - entries->pathLength = len; - pathPool += (len +1); - ++entries; - } - // lazily alloc reply port - if ( sNotifyReplyPorts[portSlot] == 0 ) { - if ( !mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sNotifyReplyPorts[portSlot]) ) - mach_port_insert_right(mach_task_self(), sNotifyReplyPorts[portSlot], sNotifyReplyPorts[portSlot], MACH_MSG_TYPE_MAKE_SEND); - //log("allocated reply port %d\n", sNotifyReplyPorts[portSlot]); - } - //log("found port to send to\n"); - mach_msg_header_t* h = (mach_msg_header_t*)buffer; - h->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND); // MACH_MSG_TYPE_MAKE_SEND_ONCE - h->msgh_id = unloading ? DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID : DYLD_PROCESS_INFO_NOTIFY_LOAD_ID; - h->msgh_local_port = sNotifyReplyPorts[portSlot]; - h->msgh_remote_port = allImageInfo->notifyPorts[portSlot]; - h->msgh_reserved = 0; - h->msgh_size = (mach_msg_size_t)sizeof(buffer); - //log("sending to port[%d]=%d, size=%d, reply port=%d, id=0x%X\n", portSlot, allImageInfo->notifyPorts[portSlot], h->msgh_size, sNotifyReplyPorts[portSlot], h->msgh_id); - kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_RCV_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[portSlot], 2000, MACH_PORT_NULL); - //log("send result = 0x%X, msg_id=%d, msg_size=%d\n", sendResult, h->msgh_id, h->msgh_size); - if ( sendResult == MACH_SEND_INVALID_DEST ) { - // sender is not responding, detatch - //log("process requesting notification gone. deallocation send port %d and receive port %d\n", allImageInfo->notifyPorts[portSlot], sNotifyReplyPorts[portSlot]); - mach_port_deallocate(mach_task_self(), allImageInfo->notifyPorts[portSlot]); - mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]); - allImageInfo->notifyPorts[portSlot] = 0; - sNotifyReplyPorts[portSlot] = 0; - } - else if ( sendResult == MACH_RCV_TIMED_OUT ) { - // client took too long, ignore him from now on - sZombieNotifiers[portSlot] = true; - mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]); - sNotifyReplyPorts[portSlot] = 0; - } +void setNotifyMonitoringDyld(void (*func)(bool unloading, unsigned imageCount, + const struct mach_header* loadAddresses[], + const char* imagePaths[])) +{ + sNotifyMonitoringDyld = func; } +namespace dyld3 { + void AllImages::notifyMonitorMain() { - dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo(); - for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { - if ( (allImageInfo->notifyPorts[slot] != 0 ) && !sZombieNotifiers[slot] ) { - if ( sNotifyReplyPorts[slot] == 0 ) { - if ( !mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sNotifyReplyPorts[slot]) ) - mach_port_insert_right(mach_task_self(), sNotifyReplyPorts[slot], sNotifyReplyPorts[slot], MACH_MSG_TYPE_MAKE_SEND); - //dyld::log("allocated reply port %d\n", sNotifyReplyPorts[slot]); - } - //dyld::log("found port to send to\n"); - uint8_t messageBuffer[sizeof(mach_msg_header_t) + MAX_TRAILER_SIZE]; - mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer; - h->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND); // MACH_MSG_TYPE_MAKE_SEND_ONCE - h->msgh_id = DYLD_PROCESS_INFO_NOTIFY_MAIN_ID; - h->msgh_local_port = sNotifyReplyPorts[slot]; - h->msgh_remote_port = allImageInfo->notifyPorts[slot]; - h->msgh_reserved = 0; - h->msgh_size = (mach_msg_size_t)sizeof(messageBuffer); - //dyld::log("sending to port[%d]=%d, size=%d, reply port=%d, id=0x%X\n", slot, allImageInfo->notifyPorts[slot], h->msgh_size, sNotifyReplyPorts[slot], h->msgh_id); - kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_RCV_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[slot], 2000, MACH_PORT_NULL); - //dyld::log("send result = 0x%X, msg_id=%d, msg_size=%d\n", sendResult, h->msgh_id, h->msgh_size); - if ( sendResult == MACH_SEND_INVALID_DEST ) { - // sender is not responding, detatch - //dyld::log("process requesting notification gone. deallocation send port %d and receive port %d\n", allImageInfo->notifyPorts[slot], sNotifyReplyPorts[slot]); - mach_port_deallocate(mach_task_self(), allImageInfo->notifyPorts[slot]); - mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); - allImageInfo->notifyPorts[slot] = 0; - sNotifyReplyPorts[slot] = 0; - } - else if ( sendResult == MACH_RCV_TIMED_OUT ) { - // client took too long, ignore him from now on - sZombieNotifiers[slot] = true; - mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); - sNotifyReplyPorts[slot] = 0; - } - } - } + assert(sNotifyMonitoringDyldMain != nullptr); + sNotifyMonitoringDyldMain(); } -void AllImages::notifyMonitorLoads(const launch_cache::DynArray& newImages) +void AllImages::notifyMonitorLoads(const Array& newImages) { - // notify each monitoring process - dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo(); - for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { - if ( allImageInfo->notifyPorts[slot] != 0 ) { - notifyMonitoringDyld(false, slot, newImages); - } - else if ( sNotifyReplyPorts[slot] != 0 ) { - // monitoring process detached from this process, so release reply port - //dyld::log("deallocated reply port %d\n", sNotifyReplyPorts[slot]); - mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); - sNotifyReplyPorts[slot] = 0; - sZombieNotifiers[slot] = false; - } + assert(sNotifyMonitoringDyld != nullptr); + const struct mach_header* loadAddresses[newImages.count()]; + const char* loadPaths[newImages.count()]; + for(uint32_t i = 0; ipath(); } + sNotifyMonitoringDyld(false, (unsigned)newImages.count(), loadAddresses, loadPaths); } -void AllImages::notifyMonitorUnloads(const launch_cache::DynArray& unloadingImages) +void AllImages::notifyMonitorUnloads(const Array& unloadingImages) { - // notify each monitoring process - dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo(); - for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { - if ( allImageInfo->notifyPorts[slot] != 0 ) { - notifyMonitoringDyld(true, slot, unloadingImages); - } - else if ( sNotifyReplyPorts[slot] != 0 ) { - // monitoring process detached from this process, so release reply port - //dyld::log("deallocated reply port %d\n", sNotifyReplyPorts[slot]); - mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); - sNotifyReplyPorts[slot] = 0; - sZombieNotifiers[slot] = false; - } + assert(sNotifyMonitoringDyld != nullptr); + const struct mach_header* loadAddresses[unloadingImages.count()]; + const char* loadPaths[unloadingImages.count()]; + for(uint32_t i = 0; ipath(); } + sNotifyMonitoringDyld(true, (unsigned)unloadingImages.count(), loadAddresses, loadPaths); } } // namespace dyld3