forked from checkpoint-restore/criu
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ptrace.c
237 lines (204 loc) · 5.24 KB
/
ptrace.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
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>
#include <limits.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include "compiler.h"
#include "asm/types.h"
#include "util.h"
#include "ptrace.h"
#include "proc_parse.h"
#include "crtools.h"
#include "security.h"
int unseize_task(pid_t pid, int orig_st, int st)
{
pr_debug("\tUnseizing %d into %d\n", pid, st);
if (st == TASK_DEAD)
kill(pid, SIGKILL);
else if (st == TASK_STOPPED) {
if (orig_st == TASK_ALIVE)
kill(pid, SIGSTOP);
} else if (st == TASK_ALIVE)
/* do nothing */ ;
else
pr_err("Unknown final state %d\n", st);
return ptrace(PTRACE_DETACH, pid, NULL, NULL);
}
/*
* This routine seizes task putting it into a special
* state where we can manipulate the task via ptrace
* interface, and finally we can detach ptrace out of
* of it so the task would not know if it was saddled
* up with someone else.
*/
int seize_task(pid_t pid, pid_t ppid)
{
siginfo_t si;
int status;
int ret, ret2, ptrace_errno, wait_errno = 0;
struct proc_status_creds cr;
ret = ptrace(PTRACE_SEIZE, pid, NULL, 0);
ptrace_errno = errno;
if (ret == 0) {
/*
* If we SEIZE-d the task stop it before going
* and reading its stat from proc. Otherwise task
* may die _while_ we're doing it and we'll have
* inconsistent seize/state pair.
*
* If task dies after we seize it but before we
* do this interrupt, we'll notice it via proc.
*/
ret = ptrace(PTRACE_INTERRUPT, pid, NULL, NULL);
if (ret < 0) {
pr_perror("SEIZE %d: can't interrupt task", pid);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
goto err;
}
}
/*
* It's ugly, but the ptrace API doesn't allow to distinguish
* attaching to zombie from other errors. Thus we have to parse
* the target's /proc/pid/stat. Sad, but parse whatever else
* we might nead at that early point.
*/
try_again:
if (!ret) {
ret = wait4(pid, &status, __WALL, NULL);
wait_errno = errno;
}
ret2 = parse_pid_status(pid, &cr);
if (ret2)
goto err;
if (!may_dump(&cr)) {
pr_err("Check uid (pid: %d) failed\n", pid);
goto err;
}
if (ret < 0) {
if (cr.state != 'Z') {
if (pid == getpid())
pr_err("The criu itself is within dumped tree.\n");
else
pr_err("Unseizable non-zombie %d found, state %c, err %d/%d/%d\n",
pid, cr.state, ret, ptrace_errno, wait_errno);
return -1;
}
return TASK_DEAD;
}
if ((ppid != -1) && (cr.ppid != ppid)) {
pr_err("Task pid reused while suspending (%d: %d -> %d)\n",
pid, ppid, cr.ppid);
goto err;
}
if (!WIFSTOPPED(status)) {
pr_err("SEIZE %d: task not stopped after seize\n", pid);
goto err;
}
ret = ptrace(PTRACE_GETSIGINFO, pid, NULL, &si);
if (ret < 0) {
pr_perror("SEIZE %d: can't read signfo", pid);
goto err;
}
if (SI_EVENT(si.si_code) != PTRACE_EVENT_STOP) {
/*
* Kernel notifies us about the task being seized received some
* event other than the STOP, i.e. -- a signal. Let the task
* handle one and repeat.
*/
if (ptrace(PTRACE_CONT, pid, NULL,
(void *)(unsigned long)si.si_signo)) {
pr_perror("Can't continue signal handling, aborting");
goto err;
}
ret = 0;
goto try_again;
}
if (si.si_signo == SIGTRAP)
return TASK_ALIVE;
else if (si.si_signo == SIGSTOP) {
/*
* PTRACE_SEIZE doesn't affect signal or group stop state.
* Currently ptrace reported that task is in stopped state.
* We need to start task again, and it will be trapped
* immediately, because we sent PTRACE_INTERRUPT to it.
*/
ret = ptrace(PTRACE_CONT, pid, 0, 0);
if (ret) {
pr_perror("Unable to start process");
goto err_stop;
}
ret = wait4(pid, &status, __WALL, NULL);
if (ret < 0) {
pr_perror("SEIZE %d: can't wait task", pid);
goto err_stop;
}
if (ret != pid) {
pr_err("SEIZE %d: wrong task attached (%d)\n", pid, ret);
goto err_stop;
}
if (!WIFSTOPPED(status)) {
pr_err("SEIZE %d: task not stopped after seize\n", pid);
goto err_stop;
}
return TASK_STOPPED;
} else {
pr_err("SEIZE %d: unsupported stop signal %d\n", pid, si.si_signo);
goto err;
}
err_stop:
kill(pid, SIGSTOP);
err:
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return -1;
}
int ptrace_peek_area(pid_t pid, void *dst, void *addr, long bytes)
{
unsigned long w;
if (bytes & (sizeof(long) - 1))
return -1;
for (w = 0; w < bytes / sizeof(long); w++) {
unsigned long *d = dst, *a = addr;
d[w] = ptrace(PTRACE_PEEKDATA, pid, a + w, NULL);
if (d[w] == -1U && errno)
goto err;
}
return 0;
err:
return -2;
}
int ptrace_poke_area(pid_t pid, void *src, void *addr, long bytes)
{
unsigned long w;
if (bytes & (sizeof(long) - 1))
return -1;
for (w = 0; w < bytes / sizeof(long); w++) {
unsigned long *s = src, *a = addr;
if (ptrace(PTRACE_POKEDATA, pid, a + w, s[w]))
goto err;
}
return 0;
err:
return -2;
}
/* don't swap big space, it might overflow the stack */
int ptrace_swap_area(pid_t pid, void *dst, void *src, long bytes)
{
void *t = alloca(bytes);
if (ptrace_peek_area(pid, t, dst, bytes))
return -1;
if (ptrace_poke_area(pid, src, dst, bytes)) {
if (ptrace_poke_area(pid, t, dst, bytes))
return -2;
return -1;
}
memcpy(src, t, bytes);
return 0;
}