1 | //===-- FDInterposing.cpp ---------------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file helps with catching double close calls on unix integer file |
10 | // descriptors by interposing functions for all file descriptor create and |
11 | // close operations. A stack backtrace for every create and close function is |
12 | // maintained, and every create and close operation is logged. When a double |
13 | // file descriptor close is encountered, it will be logged. |
14 | // |
15 | // To enable the interposing in a darwin program, set the DYLD_INSERT_LIBRARIES |
16 | // environment variable as follows: |
17 | // For sh: |
18 | // DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib /path/to/executable |
19 | // For tcsh: |
20 | // (setenv DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib ; |
21 | // /path/to/executable) |
22 | // |
23 | // Other environment variables that can alter the default actions of this |
24 | // interposing shared library include: |
25 | // |
26 | // "FileDescriptorStackLoggingNoCompact" |
27 | // |
28 | // With this environment variable set, all file descriptor create and |
29 | // delete operations will be permanantly maintained in the event map. |
30 | // The default action is to compact the create/delete events by removing |
31 | // any previous file descriptor create events that are matched with a |
32 | // corresponding file descriptor delete event when the next valid file |
33 | // descriptor create event is detected. |
34 | // |
35 | // "FileDescriptorMinimalLogging" |
36 | // |
37 | // By default every file descriptor create and delete operation is logged |
38 | // (to STDOUT by default, see the "FileDescriptorLogFile"). This can be |
39 | // suppressed to only show errors and warnings by setting this environment |
40 | // variable (the value in not important). |
41 | // |
42 | // "FileDescriptorLogFile=<path>" |
43 | // |
44 | // By default logging goes to STDOUT_FILENO, but this can be changed by |
45 | // setting FileDescriptorLogFile. The value is a path to a file that |
46 | // will be opened and used for logging. |
47 | //===----------------------------------------------------------------------===// |
48 | |
49 | #include <assert.h> |
50 | #include <dirent.h> |
51 | #include <errno.h> |
52 | #include <execinfo.h> |
53 | #include <fcntl.h> |
54 | #include <libgen.h> |
55 | #include <mach-o/dyld-interposing.h> |
56 | #include <mach-o/dyld.h> |
57 | #include <map> |
58 | #include <stdio.h> |
59 | #include <stdlib.h> |
60 | #include <string.h> |
61 | #include <string> |
62 | #include <sys/event.h> |
63 | #include <sys/mman.h> |
64 | #include <sys/socket.h> |
65 | #include <sys/time.h> |
66 | #include <sys/types.h> |
67 | #include <tr1/memory> |
68 | #include <unistd.h> |
69 | #include <vector> |
70 | |
71 | extern "C" { |
72 | int accept$NOCANCEL(int, struct sockaddr *__restrict, socklen_t *__restrict); |
73 | int close$NOCANCEL(int); |
74 | int open$NOCANCEL(const char *, int, ...); |
75 | int __open_extended(const char *, int, uid_t, gid_t, int, |
76 | struct kauth_filesec *); |
77 | } |
78 | |
79 | namespace fd_interposing { |
80 | |
81 | // String class so we can get formatted strings without having to worry |
82 | // about the memory storage since it will allocate the memory it needs. |
83 | class String { |
84 | public: |
85 | String() : m_str(NULL) {} |
86 | |
87 | String(const char *format, ...) : m_str(NULL) { |
88 | va_list args; |
89 | va_start(args, format); |
90 | vprintf(format, args); |
91 | va_end(args); |
92 | } |
93 | |
94 | ~String() { reset(); } |
95 | |
96 | void reset(char *s = NULL) { |
97 | if (m_str) |
98 | ::free(ptr: m_str); |
99 | m_str = s; |
100 | } |
101 | |
102 | const char *c_str() const { return m_str; } |
103 | |
104 | void printf(const char *format, ...) { |
105 | va_list args; |
106 | va_start(args, format); |
107 | vprintf(format, args); |
108 | va_end(args); |
109 | } |
110 | void vprintf(const char *format, va_list args) { |
111 | reset(); |
112 | ::vasprintf(ptr: &m_str, f: format, arg: args); |
113 | } |
114 | |
115 | void log(int log_fd) { |
116 | if (m_str && log_fd >= 0) { |
117 | const int len = strlen(s: m_str); |
118 | if (len > 0) { |
119 | write(fd: log_fd, buf: m_str, n: len); |
120 | const char last_char = m_str[len - 1]; |
121 | if (!(last_char == '\n' || last_char == '\r')) |
122 | write(fd: log_fd, buf: "\n" , n: 1); |
123 | } |
124 | } |
125 | } |
126 | |
127 | protected: |
128 | char *m_str; |
129 | |
130 | private: |
131 | String(const String &) = delete; |
132 | const String &operator=(const String &) = delete; |
133 | }; |
134 | |
135 | // Type definitions |
136 | typedef std::vector<void *> Frames; |
137 | class FDEvent; |
138 | typedef std::vector<void *> Frames; |
139 | typedef std::tr1::shared_ptr<FDEvent> FDEventSP; |
140 | typedef std::tr1::shared_ptr<String> StringSP; |
141 | |
142 | // FDEvent |
143 | // |
144 | // A class that describes a file descriptor event. |
145 | // |
146 | // File descriptor events fall into one of two categories: create events |
147 | // and delete events. |
148 | class FDEvent { |
149 | public: |
150 | FDEvent(int fd, int err, const StringSP &string_sp, bool is_create, |
151 | const Frames &frames) |
152 | : m_string_sp(string_sp), m_frames(frames.begin(), frames.end()), |
153 | m_fd(fd), m_err(err), m_is_create(is_create) {} |
154 | |
155 | ~FDEvent() {} |
156 | |
157 | bool IsCreateEvent() const { return m_is_create; } |
158 | |
159 | bool IsDeleteEvent() const { return !m_is_create; } |
160 | |
161 | Frames &GetFrames() { return m_frames; } |
162 | |
163 | const Frames &GetFrames() const { return m_frames; } |
164 | |
165 | int GetFD() const { return m_fd; } |
166 | |
167 | int GetError() const { return m_err; } |
168 | |
169 | void Dump(int log_fd) const; |
170 | |
171 | void SetCreateEvent(FDEventSP &create_event_sp) { |
172 | m_create_event_sp = create_event_sp; |
173 | } |
174 | |
175 | private: |
176 | // A shared pointer to a String that describes this event in |
177 | // detail (all args and return and error values) |
178 | StringSP m_string_sp; |
179 | // The frames for the stack backtrace for this event |
180 | Frames m_frames; |
181 | // If this is a file descriptor delete event, this might contain |
182 | // the corresponding file descriptor create event |
183 | FDEventSP m_create_event_sp; |
184 | // The file descriptor for this event |
185 | int m_fd; |
186 | // The error code (if any) for this event |
187 | int m_err; |
188 | // True if this event is a file descriptor create event, false |
189 | // if it is a file descriptor delete event |
190 | bool m_is_create; |
191 | }; |
192 | |
193 | // Templatized class that will save errno only if the "value" it is |
194 | // constructed with is equal to INVALID. When the class goes out of |
195 | // scope, it will restore errno if it was saved. |
196 | template <int INVALID> class Errno { |
197 | public: |
198 | // Save errno only if we are supposed to |
199 | Errno(int value) |
200 | : m_saved_errno((value == INVALID) ? errno : 0), |
201 | m_restore(value == INVALID) {} |
202 | |
203 | // Restore errno only if we are supposed to |
204 | ~Errno() { |
205 | if (m_restore) |
206 | errno = m_saved_errno; |
207 | } |
208 | |
209 | // Accessor for the saved value of errno |
210 | int get_errno() const { return m_saved_errno; } |
211 | |
212 | protected: |
213 | const int m_saved_errno; |
214 | const bool m_restore; |
215 | }; |
216 | |
217 | typedef Errno<-1> InvalidFDErrno; |
218 | typedef Errno<-1> NegativeErrorErrno; |
219 | typedef std::vector<FDEventSP> FDEventArray; |
220 | typedef std::map<int, FDEventArray> FDEventMap; |
221 | |
222 | // Globals |
223 | // Global event map that contains all file descriptor events. As file |
224 | // descriptor create and close events come in, they will get filled |
225 | // into this map (protected by g_mutex). When a file descriptor close |
226 | // event is detected, the open event will be removed and placed into |
227 | // the close event so if something tries to double close a file |
228 | // descriptor we can show the previous close event and the file |
229 | // descriptor event that created it. When a new file descriptor create |
230 | // event comes in, we will remove the previous one for that file |
231 | // descriptor unless the environment variable |
232 | // "FileDescriptorStackLoggingNoCompact" |
233 | // is set. The file descriptor history can be accessed using the |
234 | // get_fd_history() function. |
235 | static FDEventMap g_fd_event_map; |
236 | // A mutex to protect access to our data structures in g_fd_event_map |
237 | // and also our logging messages |
238 | static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; |
239 | // Log all file descriptor create and close events by default. Only log |
240 | // warnings and errors if the "FileDescriptorMinimalLogging" environment |
241 | // variable is set. |
242 | static int g_log_all_calls = 1; |
243 | // We compact the file descriptor events by default. Set the environment |
244 | // varible "FileDescriptorStackLoggingNoCompact" to keep a full history. |
245 | static int g_compact = 1; |
246 | // The current process ID |
247 | static int g_pid = -1; |
248 | static bool g_enabled = true; |
249 | // Mutex class that will lock a mutex when it is constructed, and unlock |
250 | // it when is goes out of scope |
251 | class Locker { |
252 | public: |
253 | Locker(pthread_mutex_t *mutex_ptr) : m_mutex_ptr(mutex_ptr) { |
254 | ::pthread_mutex_lock(mutex: m_mutex_ptr); |
255 | } |
256 | |
257 | // This allows clients to test try and acquire the mutex... |
258 | Locker(pthread_mutex_t *mutex_ptr, bool &lock_acquired) : m_mutex_ptr(NULL) { |
259 | lock_acquired = ::pthread_mutex_trylock(mutex: mutex_ptr) == 0; |
260 | if (lock_acquired) |
261 | m_mutex_ptr = mutex_ptr; |
262 | } |
263 | |
264 | ~Locker() { |
265 | if (m_mutex_ptr) |
266 | ::pthread_mutex_unlock(mutex: m_mutex_ptr); |
267 | } |
268 | |
269 | protected: |
270 | pthread_mutex_t *m_mutex_ptr; |
271 | }; |
272 | |
273 | static void log(const char *format, ...) __attribute__((format(printf, 1, 2))); |
274 | |
275 | static void log(int log_fd, const FDEvent *event, const char *format, ...) |
276 | __attribute__((format(printf, 3, 4))); |
277 | |
278 | static void backtrace_log(const char *format, ...) |
279 | __attribute__((format(printf, 1, 2))); |
280 | |
281 | static void backtrace_error(const char *format, ...) |
282 | __attribute__((format(printf, 1, 2))); |
283 | |
284 | static void log_to_fd(int log_fd, const char *format, ...) |
285 | __attribute__((format(printf, 2, 3))); |
286 | |
287 | static inline size_t get_backtrace(Frames &frame_buffer, |
288 | size_t frames_to_remove) { |
289 | void *frames[2048]; |
290 | int count = ::backtrace(array: &frames[0], size: sizeof(frames) / sizeof(void *)); |
291 | if (count > frames_to_remove) |
292 | frame_buffer.assign(first: &frames[frames_to_remove], last: &frames[count]); |
293 | else |
294 | frame_buffer.assign(first: &frames[0], last: &frames[count]); |
295 | while (frame_buffer.back() < (void *)1024) |
296 | frame_buffer.pop_back(); |
297 | return frame_buffer.size(); |
298 | } |
299 | |
300 | static int g_log_fd = STDOUT_FILENO; |
301 | static int g_initialized = 0; |
302 | |
303 | const char *get_process_fullpath(bool force = false) { |
304 | static char g_process_fullpath[PATH_MAX] = {0}; |
305 | if (force || g_process_fullpath[0] == '\0') { |
306 | // If DST is NULL, then return the number of bytes needed. |
307 | uint32_t len = sizeof(g_process_fullpath); |
308 | if (_NSGetExecutablePath(g_process_fullpath, &len) != 0) |
309 | strncpy(dest: g_process_fullpath, src: "<error>" , n: sizeof(g_process_fullpath)); |
310 | } |
311 | return g_process_fullpath; |
312 | } |
313 | |
314 | // Returns the current process ID, or -1 if inserposing not enabled for |
315 | // this process |
316 | static int get_interposed_pid() { |
317 | if (!g_enabled) |
318 | return -1; |
319 | |
320 | const pid_t pid = getpid(); |
321 | if (g_pid != pid) { |
322 | if (g_pid == -1) { |
323 | g_pid = pid; |
324 | log(format: "Interposing file descriptor create and delete functions for %s " |
325 | "(pid=%i)\n" , |
326 | get_process_fullpath(force: true), pid); |
327 | } else { |
328 | log(format: "pid=%i: disabling interposing file descriptor create and delete " |
329 | "functions for child process %s (pid=%i)\n" , |
330 | g_pid, get_process_fullpath(force: true), pid); |
331 | g_enabled = false; |
332 | return -1; |
333 | } |
334 | // Log when our process changes |
335 | } |
336 | return g_pid; |
337 | } |
338 | |
339 | static int get_logging_fd() { |
340 | if (!g_enabled) |
341 | return -1; |
342 | |
343 | if (!g_initialized) { |
344 | g_initialized = 1; |
345 | |
346 | const pid_t pid = get_interposed_pid(); |
347 | |
348 | if (g_enabled) { |
349 | // Keep all stack info around for all fd create and delete calls. |
350 | // Otherwise we will remove the fd create call when a corresponding |
351 | // fd delete call is received |
352 | if (getenv(name: "FileDescriptorStackLoggingNoCompact" )) |
353 | g_compact = 0; |
354 | |
355 | if (getenv(name: "FileDescriptorMinimalLogging" )) |
356 | g_log_all_calls = 0; |
357 | |
358 | const char *log_path = getenv(name: "FileDescriptorLogFile" ); |
359 | if (log_path) |
360 | g_log_fd = ::creat(file: log_path, mode: 0660); |
361 | else |
362 | g_log_fd = STDOUT_FILENO; |
363 | |
364 | // Only let this interposing happen on the first time this matches |
365 | // and stop this from happening so any child processes don't also |
366 | // log their file descriptors |
367 | ::unsetenv(name: "DYLD_INSERT_LIBRARIES" ); |
368 | } else { |
369 | log(format: "pid=%i: logging disabled\n" , getpid()); |
370 | } |
371 | } |
372 | return g_log_fd; |
373 | } |
374 | |
375 | void log_to_fd(int log_fd, const char *format, va_list args) { |
376 | if (format && format[0] && log_fd >= 0) { |
377 | char buffer[PATH_MAX]; |
378 | const int count = ::vsnprintf(s: buffer, maxlen: sizeof(buffer), format: format, arg: args); |
379 | if (count > 0) |
380 | write(fd: log_fd, buf: buffer, n: count); |
381 | } |
382 | } |
383 | |
384 | void log_to_fd(int log_fd, const char *format, ...) { |
385 | if (format && format[0]) { |
386 | va_list args; |
387 | va_start(args, format); |
388 | log_to_fd(log_fd, format, args); |
389 | va_end(args); |
390 | } |
391 | } |
392 | |
393 | void log(const char *format, va_list args) { |
394 | log_to_fd(log_fd: get_logging_fd(), format, args); |
395 | } |
396 | |
397 | void log(const char *format, ...) { |
398 | if (format && format[0]) { |
399 | va_list args; |
400 | va_start(args, format); |
401 | log(format, args); |
402 | va_end(args); |
403 | } |
404 | } |
405 | |
406 | void log(int log_fd, const FDEvent *event, const char *format, ...) { |
407 | if (format && format[0]) { |
408 | va_list args; |
409 | va_start(args, format); |
410 | log_to_fd(log_fd, format, args); |
411 | va_end(args); |
412 | } |
413 | if (event) |
414 | event->Dump(log_fd); |
415 | } |
416 | |
417 | void FDEvent::Dump(int log_fd) const { |
418 | if (log_fd >= 0) { |
419 | log_to_fd(log_fd, format: "%s\n" , m_string_sp->c_str()); |
420 | if (!m_frames.empty()) |
421 | ::backtrace_symbols_fd(array: m_frames.data(), size: m_frames.size(), fd: log_fd); |
422 | |
423 | if (m_create_event_sp) { |
424 | log_to_fd(log_fd, format: "\nfd=%i was created with this event:\n" , m_fd); |
425 | m_create_event_sp->Dump(log_fd); |
426 | log_to_fd(log_fd, format: "\n" ); |
427 | } |
428 | } |
429 | } |
430 | |
431 | void backtrace_log(const char *format, ...) { |
432 | const int log_fd = get_logging_fd(); |
433 | if (log_fd >= 0) { |
434 | if (format && format[0]) { |
435 | va_list args; |
436 | va_start(args, format); |
437 | log(format, args); |
438 | va_end(args); |
439 | } |
440 | |
441 | Frames frames; |
442 | if (get_backtrace(frame_buffer&: frames, frames_to_remove: 2)) |
443 | ::backtrace_symbols_fd(array: frames.data(), size: frames.size(), fd: log_fd); |
444 | } |
445 | } |
446 | |
447 | void backtrace_error(const char *format, ...) { |
448 | const int pid = get_interposed_pid(); |
449 | if (pid >= 0) { |
450 | const int log_fd = get_logging_fd(); |
451 | if (log_fd >= 0) { |
452 | log(format: "\nerror: %s (pid=%i): " , get_process_fullpath(), pid); |
453 | |
454 | if (format && format[0]) { |
455 | va_list args; |
456 | va_start(args, format); |
457 | log(format, args); |
458 | va_end(args); |
459 | } |
460 | |
461 | Frames frames; |
462 | if (get_backtrace(frame_buffer&: frames, frames_to_remove: 2)) |
463 | ::backtrace_symbols_fd(array: frames.data(), size: frames.size(), fd: log_fd); |
464 | } |
465 | } |
466 | } |
467 | |
468 | void save_backtrace(int fd, int err, const StringSP &string_sp, |
469 | bool is_create) { |
470 | Frames frames; |
471 | get_backtrace(frame_buffer&: frames, frames_to_remove: 2); |
472 | |
473 | FDEventSP fd_event_sp(new FDEvent(fd, err, string_sp, is_create, frames)); |
474 | |
475 | FDEventMap::iterator pos = g_fd_event_map.find(x: fd); |
476 | |
477 | if (pos != g_fd_event_map.end()) { |
478 | // We have history for this fd... |
479 | |
480 | FDEventArray &event_array = g_fd_event_map[fd]; |
481 | if (fd_event_sp->IsCreateEvent()) { |
482 | // The current fd event is a function that creates |
483 | // a descriptor, check in case last event was |
484 | // a create event. |
485 | if (event_array.back()->IsCreateEvent()) { |
486 | const int log_fd = get_logging_fd(); |
487 | // Two fd create functions in a row, we missed |
488 | // a function that closes a fd... |
489 | log(log_fd, event: fd_event_sp.get(), format: "\nwarning: unmatched file descriptor " |
490 | "create event fd=%i (we missed a file " |
491 | "descriptor close event):\n" , |
492 | fd); |
493 | } else if (g_compact) { |
494 | // We are compacting so we remove previous create event |
495 | // when we get the corresponding delete event |
496 | event_array.pop_back(); |
497 | } |
498 | } else { |
499 | // The current fd event is a function that deletes |
500 | // a descriptor, check in case last event for this |
501 | // fd was a delete event (double close!) |
502 | if (event_array.back()->IsDeleteEvent()) { |
503 | const int log_fd = get_logging_fd(); |
504 | // Two fd delete functions in a row, we must |
505 | // have missed some function that opened a descriptor |
506 | log(log_fd, event: fd_event_sp.get(), format: "\nwarning: unmatched file descriptor " |
507 | "close event for fd=%d (we missed the " |
508 | "file descriptor create event):\n" , |
509 | fd); |
510 | } else if (g_compact) { |
511 | // Since this is a close event, we want to remember the open event |
512 | // that this close if for... |
513 | fd_event_sp->SetCreateEvent(event_array.back()); |
514 | // We are compacting so we remove previous create event |
515 | // when we get the corresponding delete event |
516 | event_array.pop_back(); |
517 | } |
518 | } |
519 | |
520 | event_array.push_back(x: fd_event_sp); |
521 | } else { |
522 | g_fd_event_map[fd].push_back(x: fd_event_sp); |
523 | } |
524 | } |
525 | |
526 | // socket() interpose function |
527 | extern "C" int socket$__interposed__(int domain, int type, int protocol) { |
528 | const int pid = get_interposed_pid(); |
529 | if (pid >= 0) { |
530 | Locker locker(&g_mutex); |
531 | const int fd = ::socket(domain: domain, type: type, protocol: protocol); |
532 | InvalidFDErrno fd_errno(fd); |
533 | StringSP description_sp(new String); |
534 | if (fd == -1) |
535 | description_sp->printf(format: "pid=%i: socket (domain = %i, type = %i, protocol " |
536 | "= %i) => fd=%i errno = %i" , |
537 | pid, domain, type, protocol, fd, |
538 | fd_errno.get_errno()); |
539 | else |
540 | description_sp->printf( |
541 | format: "pid=%i: socket (domain = %i, type = %i, protocol = %i) => fd=%i" , |
542 | pid, domain, type, protocol, fd); |
543 | if (g_log_all_calls) |
544 | description_sp->log(log_fd: get_logging_fd()); |
545 | if (fd >= 0) |
546 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
547 | return fd; |
548 | } else { |
549 | return ::socket(domain: domain, type: type, protocol: protocol); |
550 | } |
551 | } |
552 | |
553 | // socketpair() interpose function |
554 | extern "C" int socketpair$__interposed__(int domain, int type, int protocol, |
555 | int fds[2]) { |
556 | const int pid = get_interposed_pid(); |
557 | if (pid >= 0) { |
558 | Locker locker(&g_mutex); |
559 | fds[0] = -1; |
560 | fds[1] = -1; |
561 | const int err = socketpair(domain: domain, type: type, protocol: protocol, fds: fds); |
562 | NegativeErrorErrno err_errno(err); |
563 | StringSP description_sp( |
564 | new String("pid=%i: socketpair (domain=%i, type=%i, protocol=%i, " |
565 | "{fd=%i, fd=%i}) -> err=%i" , |
566 | pid, domain, type, protocol, fds[0], fds[1], err)); |
567 | if (g_log_all_calls) |
568 | description_sp->log(log_fd: get_logging_fd()); |
569 | if (fds[0] >= 0) |
570 | save_backtrace(fd: fds[0], err: err_errno.get_errno(), string_sp: description_sp, is_create: true); |
571 | if (fds[1] >= 0) |
572 | save_backtrace(fd: fds[1], err: err_errno.get_errno(), string_sp: description_sp, is_create: true); |
573 | return err; |
574 | } else { |
575 | return socketpair(domain: domain, type: type, protocol: protocol, fds: fds); |
576 | } |
577 | } |
578 | |
579 | // open() interpose function |
580 | extern "C" int open$__interposed__(const char *path, int oflag, int mode) { |
581 | const int pid = get_interposed_pid(); |
582 | if (pid >= 0) { |
583 | Locker locker(&g_mutex); |
584 | int fd = -2; |
585 | StringSP description_sp(new String); |
586 | if (oflag & O_CREAT) { |
587 | fd = ::open(file: path, oflag: oflag, mode); |
588 | description_sp->printf( |
589 | format: "pid=%i: open (path = '%s', oflag = %i, mode = %i) -> fd=%i" , pid, |
590 | path, oflag, mode, fd); |
591 | } else { |
592 | fd = ::open(file: path, oflag: oflag); |
593 | description_sp->printf(format: "pid=%i: open (path = '%s', oflag = %i) -> fd=%i" , |
594 | pid, path, oflag, fd); |
595 | } |
596 | |
597 | InvalidFDErrno fd_errno(fd); |
598 | if (g_log_all_calls) |
599 | description_sp->log(log_fd: get_logging_fd()); |
600 | if (fd >= 0) |
601 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
602 | return fd; |
603 | } else { |
604 | return ::open(file: path, oflag: oflag, mode); |
605 | } |
606 | } |
607 | |
608 | // open$NOCANCEL() interpose function |
609 | extern "C" int open$NOCANCEL$__interposed__(const char *path, int oflag, |
610 | int mode) { |
611 | const int pid = get_interposed_pid(); |
612 | if (pid >= 0) { |
613 | Locker locker(&g_mutex); |
614 | const int fd = ::open$NOCANCEL(path, oflag, mode); |
615 | InvalidFDErrno fd_errno(fd); |
616 | StringSP description_sp(new String( |
617 | "pid=%i: open$NOCANCEL (path = '%s', oflag = %i, mode = %i) -> fd=%i" , |
618 | pid, path, oflag, mode, fd)); |
619 | if (g_log_all_calls) |
620 | description_sp->log(log_fd: get_logging_fd()); |
621 | if (fd >= 0) |
622 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
623 | return fd; |
624 | } else { |
625 | return ::open$NOCANCEL(path, oflag, mode); |
626 | } |
627 | } |
628 | |
629 | // __open_extended() interpose function |
630 | extern "C" int __open_extended$__interposed__(const char *path, int oflag, |
631 | uid_t uid, gid_t gid, int mode, |
632 | struct kauth_filesec *fsacl) { |
633 | const int pid = get_interposed_pid(); |
634 | if (pid >= 0) { |
635 | Locker locker(&g_mutex); |
636 | const int fd = ::__open_extended(path, oflag, uid, gid, mode, fsacl); |
637 | InvalidFDErrno fd_errno(fd); |
638 | StringSP description_sp( |
639 | new String("pid=%i: __open_extended (path='%s', oflag=%i, uid=%i, " |
640 | "gid=%i, mode=%i, fsacl=%p) -> fd=%i" , |
641 | pid, path, oflag, uid, gid, mode, fsacl, fd)); |
642 | if (g_log_all_calls) |
643 | description_sp->log(log_fd: get_logging_fd()); |
644 | if (fd >= 0) |
645 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
646 | return fd; |
647 | } else { |
648 | return ::__open_extended(path, oflag, uid, gid, mode, fsacl); |
649 | } |
650 | } |
651 | |
652 | // kqueue() interpose function |
653 | extern "C" int kqueue$__interposed__(void) { |
654 | const int pid = get_interposed_pid(); |
655 | if (pid >= 0) { |
656 | Locker locker(&g_mutex); |
657 | const int fd = ::kqueue(); |
658 | InvalidFDErrno fd_errno(fd); |
659 | StringSP description_sp(new String("pid=%i: kqueue () -> fd=%i" , pid, fd)); |
660 | if (g_log_all_calls) |
661 | description_sp->log(log_fd: get_logging_fd()); |
662 | if (fd >= 0) |
663 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
664 | return fd; |
665 | } else { |
666 | return ::kqueue(); |
667 | } |
668 | } |
669 | |
670 | // shm_open() interpose function |
671 | extern "C" int shm_open$__interposed__(const char *path, int oflag, int mode) { |
672 | const int pid = get_interposed_pid(); |
673 | if (pid >= 0) { |
674 | Locker locker(&g_mutex); |
675 | const int fd = ::shm_open(name: path, oflag: oflag, mode: mode); |
676 | InvalidFDErrno fd_errno(fd); |
677 | StringSP description_sp(new String( |
678 | "pid=%i: shm_open (path = '%s', oflag = %i, mode = %i) -> fd=%i" , pid, |
679 | path, oflag, mode, fd)); |
680 | if (g_log_all_calls) |
681 | description_sp->log(log_fd: get_logging_fd()); |
682 | if (fd >= 0) |
683 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
684 | return fd; |
685 | } else { |
686 | return ::shm_open(name: path, oflag: oflag, mode: mode); |
687 | } |
688 | } |
689 | |
690 | // accept() interpose function |
691 | extern "C" int accept$__interposed__(int socket, struct sockaddr *address, |
692 | socklen_t *address_len) { |
693 | const int pid = get_interposed_pid(); |
694 | if (pid >= 0) { |
695 | Locker locker(&g_mutex); |
696 | const int fd = ::accept(fd: socket, addr: address, addr_len: address_len); |
697 | InvalidFDErrno fd_errno(fd); |
698 | StringSP description_sp(new String( |
699 | "pid=%i: accept (socket=%i, ...) -> fd=%i" , pid, socket, fd)); |
700 | if (g_log_all_calls) |
701 | description_sp->log(log_fd: get_logging_fd()); |
702 | if (fd >= 0) |
703 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
704 | return fd; |
705 | } else { |
706 | return ::accept(fd: socket, addr: address, addr_len: address_len); |
707 | } |
708 | } |
709 | |
710 | // accept$NOCANCEL() interpose function |
711 | extern "C" int accept$NOCANCEL$__interposed__(int socket, |
712 | struct sockaddr *address, |
713 | socklen_t *address_len) { |
714 | const int pid = get_interposed_pid(); |
715 | if (pid >= 0) { |
716 | Locker locker(&g_mutex); |
717 | const int fd = ::accept$NOCANCEL(socket, address, address_len); |
718 | InvalidFDErrno fd_errno(fd); |
719 | StringSP description_sp(new String( |
720 | "pid=%i: accept$NOCANCEL (socket=%i, ...) -> fd=%i" , pid, socket, fd)); |
721 | if (g_log_all_calls) |
722 | description_sp->log(log_fd: get_logging_fd()); |
723 | if (fd >= 0) |
724 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
725 | return fd; |
726 | } else { |
727 | return ::accept$NOCANCEL(socket, address, address_len); |
728 | } |
729 | } |
730 | |
731 | // dup() interpose function |
732 | extern "C" int dup$__interposed__(int fd2) { |
733 | const int pid = get_interposed_pid(); |
734 | if (pid >= 0) { |
735 | Locker locker(&g_mutex); |
736 | const int fd = ::dup(fd: fd2); |
737 | InvalidFDErrno fd_errno(fd); |
738 | StringSP description_sp( |
739 | new String("pid=%i: dup (fd2=%i) -> fd=%i" , pid, fd2, fd)); |
740 | if (g_log_all_calls) |
741 | description_sp->log(log_fd: get_logging_fd()); |
742 | if (fd >= 0) |
743 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
744 | return fd; |
745 | } else { |
746 | return ::dup(fd: fd2); |
747 | } |
748 | } |
749 | |
750 | // dup2() interpose function |
751 | extern "C" int dup2$__interposed__(int fd1, int fd2) { |
752 | const int pid = get_interposed_pid(); |
753 | if (pid >= 0) { |
754 | Locker locker(&g_mutex); |
755 | // If "fd2" is already opened, it will be closed during the |
756 | // dup2 call below, so we need to see if we have fd2 in our |
757 | // open map and treat it as a close(fd2) |
758 | FDEventMap::iterator pos = g_fd_event_map.find(x: fd2); |
759 | StringSP dup2_close_description_sp( |
760 | new String("pid=%i: dup2 (fd1=%i, fd2=%i) -> will close (fd=%i)" , pid, |
761 | fd1, fd2, fd2)); |
762 | if (pos != g_fd_event_map.end() && pos->second.back()->IsCreateEvent()) |
763 | save_backtrace(fd: fd2, err: 0, string_sp: dup2_close_description_sp, is_create: false); |
764 | |
765 | const int fd = ::dup2(fd: fd1, fd2: fd2); |
766 | InvalidFDErrno fd_errno(fd); |
767 | StringSP description_sp(new String("pid=%i: dup2 (fd1=%i, fd2=%i) -> fd=%i" , |
768 | pid, fd1, fd2, fd)); |
769 | if (g_log_all_calls) |
770 | description_sp->log(log_fd: get_logging_fd()); |
771 | |
772 | if (fd >= 0) |
773 | save_backtrace(fd, err: fd_errno.get_errno(), string_sp: description_sp, is_create: true); |
774 | return fd; |
775 | } else { |
776 | return ::dup2(fd: fd1, fd2: fd2); |
777 | } |
778 | } |
779 | |
780 | // close() interpose function |
781 | extern "C" int close$__interposed__(int fd) { |
782 | const int pid = get_interposed_pid(); |
783 | if (pid >= 0) { |
784 | Locker locker(&g_mutex); |
785 | const int err = close(fd: fd); |
786 | NegativeErrorErrno err_errno(err); |
787 | StringSP description_sp(new String); |
788 | if (err == -1) |
789 | description_sp->printf(format: "pid=%i: close (fd=%i) => %i errno = %i (%s))" , |
790 | pid, fd, err, err_errno.get_errno(), |
791 | strerror(errnum: err_errno.get_errno())); |
792 | else |
793 | description_sp->printf(format: "pid=%i: close (fd=%i) => %i" , pid, fd, err); |
794 | if (g_log_all_calls) |
795 | description_sp->log(log_fd: get_logging_fd()); |
796 | |
797 | if (err == 0) { |
798 | if (fd >= 0) |
799 | save_backtrace(fd, err, string_sp: description_sp, is_create: false); |
800 | } else if (err == -1) { |
801 | if (err_errno.get_errno() == EBADF && fd != -1) { |
802 | backtrace_error(format: "close (fd=%d) resulted in EBADF:\n" , fd); |
803 | |
804 | FDEventMap::iterator pos = g_fd_event_map.find(x: fd); |
805 | if (pos != g_fd_event_map.end()) { |
806 | log(log_fd: get_logging_fd(), event: pos->second.back().get(), |
807 | format: "\nfd=%d was previously %s with this event:\n" , fd, |
808 | pos->second.back()->IsCreateEvent() ? "opened" : "closed" ); |
809 | } |
810 | } |
811 | } |
812 | return err; |
813 | } else { |
814 | return close(fd: fd); |
815 | } |
816 | } |
817 | |
818 | // close$NOCANCEL() interpose function |
819 | extern "C" int close$NOCANCEL$__interposed__(int fd) { |
820 | const int pid = get_interposed_pid(); |
821 | if (pid >= 0) { |
822 | Locker locker(&g_mutex); |
823 | const int err = close$NOCANCEL(fd); |
824 | NegativeErrorErrno err_errno(err); |
825 | StringSP description_sp(new String); |
826 | if (err == -1) |
827 | description_sp->printf( |
828 | format: "pid=%i: close$NOCANCEL (fd=%i) => %i errno = %i (%s))" , pid, fd, err, |
829 | err_errno.get_errno(), strerror(errnum: err_errno.get_errno())); |
830 | else |
831 | description_sp->printf(format: "pid=%i: close$NOCANCEL (fd=%i) => %i" , pid, fd, |
832 | err); |
833 | if (g_log_all_calls) |
834 | description_sp->log(log_fd: get_logging_fd()); |
835 | |
836 | if (err == 0) { |
837 | if (fd >= 0) |
838 | save_backtrace(fd, err, string_sp: description_sp, is_create: false); |
839 | } else if (err == -1) { |
840 | if (err_errno.get_errno() == EBADF && fd != -1) { |
841 | backtrace_error(format: "close$NOCANCEL (fd=%d) resulted in EBADF\n:" , fd); |
842 | |
843 | FDEventMap::iterator pos = g_fd_event_map.find(x: fd); |
844 | if (pos != g_fd_event_map.end()) { |
845 | log(log_fd: get_logging_fd(), event: pos->second.back().get(), |
846 | format: "\nfd=%d was previously %s with this event:\n" , fd, |
847 | pos->second.back()->IsCreateEvent() ? "opened" : "closed" ); |
848 | } |
849 | } |
850 | } |
851 | return err; |
852 | } else { |
853 | return close$NOCANCEL(fd); |
854 | } |
855 | } |
856 | |
857 | // pipe() interpose function |
858 | extern "C" int pipe$__interposed__(int fds[2]) { |
859 | const int pid = get_interposed_pid(); |
860 | if (pid >= 0) { |
861 | Locker locker(&g_mutex); |
862 | fds[0] = -1; |
863 | fds[1] = -1; |
864 | const int err = pipe(pipedes: fds); |
865 | const int saved_errno = errno; |
866 | StringSP description_sp(new String( |
867 | "pid=%i: pipe ({fd=%i, fd=%i}) -> err=%i" , pid, fds[0], fds[1], err)); |
868 | if (g_log_all_calls) |
869 | description_sp->log(log_fd: get_logging_fd()); |
870 | if (fds[0] >= 0) |
871 | save_backtrace(fd: fds[0], err: saved_errno, string_sp: description_sp, is_create: true); |
872 | if (fds[1] >= 0) |
873 | save_backtrace(fd: fds[1], err: saved_errno, string_sp: description_sp, is_create: true); |
874 | errno = saved_errno; |
875 | return err; |
876 | } else { |
877 | return pipe(pipedes: fds); |
878 | } |
879 | } |
880 | |
881 | // get_fd_history() |
882 | // |
883 | // This function allows runtime access to the file descriptor history. |
884 | // |
885 | // @param[in] log_fd |
886 | // The file descriptor to log to |
887 | // |
888 | // @param[in] fd |
889 | // The file descriptor whose history should be dumped |
890 | extern "C" void get_fd_history(int log_fd, int fd) { |
891 | // "create" below needs to be outside of the mutex locker scope |
892 | if (log_fd >= 0) { |
893 | bool got_lock = false; |
894 | Locker locker(&g_mutex, got_lock); |
895 | if (got_lock) { |
896 | FDEventMap::iterator pos = g_fd_event_map.find(x: fd); |
897 | log_to_fd(log_fd, format: "Dumping file descriptor history for fd=%i:\n" , fd); |
898 | if (pos != g_fd_event_map.end()) { |
899 | FDEventArray &event_array = g_fd_event_map[fd]; |
900 | const size_t num_events = event_array.size(); |
901 | for (size_t i = 0; i < num_events; ++i) |
902 | event_array[i]->Dump(log_fd); |
903 | } else { |
904 | log_to_fd(log_fd, format: "error: no file descriptor events found for fd=%i\n" , |
905 | fd); |
906 | } |
907 | } else { |
908 | log_to_fd(log_fd, format: "error: fd event mutex is locked...\n" ); |
909 | } |
910 | } |
911 | } |
912 | |
913 | // Interposing |
914 | // FD creation routines |
915 | DYLD_INTERPOSE(accept$__interposed__, accept); |
916 | DYLD_INTERPOSE(accept$NOCANCEL$__interposed__, accept$NOCANCEL); |
917 | DYLD_INTERPOSE(dup$__interposed__, dup); |
918 | DYLD_INTERPOSE(dup2$__interposed__, dup2); |
919 | DYLD_INTERPOSE(kqueue$__interposed__, kqueue); |
920 | DYLD_INTERPOSE(open$__interposed__, open); |
921 | DYLD_INTERPOSE(open$NOCANCEL$__interposed__, open$NOCANCEL); |
922 | DYLD_INTERPOSE(__open_extended$__interposed__, __open_extended); |
923 | DYLD_INTERPOSE(pipe$__interposed__, pipe); |
924 | DYLD_INTERPOSE(shm_open$__interposed__, shm_open); |
925 | DYLD_INTERPOSE(socket$__interposed__, socket); |
926 | DYLD_INTERPOSE(socketpair$__interposed__, socketpair); |
927 | |
928 | // FD deleting routines |
929 | DYLD_INTERPOSE(close$__interposed__, close); |
930 | DYLD_INTERPOSE(close$NOCANCEL$__interposed__, close$NOCANCEL); |
931 | |
932 | } // namespace fd_interposing |
933 | |