de739e6222b91fb6c488312c684dec30c5bf26dd
[apple/dyld.git] / src / dyld_process_info_notify.cpp
1 /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
2 *
3 * Copyright (c) 2016 Apple Inc. All rights reserved.
4 *
5 * @APPLE_LICENSE_HEADER_START@
6 *
7 * This file contains Original Code and/or Modifications of Original Code
8 * as defined in and that are subject to the Apple Public Source License
9 * Version 2.0 (the 'License'). You may not use this file except in
10 * compliance with the License. Please obtain a copy of the License at
11 * http://www.opensource.apple.com/apsl/ and read it before using this
12 * file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19 * Please see the License for the specific language governing rights and
20 * limitations under the License.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <limits.h>
28 #include <stdio.h>
29 #include <mach/shared_region.h>
30 #include <mach/mach_vm.h>
31 #include <libkern/OSAtomic.h>
32 #include <execinfo.h>
33 #include <mach-o/dyld_priv.h>
34 #include <mach-o/dyld_process_info.h>
35 #include <mach-o/dyld_images.h>
36
37
38 #include "dyld_process_info_internal.h"
39
40 #include "Loading.h"
41 #include "AllImages.h"
42
43 extern "C" int _dyld_func_lookup(const char* name, void** address);
44
45 typedef void (^Notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path);
46 typedef void (^NotifyExit)();
47 typedef void (^NotifyMain)();
48
49 //
50 // Object used for monitoring another processes dyld loads
51 //
52 struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base
53 {
54 dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task, kern_return_t* kr);
55 ~dyld_process_info_notify_base();
56 bool enabled() const;
57 void retain();
58 void release();
59
60 void setNotifyMain(NotifyMain notifyMain) const { _notifyMain = notifyMain; }
61
62 // override new and delete so we don't need to link with libc++
63 static void* operator new(size_t sz) { return malloc(sz); }
64 static void operator delete(void* p) { free(p); }
65
66 private:
67 void handleEvent();
68 void teardown();
69 void replyToMonitoredProcess(mach_msg_header_t& header);
70
71 RemoteBuffer _remoteAllImageInfoBuffer;
72 uint32_t* _notifyMachPorts;
73 uint32_t _notifySlot;
74 mutable std::atomic<int32_t> _retainCount;
75 dispatch_queue_t _queue;
76 Notify _notify;
77 NotifyExit _notifyExit;
78 mutable NotifyMain _notifyMain;
79 task_t _targetTask;
80 dispatch_source_t _machSource;
81 mach_port_t _sendPortInTarget; // target is process being watched for image loading/unloading
82 mach_port_t _receivePortInMonitor; // monitor is process being notified of image loading/unloading
83 std::atomic<bool> _disabled;
84 };
85
86
87 dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit,
88 task_t task, kern_return_t* kr) :
89 _notifyMachPorts(nullptr), _notifySlot(0), _retainCount(1), _queue(queue), _notify(notify), _notifyExit(notifyExit),
90 _notifyMain(nullptr), _targetTask(task), _machSource(nullptr), _sendPortInTarget(0), _receivePortInMonitor(0),
91 _disabled(false)
92 {
93 assert(_disabled == false);
94 dispatch_retain(_queue);
95 // Allocate a port to listen on in this monitoring task
96 mach_port_options_t options = { .flags = MPO_IMPORTANCE_RECEIVER | MPO_CONTEXT_AS_GUARD | MPO_STRICT,
97 .mpl = { MACH_PORT_QLIMIT_DEFAULT }};
98 if ((*kr = mach_port_construct(mach_task_self(), &options, (mach_port_context_t)this, &_receivePortInMonitor))) {
99 teardown();
100 return;
101 }
102 if (_targetTask == mach_task_self()) {
103 _sendPortInTarget = _receivePortInMonitor;
104 (void)mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND);
105 } else {
106 // Insert a deadname right into the port to trigger notifications
107 kern_return_t r = KERN_NAME_EXISTS;
108 while (r == KERN_NAME_EXISTS) {
109 _sendPortInTarget = MACH_PORT_NULL;
110 //FIXME file radar
111 r = mach_port_allocate(_targetTask, MACH_PORT_RIGHT_DEAD_NAME, &_sendPortInTarget);
112 if (r != KERN_SUCCESS) {
113 *kr = r;
114 return;
115 }
116 (void)mach_port_deallocate(_targetTask, _sendPortInTarget);
117 r = mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND);
118 }
119 if (r != KERN_SUCCESS) {
120 *kr = r;
121 return;
122 }
123
124 // Notify us if the target dies
125 mach_port_t previous = MACH_PORT_NULL;
126 if ((*kr = mach_port_request_notification(_targetTask, _sendPortInTarget, MACH_NOTIFY_DEAD_NAME, 0, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous))) {
127 (void)mach_port_deallocate(_targetTask, _sendPortInTarget);
128 (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this);
129 teardown();
130 return;
131 }
132 // This is a new port, if there is a previous notifier attached then something is wrong... abort.
133 if (previous != MACH_PORT_NULL) {
134 (void)mach_port_deallocate(mach_task_self(), previous);
135 (void)mach_port_deallocate(_targetTask, _sendPortInTarget);
136 (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this);
137 teardown();
138 return;
139 }
140 }
141
142 // Setup the event handler for the port
143 _machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _receivePortInMonitor, 0, _queue);
144 if (_machSource == nullptr) {
145 (void)mach_port_deallocate(_targetTask, _sendPortInTarget);
146 (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this);
147 teardown();
148 return;
149 }
150 dispatch_source_set_event_handler(_machSource, ^{
151 handleEvent();
152 });
153 dispatch_source_set_cancel_handler(_machSource, ^{
154 if ( _receivePortInMonitor != 0 ) {
155 (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this);
156 _receivePortInMonitor = 0;
157 }
158 });
159 dispatch_activate(_machSource);
160
161 // get location on all_image_infos in the target task
162 task_dyld_info_data_t taskDyldInfo;
163 mach_msg_type_number_t taskDyldInfoCount = TASK_DYLD_INFO_COUNT;
164 if ((*kr = task_info(_targetTask, TASK_DYLD_INFO, (task_info_t)&taskDyldInfo, &taskDyldInfoCount))) {
165 (void)mach_port_deallocate(_targetTask, _sendPortInTarget);
166 teardown();
167 return;
168 }
169 // Poke the portname of our port into the target task
170 _remoteAllImageInfoBuffer = RemoteBuffer(_targetTask, taskDyldInfo.all_image_info_addr, (size_t)taskDyldInfo.all_image_info_size, true, false);
171 *kr = _remoteAllImageInfoBuffer.getKernelReturn();
172 if (*kr) {
173 (void)mach_port_deallocate(_targetTask, _sendPortInTarget);
174 teardown();
175 return;
176 }
177
178 static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits");
179 if ( taskDyldInfo.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) {
180 _notifyMachPorts = (uint32_t *)((uint8_t *)_remoteAllImageInfoBuffer.getLocalAddress() + offsetof(dyld_all_image_infos_32,notifyMachPorts));
181 } else {
182 _notifyMachPorts = (uint32_t *)((uint8_t *)_remoteAllImageInfoBuffer.getLocalAddress() + offsetof(dyld_all_image_infos_64,notifyMachPorts));
183 }
184
185 #if 0
186 //If all the slots are filled we will sleep and retry a few times before giving up
187 for (uint32_t i=0; i<10; ++i) {
188 for (_notifySlot=0; _notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++_notifySlot) {
189 if (OSAtomicCompareAndSwap32(0, _sendPortInTarget, (volatile int32_t*)&_notifyMachPorts[_notifySlot])) {
190 break;
191 }
192 }
193 if (_notifySlot == DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT) {
194 // all the slots are filled, sleep and try again
195 usleep(1000 * 50); // 50ms
196 } else {
197 // if _notifySlot is set we are done
198 break;
199 }
200 }
201 #else
202 for (_notifySlot=0; _notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++_notifySlot) {
203 if (OSAtomicCompareAndSwap32(0, _sendPortInTarget, (volatile int32_t*)&_notifyMachPorts[_notifySlot])) {
204 break;
205 }
206 }
207 #endif
208
209 if (_notifySlot == DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT) {
210 (void)mach_port_deallocate(_targetTask, _sendPortInTarget);
211 teardown();
212 *kr = KERN_UREFS_OVERFLOW;
213 return;
214 }
215
216 *kr = KERN_SUCCESS;
217 }
218
219 dyld_process_info_notify_base::~dyld_process_info_notify_base() {
220 if (!_disabled) {
221 fprintf(stderr, "dyld: ~dyld_process_info_notify_base called while still enabled\n");
222 }
223 dispatch_release(_queue);
224 }
225
226 void dyld_process_info_notify_base::teardown() {
227 if (!_disabled) {
228 _disabled = true;
229 // The connection to the target is dead. Clean up ports
230 if ( _remoteAllImageInfoBuffer.getLocalAddress() != 0 && _notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT) {
231 mach_port_t extractedPort = MACH_PORT_NULL;
232 mach_msg_type_name_t extractedPortType;
233 kern_return_t kr = mach_port_extract_right(_targetTask, _sendPortInTarget, MACH_MSG_TYPE_COPY_SEND, &extractedPort, &extractedPortType);
234 if (kr == KERN_SUCCESS) {
235 if (extractedPort == _receivePortInMonitor) {
236 if (OSAtomicCompareAndSwap32(_sendPortInTarget, 0, (volatile int32_t*)&_notifyMachPorts[_notifySlot])) {
237 (void)mach_port_deallocate(_targetTask, _sendPortInTarget);
238 }
239 }
240 (void)mach_port_deallocate(mach_task_self(), extractedPort);
241 }
242 }
243 _sendPortInTarget = 0;
244 if ( _machSource ) {
245 dispatch_source_cancel(_machSource);
246 dispatch_release(_machSource);
247 _machSource = NULL;
248 }
249 if (_notifyExit) {
250 dispatch_async(_queue, ^{
251 _notifyExit();
252 });
253 }
254 }
255 }
256
257 bool dyld_process_info_notify_base::enabled() const
258 {
259 return !_disabled;
260 }
261
262 void dyld_process_info_notify_base::retain()
263 {
264 _retainCount++;
265 }
266
267 void dyld_process_info_notify_base::release()
268 {
269 uint32_t newCount = --_retainCount;
270
271 if ( newCount == 0 ) {
272 teardown();
273 }
274 dispatch_async(_queue, ^{
275 delete this;
276 });
277 }
278
279 void dyld_process_info_notify_base::replyToMonitoredProcess(mach_msg_header_t& header) {
280 mach_msg_header_t replyHeader;
281 replyHeader.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSGH_BITS_REMOTE(header.msgh_bits), 0, 0, 0);
282 replyHeader.msgh_id = 0;
283 replyHeader.msgh_local_port = MACH_PORT_NULL;
284 replyHeader.msgh_remote_port = header.msgh_remote_port;
285 replyHeader.msgh_reserved = 0;
286 replyHeader.msgh_size = sizeof(replyHeader);
287 kern_return_t r = mach_msg(&replyHeader, MACH_SEND_MSG, replyHeader.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
288 if (r == KERN_SUCCESS) {
289 header.msgh_remote_port = MACH_PORT_NULL;
290 } else {
291 teardown();
292 }
293 }
294
295 void dyld_process_info_notify_base::handleEvent() {
296 // References object may still exist even after the ports are dead. Disable event dispatching
297 // if the ports have been torn down.
298 if (_disabled) {
299 return;
300 }
301 // This event handler block has an implicit reference to "this"
302 // if incrementing the count goes to one, that means the object may have already been destroyed
303 uint8_t messageBuffer[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE] = {};
304 mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer;
305
306 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);
307 if ( r == KERN_SUCCESS && !(h->msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
308 //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
309
310 if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_LOAD_ID || h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID ) {
311 // run notifier block for each [un]load image
312 const dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)messageBuffer;
313 if (sizeof(*header) <= h->msgh_size
314 && header->imagesOffset <= h->msgh_size
315 && header->stringsOffset <= h->msgh_size
316 && (header->imageCount * sizeof(dyld_process_info_image_entry)) <= (h->msgh_size - header->imagesOffset)) {
317 const dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&messageBuffer[header->imagesOffset];
318 const char* const stringPool = (char*)&messageBuffer[header->stringsOffset];
319 for (unsigned i=0; i < header->imageCount; ++i) {
320 bool isUnload = (h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID);
321 if (entries[i].pathStringOffset <= h->msgh_size - header->stringsOffset) {
322 //fprintf(stderr, "Notifying about: %s\n", stringPool + entries[i].pathStringOffset);
323 _notify(isUnload, header->timestamp, entries[i].loadAddress, entries[i].uuid, stringPool + entries[i].pathStringOffset);
324 } else {
325 teardown();
326 break;
327 }
328 }
329 // reply to dyld, so it can continue
330 replyToMonitoredProcess(*h);
331 } else {
332 teardown();
333 }
334 }
335 else if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_MAIN_ID ) {
336 if (h->msgh_size != sizeof(mach_msg_header_t)) {
337 teardown();
338 } else if ( _notifyMain != NULL ) {
339 _notifyMain();
340 }
341 replyToMonitoredProcess(*h);
342 } else if ( h->msgh_id == MACH_NOTIFY_PORT_DELETED ) {
343 mach_port_t deadPort = ((mach_port_deleted_notification_t *)h)->not_port;
344 // Validate this notification came from the kernel
345 const mach_msg_audit_trailer_t *audit_tlr = (mach_msg_audit_trailer_t *)((uint8_t *)h + round_msg(h->msgh_size));
346 if (audit_tlr->msgh_trailer_type == MACH_MSG_TRAILER_FORMAT_0
347 && audit_tlr->msgh_trailer_size >= sizeof(mach_msg_audit_trailer_t)
348 // We cannot link to libbsm, so we are hardcoding the audit token offset (5)
349 // And the value the represents the kernel (0)
350 && audit_tlr->msgh_audit.val[5] == 0
351 && deadPort == _sendPortInTarget ) {
352 teardown();
353 }
354 }
355 else {
356 fprintf(stderr, "dyld: received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
357 }
358 }
359 mach_msg_destroy(h);
360 }
361
362 dyld_process_info_notify _dyld_process_info_notify(task_t task, dispatch_queue_t queue,
363 void (^notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path),
364 void (^notifyExit)(),
365 kern_return_t* kr)
366 {
367 kern_return_t krSink = KERN_SUCCESS;
368 if (kr == nullptr) {
369 kr = &krSink;
370 }
371 *kr = KERN_SUCCESS;
372
373 dyld_process_info_notify result = new dyld_process_info_notify_base(queue, notify, notifyExit, task, kr);
374 if (result->enabled())
375 return result;
376 const_cast<dyld_process_info_notify_base*>(result)->release();
377 return nullptr;
378 }
379
380 void _dyld_process_info_notify_main(dyld_process_info_notify object, void (^notifyMain)())
381 {
382 object->setNotifyMain(notifyMain);
383 }
384
385 void _dyld_process_info_notify_retain(dyld_process_info_notify object)
386 {
387 const_cast<dyld_process_info_notify_base*>(object)->retain();
388 }
389
390 void _dyld_process_info_notify_release(dyld_process_info_notify object)
391 {
392 const_cast<dyld_process_info_notify_base*>(object)->release();
393 }
394
395 static void (*sNotifyMonitoringDyldMain)() = nullptr;
396 static void (*sNotifyMonitoringDyld)(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[],
397 const char* imagePaths[]) = nullptr;
398
399 void setNotifyMonitoringDyldMain(void (*func)())
400 {
401 sNotifyMonitoringDyldMain = func;
402 }
403
404 void setNotifyMonitoringDyld(void (*func)(bool unloading, unsigned imageCount,
405 const struct mach_header* loadAddresses[],
406 const char* imagePaths[]))
407 {
408 sNotifyMonitoringDyld = func;
409 }
410
411 namespace dyld3 {
412
413 void AllImages::notifyMonitorMain()
414 {
415 #if !TARGET_OS_DRIVERKIT
416 assert(sNotifyMonitoringDyldMain != nullptr);
417 sNotifyMonitoringDyldMain();
418 #endif
419 }
420
421 void AllImages::notifyMonitorLoads(const Array<LoadedImage>& newImages)
422 {
423 #if !TARGET_OS_DRIVERKIT
424 assert(sNotifyMonitoringDyld != nullptr);
425 const struct mach_header* loadAddresses[newImages.count()];
426 const char* loadPaths[newImages.count()];
427 for(uint32_t i = 0; i<newImages.count(); ++i) {
428 loadAddresses[i] = newImages[i].loadedAddress();
429 loadPaths[i] = newImages[i].image()->path();
430 }
431 sNotifyMonitoringDyld(false, (unsigned)newImages.count(), loadAddresses, loadPaths);
432 #endif
433 }
434
435 void AllImages::notifyMonitorUnloads(const Array<LoadedImage>& unloadingImages)
436 {
437 #if !TARGET_OS_DRIVERKIT
438 assert(sNotifyMonitoringDyld != nullptr);
439 const struct mach_header* loadAddresses[unloadingImages.count()];
440 const char* loadPaths[unloadingImages.count()];
441 for(uint32_t i = 0; i<unloadingImages.count(); ++i) {
442 loadAddresses[i] = unloadingImages[i].loadedAddress();
443 loadPaths[i] = unloadingImages[i].image()->path();
444 }
445 sNotifyMonitoringDyld(true, (unsigned)unloadingImages.count(), loadAddresses, loadPaths);
446 #endif
447 }
448
449 } // namespace dyld3
450
451
452
453