/
darwintrace.c
1205 lines (1072 loc) · 35.1 KB
/
darwintrace.c
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2005 Apple Inc. All rights reserved.
* Copyright (c) 2005-2006 Paul Guyot <pguyot@kallisys.net>,
* Copyright (c) 2006-2018 The MacPorts Project
* All rights reserved.
*
* @APPLE_BSD_LICENSE_HEADER_START@
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @APPLE_BSD_LICENSE_HEADER_END@
*/
#define DARWINTRACE_USE_PRIVATE_API 1
#include "darwintrace.h"
#include "sandbox_actions.h"
#include "strlcpy.h"
#ifdef HAVE_STDATOMIC_H
#include <stdatomic.h>
#endif
#ifdef HAVE_LIBKERN_OSATOMIC_H
#include <libkern/OSAtomic.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <string.h>
#include <sys/attr.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#if __DARWIN_64_BIT_INO_T
#define STATSYSNUM SYS_stat64
#define LSTATSYSNUM SYS_lstat64
#else
#define STATSYSNUM SYS_stat
#define LSTATSYSNUM SYS_lstat
#endif
// Global Variables
/**
* PID of the process darwintrace was last used in. This is used to detect
* forking and opening a new connection to the control socket in the child
* process. Not doing so would potentially cause two processes writing to the
* same socket.
*/
pid_t __darwintrace_pid = (pid_t) - 1;
/**
* Helper variable containing the number of the darwintrace socket, iff the
* close(2) syscall should be allowed to close it. Used by \c
* __darwintrace_close.
*/
volatile int __darwintrace_close_sock = -1;
/**
* Debug socket. Will be initialized by a constructor function.
*/
FILE *__darwintrace_stderr = NULL;
static inline void __darwintrace_log_op(const char *op, const char *path);
static char *__send(const char *buf, uint32_t len, int answer);
/**
* pthread_key_ts for the pthread_t returned by pthread_self() and the
* darwintrace socket to ensure the socket is only used from a single thread.
*/
static pthread_key_t tid_key;
// The sock key is needed in close(2) and dup2(2)
pthread_key_t sock_key;
/**
* size of the communication buffer
*/
#define BUFFER_SIZE 4096
/**
* Variable holding the sandbox bounds in the following format:
* <filemap> :: (<spec> '\0')+ '\0'
* <spec> :: <path> '\0' <operation> <additional_data>?
* <operation> :: '0' | '1' | '2'
* where
* 0: allow
* 1: map the path to the one given in additional_data (currently unsupported)
* 2: check for a dependency using the socket
* 3: deny access to the path and stop processing
*/
#ifdef HAVE_STDATOMIC_H
static _Atomic(char *) filemap;
#else
static char *filemap;
#endif
volatile bool __darwintrace_initialized = false;
/**
* "Constructors" we'd like to run before we do anything. As using
* __attribute__((constructor)) for these would be unsafe here (as our
* interposed functions might end up being called first) we'll run them manually
* before we interpose anything.
*/
static void (*constructors[])(void) = {
__darwintrace_setup_tls,
__darwintrace_store_env,
};
void __darwintrace_run_constructors(void) {
for (size_t i = 0; i < sizeof(constructors) / sizeof(*constructors); ++i) {
constructors[i]();
}
__darwintrace_initialized = true;
}
static void __darwintrace_sock_destructor(FILE *dtsock) {
__darwintrace_close_sock = fileno(dtsock);
fclose(dtsock);
__darwintrace_close_sock = -1;
__darwintrace_sock_set(NULL);
}
/**
* Setup method called as constructor to set up thread-local storage for the
* thread id and the darwintrace socket.
*/
void __darwintrace_setup_tls(void) {
if (0 != (errno = pthread_key_create(&tid_key, NULL))) {
perror("darwintrace: pthread_key_create");
abort();
}
if (0 != (errno = pthread_key_create(&sock_key, (void (*)(void *)) __darwintrace_sock_destructor))) {
perror("darwintrace: pthread_key_create");
abort();
}
}
/**
* Convenience getter function for the thread ID
*/
/*
static inline pthread_t __darwintrace_tid() {
return (pthread_t) pthread_getspecific(tid_key);
}
*/
/**
* Convenience setter function for the thread-local darwintrace socket
*/
static inline void __darwintrace_tid_set(void) {
if (0 != (errno = pthread_setspecific(tid_key, (const void *) pthread_self()))) {
perror("darwintrace: pthread_setspecific");
abort();
}
}
/**
* Return false if str doesn't begin with prefix, true otherwise. Note that
* this is not a simple string comparison, but works on a path component level.
* A prefix of /var/tmp will not match a string of /var/tmpfoo.
*/
static inline bool __darwintrace_pathbeginswith(const char *str, const char *prefix) {
char s;
char p;
/* '/' is the allow all wildcard */
if (prefix[0] == '\0' || (prefix[0] == '/' && prefix[1] == '\0')) {
return 1;
}
do {
s = *str++;
p = *prefix++;
} while (p && (p == s));
return (p == '\0' && (s == '/' || s == '\0'));
}
/*
* Data structures and functions to iterate over the filemap received from
* tracelib code.
*/
/**
* \c filemap_iterator_t is an (opaque) iterator type that keeps the state
* required to iterate through the filemap. Create a new filemap_iterator_t on
* stack, initialize it using \c __darwintrace_filemap_iterator_init and pass
* it to \c __darwintrace_filemap_iter to iterate over the filemap.
*/
typedef struct filemap_iterator {
char *next;
} filemap_iterator_t;
/**
* Initialize a given \c filemap_iterator_t. Calling this function again will
* rewind the iterator.
*
* \param[in] it pointer to the iterator to be initialized
*/
static inline void __darwintrace_filemap_iterator_init(filemap_iterator_t *it) {
it->next = filemap;
}
/**
* Iterate through the filemap passed from tracelib code. Call this multiple
* times with the same iterator object until it returns \c NULL to iterate
* through the filemap.
*
* \param[out] command location for the command specified for this filemap
* entry
* \param[in] it pointer to a \c filemap_iterator_t keeping the state of this
* iteration
* \return string containing the path this filemap entry corresponds to, or \c
* NULL if the end of the filemap was reached
*/
static inline char *__darwintrace_filemap_iter(char *command, filemap_iterator_t *it) {
enum { PATH, COMMAND, DONE } state = PATH;
char *t;
char *path;
if (it == NULL || it->next == NULL || *it->next == '\0') {
return NULL;
}
path = t = it->next;
/* advance the cursor: if the number after the string is not 1, there's no
* path behind it and we can advance by strlen(t) + 3. If it is 1, make
* sure to skip the path, too.
*/
state = PATH;
while (state != DONE) {
switch (state) {
case DONE:
/* unreachable */
break;
case PATH:
if (*t == '\0') {
state = COMMAND;
}
break;
case COMMAND:
*command = *t;
if (*t == 1) {
fprintf(stderr, "darwintrace: unsupported state REPLACEPATH in dfa in " __FILE__ ":%d\n", __LINE__);
abort();
}
state = DONE;
/* the byte after the status code is '\0', if the status code
* isn't 1 (which is no longer supported) */
t++;
break;
}
t++;
}
it->next = t;
return path;
}
/**
* Request sandbox boundaries from tracelib (the MacPorts base-controlled side
* of the trace setup) and store it.
*/
static void __darwintrace_get_filemap(void) {
char *newfilemap;
#if DARWINTRACE_DEBUG && 0
filemap_iterator_t it;
char *path, command;
#endif
#if HAVE_DECL_ATOMIC_COMPARE_EXCHANGE_STRONG_EXPLICIT /* HAVE_DECL_* is always defined and set to 1 or 0 */
# define CAS(old, new, mem) atomic_compare_exchange_strong_explicit(mem, old, new, memory_order_relaxed, memory_order_relaxed)
#elif defined(HAVE_OSATOMICCOMPAREANDSWAPPTR)
# define CAS(old, new, mem) OSAtomicCompareAndSwapPtr(*old, new, (void * volatile *) (mem))
#elif defined(__LP64__)
# ifdef HAVE_OSATOMICCOMPAREANDSWAP64
# define CAS(old, new, mem) OSAtomicCompareAndSwap64((int64_t) (*old), (int64_t) (new), (volatile int64_t *) (mem))
# else
# error "No 64-bit compare and swap primitive available on 64-bit OS."
# endif
#else
# ifdef HAVE_OSATOMICCOMPAREANDSWAP32
# define CAS(old, new, mem) OSAtomicCompareAndSwap32((int32_t) (*old), (int32_t) (new), (volatile int32_t *) (mem))
# else
# error "No 32-bit compare and swap primitive available."
# endif
#endif
/*
* ensure we have a filemap present; this might be called simultaneously
* from multiple threads and needs to work without leaking and in a way
* that ensures a filemap has been set before any of the calls return. We
* achieve that by using non-blocking synchronization. Blocking
* synchronization might be a bad idea, because we never know where this
* code is actually called in an application.
*/
newfilemap = NULL;
char *nullpointer = NULL;
do {
free(newfilemap);
if (filemap != NULL)
break;
newfilemap = __send("filemap\t", 8, 1);
} while (!CAS(&nullpointer, newfilemap, &filemap));
#if DARWINTRACE_DEBUG && 0
for (__darwintrace_filemap_iterator_init(&it);
(path = __darwintrace_filemap_iter(&command, &it));) {
debug_printf("filemap: {cmd=%d, path=%s}\n", command, path);
}
#endif
}
/**
* Close the darwintrace socket and set it to \c NULL. Since this uses \c
* fclose(3), which internally calls \c close(2), which is intercepted by this
* library and this library prevents closing the socket to MacPorts, we use \c
* __darwintrace_close_sock to allow closing specific FDs.
*/
void __darwintrace_close(void) {
FILE *dtsock = __darwintrace_sock();
if (dtsock) {
__darwintrace_close_sock = fileno(dtsock);
fclose(dtsock);
__darwintrace_close_sock = -1;
__darwintrace_sock_set(NULL);
}
}
/**
* Ensures darwintrace is correctly set up by opening a socket connection to
* the MacPorts-side of trace mode. Will close and re-open this connection when
* called after \c fork(2), i.e. when the current PID doesn't match the one
* stored when the function was called last.
*/
void __darwintrace_setup(void) {
/*
* Check whether this is a child process and we've inherited the socket. We
* want to avoid race conditions with our parent process when communicating
* with tracelib and thus re-open all sockets, if that's the case. Note
* this also applies to threads within the same process, since we really
* want to avoid mixing up the results from two calls in different threads
* when reading from the socket.
*/
/*
* if the PID changed, close the current socket (which will force the
* following code to re-open it).
*/
if (__darwintrace_pid != (pid_t) -1 && __darwintrace_pid != getpid()) {
__darwintrace_close();
__darwintrace_pid = (pid_t) -1;
}
/*
* We don't need to watch for TID changes, because each thread has thread
* local storage for the socket that will contain NULL when the socket has
* not been initialized.
*/
if (__darwintrace_sock() == NULL) {
int sock;
int sockflags;
FILE *stream;
struct sockaddr_un sun;
__darwintrace_pid = getpid();
__darwintrace_tid_set();
if (__env_darwintrace_log == NULL) {
fprintf(stderr, "darwintrace: trace library loaded, but DARWINTRACE_LOG not set\n");
abort();
}
if (-1 == (sock = socket(PF_LOCAL, SOCK_STREAM, 0))) {
perror("darwintrace: socket");
abort();
}
/* Set the close-on-exec flag as early as possible after the socket
* creation. On macOS, there is no way to do this race-condition free
* unless you synchronize around creation and fork(2) -- however,
* blocking in this function is not acceptable for darwintrace, because
* it could possibly run in a signal handler, leading to a deadlock.
*
* The close-on-exec flag is needed because we're using a thread-local
* variable to hold a reference to this socket, but multi-threaded
* programs that fork will only clone the thread that calls fork(2),
* which leaves us with no reference to the other sockets (which are
* inherited, because FDs are process-wide). Consequently, this can
* lead to a resource leak.
*/
if (-1 == (sockflags = fcntl(sock, F_GETFD))) {
perror("darwintrace: fcntl(F_GETFD)");
abort();
}
sockflags |= FD_CLOEXEC;
if (-1 == fcntl(sock, F_SETFD, sockflags)) {
perror("darwintrace: fcntl(F_SETFD, flags | FD_CLOEXEC)");
abort();
}
if (strlen(__env_darwintrace_log) > sizeof(sun.sun_path) - 1) {
fprintf(stderr, "darwintrace: Can't connect to socket %s: name too long\n", __env_darwintrace_log);
abort();
}
sun.sun_family = AF_UNIX;
strlcpy(sun.sun_path, __env_darwintrace_log, sizeof(sun.sun_path));
if (-1 == (connect(sock, (struct sockaddr *) &sun, sizeof(sun)))) {
perror("darwintrace: connect");
abort();
}
if (NULL == (stream = fdopen(sock, "a+"))) {
perror("darwintrace: fdopen");
abort();
}
/* store FILE * into thread local storage for the socket */
__darwintrace_sock_set(stream);
/* request sandbox bounds */
__darwintrace_get_filemap();
}
}
/**
* Send a path to tracelib either given a path, or an FD (where
* fcntl(F_GETPATH) will be used).
*
* \param[in] op the operation (sent as-is to tracelib, should be interpreted
* as command)
* \param[in] path the (not necessarily absolute) path to send to tracelib
*/
static inline void __darwintrace_log_op(const char *op, const char *path) {
uint32_t size;
char logbuffer[BUFFER_SIZE];
size = snprintf(logbuffer, sizeof(logbuffer), "%s\t%s", op, path);
// Check if the buffer was short. If it was, discard the message silently,
// assuming it isn't important enough to error out.
if (size < BUFFER_SIZE) {
__send(logbuffer, size, 0);
}
}
/**
* Check whether the port currently being installed declares a dependency on
* a given file. Communicates with MacPorts tracelib, which uses the registry
* database to answer this question. Returns 1, if a dependency was declared,
* 0, if the file belongs to a port and no dependency was declared and -1 if
* the file isnt't registered to any port.
*
* \param[in] path the path to send to MacPorts for dependency info
* \return 1, if access should be granted, 0, if access should be denied, and
* -1 if MacPorts doesn't know about the file.
*/
static int dependency_check(const char *path) {
char buffer[BUFFER_SIZE], *p;
uint32_t len;
int result = 0;
struct stat st;
if (-1 == lstat(path, &st)) {
return 1;
}
if (S_ISDIR(st.st_mode)) {
debug_printf("%s is directory\n", path);
return 1;
}
len = snprintf(buffer, sizeof(buffer), "dep_check\t%s", path);
if (len >= sizeof(buffer)) {
fprintf(stderr, "darwintrace: truncating buffer length from %" PRIu32 " to %zu.", len, sizeof(buffer) - 1);
len = sizeof(buffer) - 1;
}
p = __send(buffer, len, 1);
if (!p) {
fprintf(stderr, "darwintrace: dependency check failed for %s\n", path);
abort();
}
switch (*p) {
case '+':
result = 1;
break;
case '!':
result = 0;
break;
case '?':
result = -1;
break;
default:
fprintf(stderr, "darwintrace: unexpected answer from tracelib: '%c' (0x%x)\n", *p, *p);
abort();
/*NOTREACHED*/
}
debug_printf("dependency_check: %s returned %d\n", path, result);
free(p);
return result;
}
/**
* Helper function to receive a number of bytes from the tracelib communication
* socket and deal with any errors that might occur.
*
* \param[out] buf buffer to hold received data
* \param[in] size number of bytes to read from the socket
*/
static void frecv(void *restrict buf, size_t size) {
/* We cannot safely use fread(3) here, because we're not in control of the
* application's signal handling settings (which means we must assume
* SA_RESTART isn't set) and fread(3) may return short without giving us
* a way to know how many bytes have actually been read, i.e. without a way
* to do the call again. Because of this great API design and
* implementation on macOS, we'll just use read(2) here. */
int fd = fileno(__darwintrace_sock());
size_t count = 0;
while (count < size) {
ssize_t res = read(fd, buf + count, size - count);
if (res < 0) {
if (errno == EINTR) {
continue;
}
perror("darwintrace: read");
abort();
}
if (res == 0) {
fprintf(stderr, "darwintrace: read: end-of-file\n");
abort();
}
count += res;
}
}
/**
* Helper function to send a buffer to MacPorts using the tracelib
* communication socket and deal with any errors that might occur.
*
* \param[in] buf buffer to send
* \param[in] size number of bytes in the buffer
*/
static void fsend(const void *restrict buf, size_t size) {
/* We cannot safely use fwrite(3) here, because we're not in control of the
* application's signal handling settings (which means we must assume
* SA_RESTART isn't set) and fwrite(3) may return short without giving us
* a way to know how many bytes have actually been written, i.e. without
* a way to do the call again. Because of this great API design and
* implementation on macOS, we'll just use write(2) here. */
int fd = fileno(__darwintrace_sock());
size_t count = 0;
while (count < size) {
ssize_t res = write(fd, buf + count, size - count);
if (res < 0) {
if (errno == EINTR) {
continue;
}
perror("darwintrace: write");
abort();
}
count += res;
}
}
/**
* Communication wrapper targeting tracelib. Automatically enforces the on-wire
* protocol and supports reading and returning an answer.
*
* \param[in] buf buffer to send to tracelib
* \param[in] len size of the buffer to send
* \param[in] answer boolean indicating whether an answer is expected and
* should be returned
* \return allocated answer buffer. Callers should free this buffer. If an
* answer was not requested, \c NULL.
*/
static char *__send(const char *buf, uint32_t len, int answer) {
fsend(&len, sizeof(len));
fsend(buf, len);
if (!answer) {
return NULL;
}
uint32_t recv_len = 0;
char *recv_buf;
frecv(&recv_len, sizeof(recv_len));
if (recv_len == 0) {
return 0;
}
recv_buf = malloc(recv_len + 1);
if (recv_buf == NULL) {
return NULL;
}
recv_buf[recv_len] = '\0';
frecv(recv_buf, recv_len);
return recv_buf;
}
/**
* Check a fully normalized path against the current sandbox. Helper function
* for __darwintrace_is_in_sandbox; do not use directly.
*
* \param[in] path the path to be checked; must be absolute and normalized.
* \param[in] flags A binary or combination of the following flags:
* - DT_REPORT: If access to this path is being denied, report
* it as sandbox violation. Set this for all operations that
* read file contents or check file attributes. Omit this
* flag for operations that might only attempt to access
* a file by chance, such as readdir(3).
* - DT_ALLOWDIR: Whether to always allow access if the given
* path references an existing directory. Set this for
* read operations such as stat(2), omit this for operations
* that modify directories like rmdir(2) and mkdir(2).
* \return \c true if the file is within sandbox bounds, \c false if access
* should be denied
*/
static inline bool __darwintrace_sandbox_check(const char *path, int flags) {
filemap_iterator_t filemap_it;
char command = -1;
char *t;
if (path[0] == '/' && path[1] == '\0') {
// Always allow access to /. Strange things start to happen if you deny this.
return true;
}
if ((flags & DT_ALLOWDIR) > 0) {
struct stat st;
if (-1 != lstat(path, &st) && S_ISDIR(st.st_mode)) {
return true;
}
}
// Iterate over the sandbox bounds and try to find a directive matching this path
for (__darwintrace_filemap_iterator_init(&filemap_it);
(t = __darwintrace_filemap_iter(&command, &filemap_it));) {
if (__darwintrace_pathbeginswith(path, t)) {
switch (command) {
case FILEMAP_ALLOW:
return true;
case FILEMAP_ASK:
// ask the socket whether this file is OK
switch (dependency_check(path)) {
case 1:
return true;
case -1:
// if the file isn't known to MacPorts, allow
// access anyway, but report a sandbox violation.
// TODO find a better solution
if ((flags & DT_REPORT) > 0) {
__darwintrace_log_op("sandbox_unknown", path);
}
return true;
case 0:
// file belongs to a foreign port, deny access
if ((flags & DT_REPORT) > 0) {
__darwintrace_log_op("sandbox_violation", path);
}
return false;
}
case FILEMAP_DENY:
if ((flags & DT_REPORT) > 0) {
__darwintrace_log_op("sandbox_violation", path);
}
return false;
default:
fprintf(stderr, "darwintrace: error: unexpected byte in file map: `%x'\n", *t);
abort();
}
}
}
if ((flags & DT_REPORT) > 0) {
__darwintrace_log_op("sandbox_violation", path);
}
return false;
}
/**
* Structure to represent a filesystem path component, i.e., a single level of a path.
*/
typedef struct {
char *path;
size_t len;
} path_component_t;
/**
* Structure to represent a normalized filesystem path.
*/
typedef struct {
size_t num;
size_t capacity;
path_component_t *components;
} path_t;
#define PATH_INITIAL_CAPACITY (PATH_MAX / 4 + 2)
/**
* Allocate a new filesystem path structure.
*
* @return Pointer to the new path_t on success, NULL on error.
*/
static path_t *path_new(void) {
path_t *path = NULL;
path = malloc(sizeof(path_t));
if (!path) {
goto out;
}
path->num = 0;
path->capacity = PATH_INITIAL_CAPACITY;
path->components = malloc(sizeof(path_component_t) * path->capacity);
if (!path->components) {
free(path);
path = NULL;
}
out:
return path;
}
/**
* Free a filesystem path structure.
*
* @param[in] path The path to release.
*/
static void path_free(path_t *path) {
for (size_t idx = 0; idx < path->num; ++idx) {
free(path->components[idx].path);
}
free(path->components);
free(path);
}
/**
* Append a component (given as string) to an existing filesystem path while
* preserving normality.
*
* If the given component is empty, or ".", the path will remain unmodified. If
* the given component is "..", the last component of the path is deleted.
* Otherwise, the new component is appended to the end of the path. Note that
* component must NOT contain slashes.
*
* @param[in] path The path to which the component should be appended.
* @param[in] component The path component to append.
* @return true on success, false when memory allocation fails.
*/
static bool path_append(path_t *path, const char *component) {
if (*component == '\0') {
// ignore empty path components, i.e., consecutive slashes
return true;
} else if (component[0] == '.' && component[1] == '\0') {
// ignore self-referencing path components
return true;
} else if (component[0] == '.' && component[1] == '.' && component[2] == '\0') {
// walk up one path component, if possible
if (path->num > 0) {
free(path->components[path->num - 1].path);
path->num--;
}
} else {
if (path->num >= path->capacity) {
// need more space for components, realloc to make that space
size_t new_capacity = path->capacity + (PATH_INITIAL_CAPACITY / 2);
path_component_t *new_components = realloc(path->components, sizeof(path_component_t) * new_capacity);
if (!new_components) {
return false;
}
path->capacity = new_capacity;
path->components = new_components;
}
// initialize new path_component_t
size_t len = strlen(component);
path_component_t *new_component = &path->components[path->num];
new_component->len = len;
new_component->path = malloc(len + 1);
if (!new_component->path) {
return false;
}
strlcpy(new_component->path, component, len + 1);
path->num++;
}
return true;
}
/**
* Take the given input path as string, tokenize it into separate path
* components, then append them to the given path, normalizing the path in the
* process. Modifies the given inpath string.
*
* @param[in] path The path to which the new components should be appended.
* @param[in] inpath The string input path which will be tokenized and normalized.
* @return true on success, false on memory allocation failure.
*/
static bool path_tokenize(path_t *path, char *inpath) {
char *pos = inpath;
const char *token;
while ((token = strsep(&pos, "/")) != NULL) {
if (!path_append(path, token)) {
return false;
}
}
return true;
}
/**
* The the given symbolic link as string, tokenize it into separate path
* components and normalize it in the context of the given path. If the
* symbolic link is absolute, this will replace the entire path, otherwise
* normalize the symlink relative to the current path. Modifies the given link.
*
* @param[in] path The path relative to which the symlink should be interpreted.
* @param[in] link The symbolic link contents obtained from readlink(2).
* @return true on success, false on memory allocation failure.
*/
static bool path_tokenize_symlink(path_t *path, char *link) {
if (*link == '/') {
// symlink is absolute, start fresh
for (size_t idx = 0; idx < path->num; idx++) {
free(path->components[idx].path);
}
path->num = 0;
return path_tokenize(path, link + 1);
}
// symlink is relative, remove last component
if (path->num > 0) {
free(path->components[path->num - 1].path);
path->num--;
}
return path_tokenize(path, link);
}
/**
* Strip a resource fork from the end of the path, if present.
*
* @param[in] path The path which should be checked for resource forks
*/
static void path_strip_resource_fork(path_t *path) {
if (path->num >= 2) {
if ( strcmp(path->components[path->num - 2].path, "..namedfork") == 0
&& strcmp(path->components[path->num - 1].path, "rsrc") == 0) {
free(path->components[path->num - 2].path);
free(path->components[path->num - 1].path);
path->num -= 2;
}
}
}
/**
* Return the length of the given path when represented as a native filesystem
* path with "/" separators, excluding the terminating \0 byte.
*
* @param[in] path The path whose length should be determined.
* @return The length of the path.
*/
static size_t path_len(const path_t *path) {
// One slash for each component
size_t len = path->num;
// Plus the length for each component
for (size_t idx = 0; idx < path->num; ++idx) {
len += path->components[idx].len;
}
return len;
}
/**
* Convert the given path into a string. The returned pointer is allocated and
* must be released with free(3).
*
* @param[in] path The path to convert to a string.
* @return An allocated string representation of the path on success, NULL on error.
*/
static char *path_str(const path_t *path) {
size_t len = path_len(path);
char *out = malloc(len + 1);
if (!out) {
return NULL;
}
out[0] = '/';
out[1] = '\0';
char *pos = out;
for (size_t idx = 0; idx < path->num; idx++) {
*pos = '/';
pos++;
path_component_t *component = &path->components[idx];
strlcpy(pos, component->path, component->len + 1);
pos += component->len;
}
return out;
}
/**
* Check whether the given path is a volfs path (i.e., /.vol/$fsnum/$inode),
* and return a non-volfs path if possible.
*
* If the return value is not the argument, the argument was correctly freed.
* Always use this function as
*
* path = path_resolve_volfs(path);
*
* for this reason.
*
* @param[in] path The path to check for volfs paths
* @return The orginal path if no modification was required or expansion
* failed. A fresh path if the path was a volfs path and was expanded.
*/
static path_t *path_resolve_volfs(path_t *path) {
#ifdef ATTR_CMN_FULLPATH
if (path->num >= 3 && strcmp(path->components[0].path, ".vol") == 0) {
struct attrlist attrlist;
attrlist.bitmapcount = ATTR_BIT_MAP_COUNT;
attrlist.reserved = 0;
attrlist.commonattr = ATTR_CMN_FULLPATH;
attrlist.volattr = 0;
attrlist.dirattr = 0;
attrlist.fileattr = 0;
attrlist.forkattr = 0;
char attrbuf[sizeof(uint32_t) + sizeof(attrreference_t) + (PATH_MAX + 1)];
/* attrlength attrref_t for the name UTF-8 name up to PATH_MAX chars */
char *path_native = path_str(path);
if (!path_native) {
goto out;
}
if (-1 == (getattrlist(path_native, &attrlist, attrbuf, sizeof(attrbuf), FSOPT_NOFOLLOW))) {
perror("darwintrace: getattrlist");
// ignore and just return the /.vol/ path
} else {
path_t *newpath = path_new();
if (!newpath) {
goto out;
}
attrreference_t *nameAttrRef = (attrreference_t *) (attrbuf + sizeof(uint32_t));
if (!path_tokenize(newpath, ((char *) nameAttrRef) + nameAttrRef->attr_dataoffset)) {
path_free(newpath);
goto out;
}
path_free(path);
path = newpath;
}