-
Notifications
You must be signed in to change notification settings - Fork 561
/
injector.c
1937 lines (1802 loc) · 65 KB
/
injector.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) 2012-2022 Google, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of Google, Inc. 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 THE COPYRIGHT HOLDERS AND 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 GOOGLE, INC. OR 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.
*/
/* Simple reimplementation of the dr_inject API for Linux.
*
* To match the Windows API, we fork a child and suspend it before the call to
* exec.
*/
#include "configure.h"
#include "globals_shared.h"
#include "../config.h" /* for get_config_val_other_app */
#include "../globals.h"
#ifdef LINUX
# include "include/syscall.h" /* for SYS_ptrace */
#else
# include <sys/syscall.h>
#endif
#include "instrument.h"
#include "instr.h"
#include "instr_create_shared.h"
#include "decode.h"
#include "disassemble.h"
#include "os_private.h"
#include "module.h"
#include "module_private.h"
#include "dr_inject.h"
#include <assert.h>
#ifndef MACOS
/* If we don't define _EXTERNALIZE_CTYPE_INLINES_*, we get errors vs tolower
* in globals.h; if we do, we get errors on isspace missing. We solve that
* by just by supplying our own isspace.
*/
# include <ctype.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h> /* for strerror */
#include <sys/mman.h>
#include <sys/ptrace.h>
#if defined(LINUX) && defined(AARCH64)
# include <linux/ptrace.h> /* for struct user_pt_regs */
#endif
#include <sys/uio.h> /* for struct iovec */
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
#include <time.h>
#ifdef MACOS
# include <spawn.h>
# include <crt_externs.h> /* _NSGetEnviron() */
#endif
/* i#1925: we need to support executing a shell script so we distinguish a
* non-image from a not-found or unreadable file.
*/
typedef enum {
PLATFORM_SUCCESS,
PLATFORM_ERROR_CANNOT_OPEN,
PLATFORM_UNKNOWN,
} platform_status_t;
#ifdef MACOS
/* The type is just "int", and the values are different, so we use the Linux
* type name to match the Linux constant names.
*/
# ifndef PT_ATTACHEXC /* New replacement for PT_ATTACH */
# define PT_ATTACHEXC PT_ATTACH
# endif
enum __ptrace_request {
PTRACE_TRACEME = PT_TRACE_ME,
PTRACE_CONT = PT_CONTINUE,
PTRACE_KILL = PT_KILL,
PTRACE_ATTACH = PT_ATTACHEXC,
PTRACE_DETACH = PT_DETACH,
PTRACE_SINGLESTEP = PT_STEP,
};
/* clang-format off */ /* (work around clang-format bug) */
static int inline
isspace(int c)
/* clang-format on */
{
return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v');
}
#endif
static bool verbose = false;
/* Set from a signal handler.
*/
static volatile int timeout_expired;
typedef enum _inject_method_t {
INJECT_EARLY, /* Works with self or child. */
INJECT_LD_PRELOAD, /* Works with self or child. */
INJECT_PTRACE /* Doesn't work with exec_self. */
} inject_method_t;
/* Opaque type to users, holds our state */
typedef struct _dr_inject_info_t {
process_id_t pid;
const char *exe; /* full path of executable */
const char *image_name; /* basename of exe */
const char **argv; /* array of arguments */
int pipe_fd;
bool exec_self; /* this process will exec the app */
inject_method_t method;
bool killpg;
bool exited;
int exitcode;
bool no_emulate_brk; /* is -no_emulate_brk in the option string? */
bool wait_syscall; /* valid iff -attach: handle blocking syscalls */
#ifdef MACOS
bool spawn_32bit;
#endif
} dr_inject_info_t;
#if defined(LINUX) && !defined(ANDROID) /* XXX i#1290/i#1701: NYI on MacOS/Android */
static bool
inject_ptrace(dr_inject_info_t *info, const char *library_path);
/* "enum __ptrace_request request" is used by glibc, but musl uses "int". */
static long
our_ptrace(int request, pid_t pid, void *addr, void *data);
#endif
/*******************************************************************************
* Core compatibility layer
*/
/* Never actually called, but needed to link in config.c. */
const char *
get_application_short_name(void)
{
ASSERT(false);
return "";
}
/* Shadow DR's d_r_internal_error so assertions work in standalone mode. DR tries
* to use safe_read to take a stack trace, but none of its signal handlers are
* installed, so it will segfault before it prints our error.
*/
void
d_r_internal_error(const char *file, int line, const char *expr)
{
fprintf(stderr, "ASSERT failed: %s:%d (%s)\n", file, line, expr);
fflush(stderr);
abort();
}
bool
ignore_assert(const char *assert_stmt, const char *expr)
{
return false;
}
void
report_dynamorio_problem(dcontext_t *dcontext, uint dumpcore_flag, app_pc exception_addr,
app_pc report_ebp, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "DynamoRIO problem: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
fflush(stderr);
abort();
}
/*******************************************************************************
* Injection implementation
*/
/* Environment modifications before executing the child process for LD_PRELOAD
* injection.
*/
static void
pre_execve_ld_preload(const char *dr_path)
{
char ld_lib_path[MAX_OPTIONS_STRING];
char preload_prefix[MAX_OPTIONS_STRING];
const char *last_slash = NULL;
const char *mode_slash = NULL;
const char *lib_slash = NULL;
const char *cur_path = getenv("LD_LIBRARY_PATH");
const char *cur = dr_path;
#ifdef MACOS
const char *cur_preload = getenv("DYLD_INSERT_LIBRARIES");
const char preload_delimiter = ':';
#else
const char *cur_preload = getenv("LD_PRELOAD");
const char preload_delimiter = ' ';
#endif
/* Find last three occurrences of '/'. */
while (*cur != '\0') {
if (*cur == '/') {
lib_slash = mode_slash;
mode_slash = last_slash;
last_slash = cur;
}
cur++;
}
/* dr_path should be absolute and have at least three components. */
ASSERT(lib_slash != NULL && last_slash != NULL);
ASSERT(strncmp(lib_slash, "/lib32", 5) == 0 || strncmp(lib_slash, "/lib64", 5) == 0);
/* Put both DR's path and the extension path on LD_LIBRARY_PATH. We only
* need the extension path if -no_private_loader is used.
*/
snprintf(ld_lib_path, BUFFER_SIZE_ELEMENTS(ld_lib_path), "%.*s:%.*s/ext%.*s%s%s",
last_slash - dr_path, dr_path, /* DR path */
lib_slash - dr_path, dr_path, /* pre-ext path */
last_slash - lib_slash, lib_slash, /* libNN component */
cur_path == NULL ? "" : ":", cur_path == NULL ? "" : cur_path);
NULL_TERMINATE_BUFFER(ld_lib_path);
preload_prefix[0] = '\0';
if (cur_preload != NULL) {
snprintf(preload_prefix, BUFFER_SIZE_ELEMENTS(preload_prefix), "%s%c",
cur_preload, preload_delimiter);
NULL_TERMINATE_BUFFER(preload_prefix);
}
#ifdef MACOS
setenv("DYLD_LIBRARY_PATH", ld_lib_path, true /*overwrite*/);
/* XXX: why does it not work w/o the full path? */
snprintf(ld_lib_path, BUFFER_SIZE_ELEMENTS(ld_lib_path), "%s%.*s/%s:%.*s/%s",
preload_prefix, last_slash - dr_path, dr_path, "libdrpreload.dylib",
last_slash - dr_path, dr_path, "libdynamorio.dylib");
NULL_TERMINATE_BUFFER(ld_lib_path);
setenv("DYLD_INSERT_LIBRARIES", ld_lib_path, true /*overwrite*/);
/* This is required to use DYLD_INSERT_LIBRARIES on apps that use
* two-level naming, but it can cause an app to run incorrectly.
* Long-term we'll want a true early injector.
*/
setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", true /*overwrite*/);
#else
setenv("LD_LIBRARY_PATH", ld_lib_path, true /*overwrite*/);
snprintf(ld_lib_path, BUFFER_SIZE_ELEMENTS(ld_lib_path), "%s%s", preload_prefix,
"libdynamorio.so libdrpreload.so");
NULL_TERMINATE_BUFFER(ld_lib_path);
setenv("LD_PRELOAD", ld_lib_path, true /*overwrite*/);
#endif
if (verbose) {
printf("Setting LD_USE_LOAD_BIAS for PIEs so the loader will honor "
"DR's preferred base. (i#719)\n"
"Set LD_USE_LOAD_BIAS=0 prior to injecting if this is a "
"problem.\n");
}
setenv("LD_USE_LOAD_BIAS", "1", false /*!overwrite, let user set it*/);
}
/* Environment modifications before executing the child process for early
* injection.
*/
static void
pre_execve_early(dr_inject_info_t *info, const char *exe)
{
setenv(DYNAMORIO_VAR_EXE_PATH, exe, true /*overwrite*/);
if (info->no_emulate_brk)
setenv(DYNAMORIO_VAR_NO_EMULATE_BRK, exe, true /*overwrite*/);
}
static void
execute_exec(dr_inject_info_t *info, const char *toexec)
{
#ifdef MACOS
if (info->spawn_32bit) {
/* i#1643: a regular execve will always match the kernel bitwidth */
/* XXX: use raw data structures and SYS_posix_spawn */
posix_spawnattr_t attr;
if (posix_spawnattr_init(&attr) == 0) {
cpu_type_t cpu = CPU_TYPE_X86;
size_t sz;
if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETEXEC) == 0 &&
posix_spawnattr_setbinpref_np(&attr, sizeof(cpu), &cpu, &sz) == 0) {
posix_spawn(NULL, toexec, NULL, &attr, (char *const *)info->argv,
/* On Mac, shared libs don't have access to environ
* directly and must use this routine (if we declare
* environ as extern we end up looking in the wrong
* place at some copy of the env vars).
*/
(char *const *)*_NSGetEnviron());
}
/* If we get here the spawn failed */
posix_spawnattr_destroy(&attr);
}
return; /* don't do exec if error */
}
#endif
execv(toexec, (char **)info->argv);
}
static process_id_t
fork_suspended_child(const char *exe, dr_inject_info_t *info, int fds[2])
{
process_id_t pid = fork();
if (pid == 0) {
/* child, suspend before exec */
char pipe_cmd[MAXIMUM_PATH];
ssize_t nread;
size_t sofar = 0;
const char *real_exe = NULL;
const char *arg;
close(fds[1]); /* Close writer in child, keep reader. */
do {
nread = read(fds[0], pipe_cmd + sofar, BUFFER_SIZE_BYTES(pipe_cmd) - sofar);
sofar += nread;
} while (nread > 0 && sofar < BUFFER_SIZE_BYTES(pipe_cmd) - 1);
pipe_cmd[sofar] = '\0';
close(fds[0]); /* Close reader before exec. */
arg = pipe_cmd;
/* The first token is the command and the rest is an argument. */
while (*arg != '\0' && !isspace(*arg))
arg++;
while (*arg != '\0' && isspace(*arg))
arg++;
if (pipe_cmd[0] == '\0') {
/* If nothing was written to the pipe, let it run natively. */
real_exe = exe;
} else if (strstr(pipe_cmd, "ld_preload ") == pipe_cmd) {
pre_execve_ld_preload(arg);
real_exe = exe;
} else if (strcmp("ptrace", pipe_cmd) == 0) {
/* If using ptrace, we're already attached and will walk across the
* execv.
*/
real_exe = exe;
} else if (strstr(pipe_cmd, "exec_dr ") == pipe_cmd) {
pre_execve_early(info, exe);
real_exe = arg;
}
/* Trigger automated takeover in case DR is statically linked (yes
* we blindly do this rather than try to pass in a parameter).
*/
setenv("DYNAMORIO_TAKEOVER_IN_INIT", "1", true /*overwrite*/);
execute_exec(info, real_exe);
/* If execv returns, there was an error. */
exit(-1);
}
return pid;
}
static void
write_pipe_cmd(int pipe_fd, const char *cmd)
{
ssize_t towrite = strlen(cmd);
ssize_t written = 0;
if (verbose)
fprintf(stderr, "writing cmd: %s\n", cmd);
while (towrite > 0) {
ssize_t nwrote = write(pipe_fd, cmd + written, towrite);
if (nwrote <= 0)
break;
towrite -= nwrote;
written += nwrote;
}
}
static bool
inject_early(dr_inject_info_t *info, const char *library_path)
{
if (info->exec_self) {
/* exec DR with the original command line and set an environment
* variable pointing to the real exe.
*/
pre_execve_early(info, info->exe);
execute_exec(info, library_path);
return false; /* if execv returns, there was an error */
} else {
/* Write the path to DR to the pipe. */
char cmd[MAXIMUM_PATH];
snprintf(cmd, BUFFER_SIZE_ELEMENTS(cmd), "exec_dr %s", library_path);
NULL_TERMINATE_BUFFER(cmd);
write_pipe_cmd(info->pipe_fd, cmd);
}
return true;
}
static bool
inject_ld_preload(dr_inject_info_t *info, const char *library_path)
{
if (info->exec_self) {
pre_execve_ld_preload(library_path);
execute_exec(info, info->exe);
return false; /* if execv returns, there was an error */
} else {
/* Write the path to DR to the pipe. */
char cmd[MAXIMUM_PATH];
snprintf(cmd, BUFFER_SIZE_ELEMENTS(cmd), "ld_preload %s", library_path);
NULL_TERMINATE_BUFFER(cmd);
write_pipe_cmd(info->pipe_fd, cmd);
}
return true;
}
static dr_inject_info_t *
create_inject_info(const char *exe, const char **argv)
{
dr_inject_info_t *info = calloc(sizeof(*info), 1);
info->exe = exe;
info->argv = argv;
info->image_name = strrchr(exe, '/');
info->image_name = (info->image_name == NULL ? exe : info->image_name + 1);
info->exited = false;
info->killpg = false;
info->exitcode = -1;
return info;
}
static platform_status_t
module_get_platform_path(const char *exe_path, dr_platform_t *platform,
dr_platform_t *alt_platform)
{
file_t fd = os_open(exe_path, OS_OPEN_READ);
platform_status_t res = PLATFORM_SUCCESS;
if (fd == INVALID_FILE)
res = PLATFORM_ERROR_CANNOT_OPEN;
else {
if (!module_get_platform(fd, platform, alt_platform)) {
/* It may be a shell script so we try it. */
res = PLATFORM_UNKNOWN;
*platform = IF_X64_ELSE(DR_PLATFORM_64BIT, DR_PLATFORM_32BIT);
}
os_close(fd);
}
return res;
}
static bool
exe_is_right_bitwidth(const char *exe, int *errcode)
{
dr_platform_t platform, alt_platform;
if (module_get_platform_path(exe, &platform, &alt_platform) ==
PLATFORM_ERROR_CANNOT_OPEN) {
*errcode = errno;
if (*errcode == 0)
*errcode = ESRCH;
return true; // false;
}
/* Check if the executable is the right bitwidth and set errcode to be
* special code WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE if not.
* The caller may decide what to do, e.g., terminate with error or
* continue with cross arch executable.
* XXX: i#1176 and DrM-i#1037, we need a long term solution to
* support cross-arch injection.
*/
if (platform !=
IF_X64_ELSE(DR_PLATFORM_64BIT, DR_PLATFORM_32BIT)
IF_MACOS(IF_NOT_X64(&&alt_platform != DR_PLATFORM_32BIT))) {
*errcode = WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE;
return false;
}
return true;
}
/* Returns 0 on success.
*/
DR_EXPORT
int
dr_inject_process_create(const char *exe, const char **argv, void **data DR_PARAM_OUT)
{
int r;
int fds[2];
dr_inject_info_t *info = create_inject_info(exe, argv);
int errcode = 0;
#if defined(MACOS) && !defined(X64)
dr_platform_t platform, alt_platform;
if (module_get_platform_path(info->exe, &platform, &alt_platform) ==
PLATFORM_ERROR_CANNOT_OPEN)
return false; /* couldn't read header */
if (platform == DR_PLATFORM_64BIT) {
/* The target app is a universal binary and we're on a 64-bit kernel,
* so we have to use posix_spawn to exec the app as 32-bit.
*/
ASSERT(alt_platform == DR_PLATFORM_32BIT);
info->spawn_32bit = true;
}
#endif
if (!exe_is_right_bitwidth(exe, &errcode) &&
/* WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE is just a warning on Unix,
* so we carry on but be sure to return the code.
*/
errcode != WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE) {
/* return here if couldn't find app */
goto error;
}
/* Create a pipe to a forked child and have it block on the pipe. */
r = pipe(fds);
if (r != 0)
goto error;
info->argv = argv;
info->pid = fork_suspended_child(exe, info, fds);
close(fds[0]); /* Close reader, keep writer. */
info->pipe_fd = fds[1];
info->exec_self = false;
info->method = INJECT_LD_PRELOAD;
if (info->pid == -1)
goto error;
*data = info;
return errcode;
error:
free(info);
return errno;
}
DR_EXPORT
int
dr_inject_prepare_to_exec(const char *exe, const char **argv, void **data DR_PARAM_OUT)
{
dr_inject_info_t *info = create_inject_info(exe, argv);
int errcode = 0;
*data = info;
if (!exe_is_right_bitwidth(exe, &errcode) &&
errcode != WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE) {
free(info);
return errcode;
}
info->pid = getpid();
info->pipe_fd = 0; /* No pipe. */
info->exec_self = true;
info->method = INJECT_LD_PRELOAD;
/* Trigger automated takeover in case DR is statically linked. */
setenv("DYNAMORIO_TAKEOVER_IN_INIT", "1", true /*overwrite*/);
return errcode;
}
DR_EXPORT
int
dr_inject_prepare_to_attach(process_id_t pid, const char *appname, bool wait_syscall,
void **data DR_PARAM_OUT)
{
dr_inject_info_t *info = create_inject_info(appname, NULL);
int errcode = 0;
*data = info;
info->pid = pid;
info->pipe_fd = 0; /* No pipe. */
info->exec_self = false;
info->method = INJECT_PTRACE;
info->wait_syscall = wait_syscall;
return errcode;
}
DR_EXPORT
bool
dr_inject_prepare_to_ptrace(void *data)
{
dr_inject_info_t *info = (dr_inject_info_t *)data;
if (data == NULL)
return false;
if (info->exec_self)
return false;
info->method = INJECT_PTRACE;
return true;
}
DR_EXPORT
bool
dr_inject_prepare_new_process_group(void *data)
{
dr_inject_info_t *info = (dr_inject_info_t *)data;
int res;
if (data == NULL)
return false;
if (info->exec_self)
return false;
/* Put the child in its own process group. */
res = setpgid(info->pid, info->pid);
if (res < 0)
return false;
info->killpg = true;
return true;
}
DR_EXPORT
process_id_t
dr_inject_get_process_id(void *data)
{
dr_inject_info_t *info = (dr_inject_info_t *)data;
return info->pid;
}
DR_EXPORT
char *
dr_inject_get_image_name(void *data)
{
dr_inject_info_t *info = (dr_inject_info_t *)data;
return (char *)info->image_name;
}
/* FIXME: Use the parser in options.c. The implementation here will find
* options in quoted strings, like the client options string.
*/
static bool
option_present(const char *dr_ops, const char *op)
{
size_t oplen = strlen(op);
const char *cur = strstr(dr_ops, op);
return (cur != NULL &&
/* drrun now re-quotes args so we have to look for ".
* This is not perfect: but we don't expect "-no_early_inject" to be
* embedded into some longer string w/ literal quotes around it.
*/
(cur[oplen] == '\0' || cur[oplen] == '\"' || isspace(cur[oplen])) &&
(cur == dr_ops || cur[-1] == '\"' || isspace(cur[-1])));
}
DR_EXPORT
bool
dr_inject_process_inject(void *data, bool force_injection, const char *library_path)
{
dr_inject_info_t *info = (dr_inject_info_t *)data;
char dr_path_buf[MAXIMUM_PATH];
char dr_ops[MAX_OPTIONS_STRING];
dr_platform_t platform, alt_platform;
if (module_get_platform_path(info->exe, &platform, &alt_platform) ==
PLATFORM_ERROR_CANNOT_OPEN)
return false; /* couldn't read header */
#if defined(MACOS) && !defined(X64)
if (platform == DR_PLATFORM_64BIT) {
/* The target app is a universal binary and we're on a 64-bit kernel,
* so we have to use posix_spawn to exec the app as 32-bit.
*/
ASSERT(alt_platform == DR_PLATFORM_32BIT);
platform = DR_PLATFORM_32BIT;
info->spawn_32bit = true;
}
#endif
if (!get_config_val_other_app(info->image_name, info->pid, platform,
DYNAMORIO_VAR_OPTIONS, dr_ops,
BUFFER_SIZE_ELEMENTS(dr_ops), NULL, NULL, NULL)) {
return false;
}
if (info->method == INJECT_LD_PRELOAD &&
!option_present(dr_ops, "-no_early_inject")) {
#ifndef MACOS /* XXX i#1285: implement private loader for MacOS */
info->method = INJECT_EARLY;
/* i#1004: -early_inject has to decide whether to emulate the brk
* before it can parse the options so we use an env var:
*/
if (option_present(dr_ops, "-no_emulate_brk"))
info->no_emulate_brk = true;
#endif
}
#ifdef STATIC_LIBRARY
return true; /* Do nothing. DR will takeover by itself. */
#endif
/* Read the autoinject var from the config file if the caller didn't
* override it.
*/
if (library_path == NULL) {
if (!get_config_val_other_app(
info->image_name, info->pid, platform, DYNAMORIO_VAR_AUTOINJECT,
dr_path_buf, BUFFER_SIZE_ELEMENTS(dr_path_buf), NULL, NULL, NULL)) {
return false;
}
library_path = dr_path_buf;
}
switch (info->method) {
case INJECT_EARLY: return inject_early(info, library_path);
case INJECT_LD_PRELOAD: return inject_ld_preload(info, library_path);
case INJECT_PTRACE:
#if defined(LINUX) && !defined(ANDROID) /* XXX i#1290/i#1701: NYI on MacOS/Android */
return inject_ptrace(info, library_path);
#else
return false;
#endif
}
return false;
}
/* We get the signal, we set the volatile, which is guaranteed to be signal
* safe. waitpid should return EINTR after we receive the signal.
*/
static void
alarm_handler(int sig)
{
timeout_expired = true;
}
DR_EXPORT
bool
dr_inject_process_run(void *data)
{
dr_inject_info_t *info = (dr_inject_info_t *)data;
if (info->exec_self) {
/* If we're injecting with LD_PRELOAD or STATIC_LIBRARY, we already set
* up the environment. If not, then let the app run natively.
*/
execute_exec(info, info->exe);
return false; /* if execv returns, there was an error */
} else {
if (info->method == INJECT_PTRACE) {
#if defined(LINUX) && !defined(ANDROID) /* XXX i#1290/i#1701: NYI on MacOS/Android */
our_ptrace(PTRACE_DETACH, info->pid, NULL, NULL);
#else
return false;
#endif
}
/* Close the pipe. */
close(info->pipe_fd);
info->pipe_fd = 0;
}
return true;
}
DR_EXPORT
bool
dr_inject_wait_for_child(void *data, uint64 timeout_millis)
{
dr_inject_info_t *info = (dr_inject_info_t *)data;
timeout_expired = false;
if (timeout_millis > 0) {
/* Set a timer ala runstats. */
struct sigaction act;
struct itimerval timer;
/* Set an alarm handler. */
memset(&act, 0, sizeof(act));
act.sa_handler = alarm_handler;
sigaction(SIGALRM, &act, NULL);
/* No interval, one shot only. */
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
timer.it_value.tv_sec = timeout_millis / 1000;
timer.it_value.tv_usec = (timeout_millis % 1000) * 1000;
setitimer(ITIMER_REAL, &timer, NULL);
}
if (info->method != INJECT_PTRACE) {
pid_t res;
do {
res = waitpid(info->pid, &info->exitcode, 0);
} while (res != info->pid && res != -1 &&
/* The signal handler sets this and makes waitpid return EINTR. */
!timeout_expired);
info->exited = (res == info->pid);
} else {
bool exit = false;
struct timespec t;
t.tv_sec = 1;
t.tv_nsec = 0L;
do {
/* At this point dr_inject_process_run has called PTRACE_DETACH
* For non-child target, we should poll for its exit.
* There is no standard way of getting non-child target process' exit code.
*/
if (kill(info->pid, 0) == -1) {
if (errno == ESRCH)
exit = true;
}
/* sleep might not be implemented using nanosleep */
nanosleep(&t, 0);
} while (!exit && !timeout_expired);
info->exitcode = 0;
info->exited = (exit != false);
}
return info->exited;
}
DR_EXPORT
int
dr_inject_process_exit(void *data, bool terminate)
{
dr_inject_info_t *info = (dr_inject_info_t *)data;
int status = 0;
if (info->exited) {
/* If it already exited when we waited on it above, then we *cannot*
* wait on it again or try to kill it, or we might target some new
* process with the same pid.
*/
status = WEXITSTATUS(info->exitcode);
} else if (info->exec_self) {
status = -1; /* We never injected, must have been some other error. */
} else if (terminate) {
/* We use SIGKILL to match Windows, which doesn't provide the app a
* chance to clean up.
*/
if (info->killpg) {
/* i#501: Kill app subprocesses to prevent hangs. */
killpg(info->pid, SIGKILL);
} else {
kill(info->pid, SIGKILL);
}
/* Do a blocking wait to get the real status code. This shouldn't take
* long since we just sent an unblockable SIGKILL.
* Return immediately if we are under INJECT_PTRACE because we can't wait
* for detached non-child process.
*/
if (info->method != INJECT_PTRACE)
waitpid(info->pid, &status, 0);
else
status = WEXITSTATUS(info->exitcode);
} else {
/* Use WNOHANG to match our Windows semantics, which does not block if
* the child hasn't exited. The status returned is probably not useful,
* but the caller shouldn't look at it if they haven't waited for the
* app to terminate.
* Return immediately if we are under INJECT_PTRACE because we can't wait
* for detached non-child process.
*/
if (info->method != INJECT_PTRACE)
waitpid(info->pid, &status, WNOHANG);
else
status = WEXITSTATUS(info->exitcode);
}
if (info->pipe_fd != 0)
close(info->pipe_fd);
free(info);
return status;
}
#if defined(LINUX) && !defined(ANDROID) /* XXX i#1290/i#1701: NYI on MacOS/Android */
/*******************************************************************************
* ptrace injection code
*/
enum { MAX_SHELL_CODE = 4096 };
# ifdef DR_HOST_X86
# define USER_REGS_TYPE user_regs_struct
# define REG_PC_FIELD IF_X64_ELSE(rip, eip)
# define REG_SP_FIELD IF_X64_ELSE(rsp, esp)
# define REG_DI_FIELD IF_X64_ELSE(rdi, edi)
# define REG_RETVAL_FIELD IF_X64_ELSE(rax, eax)
# elif defined(DR_HOST_ARM)
/* On AArch32, glibc uses user_regs instead of user_regs_struct.
* struct user_regs {
* unsigned long int uregs[18];
* };
* - uregs[0..15] are for [r0..r15],
* - uregs[16] is for cpsr,
* - uregs[17] is for "orig_r0".
*/
# define USER_REGS_TYPE user_regs
# define REG_PC_FIELD uregs[15] /* r15 in user_regs */
# define REG_SP_FIELD uregs[13] /* r13 in user_regs */
/* On ARM, all reg args are also reg retvals. */
# define REG_RETVAL_FIELD uregs[0] /* r0 in user_regs */
# elif defined(DR_HOST_AARCH64)
# define USER_REGS_TYPE user_pt_regs
# define REG_PC_FIELD pc
# define REG_SP_FIELD sp
# define REG_RETVAL_FIELD regs[0] /* x0 in user_regs_struct */
# elif defined(DR_HOST_RISCV64)
# define USER_REGS_TYPE user_regs_struct
# define REG_PC_FIELD pc
# define REG_SP_FIELD sp
# define REG_RETVAL_FIELD a0
# endif
enum { REG_PC_OFFSET = offsetof(struct USER_REGS_TYPE, REG_PC_FIELD) };
# define APP instrlist_append
# define PRE instrlist_prepend
/* XXX: Ideally we'd use syscall_instr_length() in arch.c but that requires some
* movement or refactoring to get it into a common file/library.
*/
static inline size_t
system_call_length(dr_isa_mode_t mode)
{
# if defined(X86)
ASSERT(INT_LENGTH == SYSCALL_LENGTH);
ASSERT(SYSENTER_LENGTH == SYSCALL_LENGTH);
return SYSCALL_LENGTH;
# elif defined(AARCH64)
return SVC_LENGTH;
# elif defined(ARM)
return mode == DR_ISA_ARM_THUMB ? SVC_THUMB_LENGTH : SVC_ARM_LENGTH;
# elif defined(RISCV64)
return SYSCALL_LENGTH;
# else
# error Unsupported arch.
# endif
}
# if defined(X86)
/* X86s are little endian */
enum { SYSCALL_AS_SHORT = 0x050f, SYSENTER_AS_SHORT = 0x340f, INT80_AS_SHORT = 0x80cd };
# elif defined(AARCH64)
enum { SVC_RAW = 0xd4000001 };
# elif defined(ARM)
/* XXX: The arm one *could* have a predicate so the 1st nibble could be <0xe. */
enum { SVC_ARM_RAW = 0xef000000, SVC_THUMB_RAW = 0xdf00 };
# endif
enum { ERESTARTSYS = 512, ERESTARTNOINTR = 513, ERESTARTNOHAND = 514 };
static bool op_exec_gdb = false;
/* Used to pass data into the remote mapping routines. */
static dr_inject_info_t *injector_info;
static file_t injector_dr_fd;
static file_t injectee_dr_fd;
typedef struct _enum_name_pair_t {
const int enum_val;
const char *const enum_name;
} enum_name_pair_t;
/* Ptrace request enum name mapping. The complete enumeration is in
* sys/ptrace.h.
*/
static const enum_name_pair_t pt_req_map[] = { { PTRACE_TRACEME, "PTRACE_TRACEME" },
{ PTRACE_PEEKTEXT, "PTRACE_PEEKTEXT" },
{ PTRACE_PEEKDATA, "PTRACE_PEEKDATA" },
{ PTRACE_POKETEXT, "PTRACE_POKETEXT" },
{ PTRACE_POKEDATA, "PTRACE_POKEDATA" },
{ PTRACE_CONT, "PTRACE_CONT" },
{ PTRACE_KILL, "PTRACE_KILL" },
{ PTRACE_SINGLESTEP, "PTRACE_SINGLESTEP" },
# ifdef DR_HOST_AARCH64
{ PTRACE_GETREGSET, "PTRACE_GETREGSET" },
{ PTRACE_SETREGSET, "PTRACE_SETREGSET" },
# else
{ PTRACE_PEEKUSER, "PTRACE_PEEKUSER" },
{ PTRACE_POKEUSER, "PTRACE_POKEUSER" },
{ PTRACE_GETREGS, "PTRACE_GETREGS" },
{ PTRACE_SETREGS, "PTRACE_SETREGS" },
{ PTRACE_GETFPREGS, "PTRACE_GETFPREGS" },
{ PTRACE_SETFPREGS, "PTRACE_SETFPREGS" },
# endif
{ PTRACE_ATTACH, "PTRACE_ATTACH" },
{ PTRACE_DETACH, "PTRACE_DETACH" },
# if defined(PTRACE_GETFPXREGS) && defined(PTRACE_SETFPXREGS)
{ PTRACE_GETFPXREGS, "PTRACE_GETFPXREGS" },
{ PTRACE_SETFPXREGS, "PTRACE_SETFPXREGS" },
# endif
{ PTRACE_SYSCALL, "PTRACE_SYSCALL" },
{ PTRACE_SETOPTIONS, "PTRACE_SETOPTIONS" },
{ PTRACE_GETEVENTMSG,
"PTRACE_GETEVENTMSG" },
{ PTRACE_GETSIGINFO, "PTRACE_GETSIGINFO" },
{ PTRACE_SETSIGINFO, "PTRACE_SETSIGINFO" },
{ 0 } };
/* Ptrace syscall wrapper, for logging.
* XXX: We could call libc's ptrace instead of using dynamorio_syscall.
* Initially I used the raw syscall to avoid adding a libc import, but calling
* libc from the injector process should always work.
*/
static long
our_ptrace(int request, pid_t pid, void *addr, void *data)
{
long r = dynamorio_syscall(SYS_ptrace, 4, request, pid, addr, data);