-
Notifications
You must be signed in to change notification settings - Fork 252
/
syscall_linux.c
323 lines (300 loc) · 11.4 KB
/
syscall_linux.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
/* **********************************************************
* Copyright (c) 2010-2017 Google, Inc. All rights reserved.
* Copyright (c) 2009-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/* Dr. Memory: the memory debugger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License, and no later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "dr_api.h"
#include "drmemory.h"
#include "syscall.h"
#include "syscall_os.h"
#include "shadow.h"
#include "slowpath.h"
#include "sysnum_linux.h"
#include "alloc_drmem.h"
#include "alloc.h"
#include "heap.h"
#include "stack.h"
#include "report.h"
#include <linux/sched.h>
#include <sys/prctl.h>
/***************************************************************************
* SYSTEM CALLS FOR LINUX
*/
void
syscall_os_init(void *drcontext)
{
}
void
syscall_os_exit(void)
{
}
void
syscall_os_thread_init(void *drcontext)
{
}
void
syscall_os_thread_exit(void *drcontext)
{
}
void
syscall_os_module_load(void *drcontext, const module_data_t *info, bool loaded)
{
}
/***************************************************************************
* PER-SYSCALL HANDLING
*/
bool
os_process_syscall_memarg(drsys_arg_t *arg)
{
return false; /* not handled */
}
static void
handle_clone(void *drcontext, dr_mcontext_t *mc)
{
uint flags = (uint) dr_syscall_get_param(drcontext, 0);
app_pc newsp = (app_pc) dr_syscall_get_param(drcontext, 1);
if (!options.shadowing)
return;
/* PR 418629: we need to change the stack from defined (marked when it
* was allocated) to unaddressable. Originally we couldn't get the stack
* bounds in the thread init event (xref PR 395156) so we watch here:
* we could move this code now but not worth it.
* FIXME: should we watch SYS_exit and put stack back to defined
* in case it's re-used? Seems better to leave it unaddressable
* since may be more common to have racy accesses we want to flag
* rather than legitimate re-use?
*/
if (TEST(CLONE_VM, flags) && newsp != NULL) {
app_pc stack_base = NULL;
size_t stack_size;
/* newsp is TOS */
ASSERT(options.track_heap, "now relying on -track_heap in general");
if (is_in_heap_region(newsp)) {
/* How find base of malloc chunk to then find size?
* Don't want to store all mallocs in an interval data structure
* (shown to be slow in PR 535568).
* Maybe hardcode knowledge of how far from upper address
* glibc clone() sets newsp?
* Actually, should just walk shadow memory until hit
* unaddressable.
*/
/* FIXME: NEVER TESTED! */
app_pc pc;
ssize_t sz;
/* PR 525807 added an interval tree of "large mallocs" */
if (malloc_large_lookup(newsp, &pc, (size_t*)&sz)) {
stack_base = pc;
stack_size = sz;
} else {
/* Should be rare so we just do brute force and slow */
pc = shadow_prev_dword(newsp, newsp - options.stack_swap_threshold,
SHADOW_UNADDRESSABLE);
sz = malloc_chunk_size(pc+1);
if (sz > 0) { /* returns -1 on failure */
stack_base = pc + 1;
stack_size = sz;
}
}
} else {
/* On linux a pre-adjacent mmap w/ same prot will be merged into the
* same region as returned by dr_query_memory() and we'll mark it as
* unaddressable => many false positives (on FC10, adding a printf
* to suite/tests/linux/clone.c between the stack mmap and the clone
* call resulted in the merge). My solution is to track mmaps and
* assume a stack will be a single mmap (maybe separate guard page
* but that should be noprot so ok to not mark unaddress: xref PR
* 406328).
*/
if (!mmap_anon_lookup(newsp, &stack_base, &stack_size)) {
/* Fall back to a query */
LOG(2, "thread stack "PFX" not in mmap table, querying\n", newsp);
if (!dr_query_memory(newsp - 1, &stack_base, &stack_size, NULL)) {
/* We can estimate the stack end by assuming that clone()
* puts less than a page on the stack, but the base is harder:
* instead we rely on PR 525807 handle_push_addressable() to
* mark the stack unaddr one page at a time.
*/
stack_base = NULL;
}
}
}
if (stack_base != NULL) {
LOG(2, "changing thread stack "PFX"-"PFX" -"PFX" to unaddressable\n",
stack_base, stack_base + stack_size, newsp);
ASSERT(stack_base + stack_size >= newsp,
"new thread's stack alloc messed up");
if (options.check_stack_bounds) {
/* assume that above newsp should stay defined */
shadow_set_range(stack_base, newsp, SHADOW_UNADDRESSABLE);
check_stack_size_vs_threshold(drcontext, stack_size);
if (BEYOND_TOS_REDZONE_SIZE > 0) {
size_t redzone_sz = BEYOND_TOS_REDZONE_SIZE;
if (newsp - BEYOND_TOS_REDZONE_SIZE < stack_base)
redzone_sz = newsp - stack_base;
shadow_set_range(newsp - redzone_sz, newsp, SHADOW_UNDEFINED);
}
}
} else {
LOG(0, "ERROR: cannot find bounds of new thread's stack "PFX"\n",
newsp);
ASSERT(false, "can't find bounds of thread's stack");
}
}
}
#define PRCTL_NAME_SZ 16 /* from man page */
static void
check_prctl_whitelist(byte *prctl_arg1)
{
/* disable instrumentation on seeing prctl(PR_SET_NAME) that does not
* match any of the specified ,-separated names (PR 574018)
*/
char nm[PRCTL_NAME_SZ+1];
ASSERT(options.prctl_whitelist[0] != '\0', "caller should check for empty op");
if (safe_read(prctl_arg1, PRCTL_NAME_SZ, nm)) {
bool on_whitelist = false;
char *s, *next;
char *list_end = options.prctl_whitelist + strlen(options.prctl_whitelist);
size_t white_sz;
NULL_TERMINATE_BUFFER(nm);
LOG(1, "prctl set name %s\n", nm);
s = options.prctl_whitelist;
while (s < list_end) {
next = strchr(s, ',');
if (next == NULL)
white_sz = (list_end - s);
else
white_sz = (next - s);
LOG(2, "comparing \"%s\" with whitelist entry \"%.*s\" sz=%d\n",
nm, white_sz, s, white_sz);
if (strncmp(nm, s, white_sz) == 0) {
LOG(0, "prctl name %s matches whitelist\n", nm);
on_whitelist = true;
break;
}
s += white_sz + 1 /* skip , itself */;
}
if (!on_whitelist) {
/* ideally: suspend world, then set options, then flush
* w/o resuming.
* FIXME: just setting options is unsafe if another thread
* hits an event and fails to restore state or sthg.
* Fortunately we expect most uses of PR_SET_NAME to be
* immediately after forking.
* Ideally we'd call dr_suspend_all_other_threads()
* and nest dr_flush_region() inside it but both want
* the same master lock: should check whether easy to support
* via internal vars indicating whether lock held.
*/
ELOGF(0, f_global, "\n*********\nDISABLING MEMORY CHECKING for %s\n", nm);
options.shadowing = false;
options.track_allocs = false;
options.count_leaks = false;
dr_flush_region(0, ~((ptr_uint_t)0));
}
}
}
static void
handle_pre_prctl(void *drcontext, dr_mcontext_t *mc)
{
uint request = (uint) dr_syscall_get_param(drcontext, 0);
ptr_int_t arg1 = (ptr_int_t) dr_syscall_get_param(drcontext, 1);
/* They all use param #0, which is checked via table specifying 1 arg.
* Officially it's a 5-arg syscall but so far nothing using beyond 2 args.
*/
/* XXX: could use SYSINFO_SECONDARY_TABLE instead */
switch (request) {
case PR_SET_NAME:
case PR_GET_NAME:
if (request == PR_SET_NAME && options.prctl_whitelist[0] != '\0')
check_prctl_whitelist((byte *)arg1);
break;
}
}
static void
handle_pre_execve(void *drcontext)
{
#ifndef USE_DRSYMS
/* PR 453867: tell postprocess.pl to watch for new logdir and
* fork a new copy.
* FIXME: what if syscall fails? Punting on that for now.
* Note that if it fails and then a later one succeeds, postprocess.pl
* will replace the first with the last.
*/
char logdir[MAXIMUM_PATH]; /* one reason we're not inside os_post_syscall() */
size_t bytes_read = 0;
/* Not using safe_read() since we want a partial read if hits page boundary */
dr_safe_read((void *) dr_syscall_get_param(drcontext, 0),
BUFFER_SIZE_BYTES(logdir), logdir, &bytes_read);
if (bytes_read < BUFFER_SIZE_BYTES(logdir))
logdir[bytes_read] = '\0';
NULL_TERMINATE_BUFFER(logdir);
ELOGF(0, f_fork, "EXEC path=%s\n", logdir);
#endif
}
/* for tasks unrelated to shadowing that are common to all tools */
bool
os_shared_pre_syscall(void *drcontext, cls_syscall_t *pt, drsys_sysnum_t sysnum,
dr_mcontext_t *mc, drsys_syscall_t *syscall)
{
bool res = true;
switch (sysnum.number) {
case SYS_close: {
/* DRi#357 has DR isolating our files for us, so nothing to do here anymore */
break;
}
case SYS_execve: {
handle_pre_execve(drcontext);
break;
}
case SYS_clone: {
handle_clone(drcontext, mc);
break;
}
case SYS_prctl: {
handle_pre_prctl(drcontext, mc);
break;
}
}
return res;
}
/* for tasks unrelated to shadowing that are common to all tools */
void
os_shared_post_syscall(void *drcontext, cls_syscall_t *pt, drsys_sysnum_t sysnum,
dr_mcontext_t *mc, drsys_syscall_t *syscall)
{
switch (sysnum.number) {
case SYS_clone: {
uint flags = (uint) syscall_get_param(drcontext, 0);
if (TEST(CLONE_VM, flags)) {
thread_id_t child = dr_syscall_get_result(drcontext);
report_child_thread(drcontext, child);
break;
}
/* else, fall through */
}
case SYS_fork: {
#ifndef USE_DRSYMS
/* PR 453867: tell postprocess.pl to not exit until it sees a message
* from the child starting up.
*/
process_id_t child = dr_syscall_get_result(drcontext);
if (child != 0)
ELOGF(0, f_fork, "FORK child=%d\n", child);
#endif
break;
}
}
}