• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/procd/trace/trace.c

  1 /*
  2  * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
  3  *
  4  * This program is free software; you can redistribute it and/or modify
  5  * it under the terms of the GNU Lesser General Public License version 2.1
  6  * as published by the Free Software Foundation
  7  *
  8  * This program is distributed in the hope that it will be useful,
  9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 11  * GNU General Public License for more details.
 12  */
 13 
 14 #define _GNU_SOURCE
 15 #include <fcntl.h>
 16 #include <stddef.h>
 17 #include <sys/ptrace.h>
 18 #include <sys/stat.h>
 19 #include <sys/types.h>
 20 #include <sys/user.h>
 21 #include <sys/wait.h>
 22 #include <unistd.h>
 23 #include <stdlib.h>
 24 #include <stdio.h>
 25 #include <errno.h>
 26 #include <string.h>
 27 #include <syslog.h>
 28 #include <limits.h>
 29 
 30 #ifndef PTRACE_EVENT_STOP
 31 /* PTRACE_EVENT_STOP is defined in linux/ptrace.h, but this header
 32  * collides with musl's sys/ptrace.h */
 33 #define PTRACE_EVENT_STOP 128
 34 #endif
 35 
 36 #include <libubox/ulog.h>
 37 #include <libubox/uloop.h>
 38 #include <libubox/blobmsg.h>
 39 #include <libubox/blobmsg_json.h>
 40 
 41 #include "../syscall-names.h"
 42 
 43 #define _offsetof(a, b) __builtin_offsetof(a,b)
 44 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
 45 
 46 #ifdef __amd64__
 47 #define reg_syscall_nr  _offsetof(struct user, regs.orig_rax)
 48 #elif defined(__i386__)
 49 #define reg_syscall_nr  _offsetof(struct user, regs.orig_eax)
 50 #elif defined(__mips)
 51 # ifndef EF_REG2
 52 # define EF_REG2        8
 53 # endif
 54 #define reg_syscall_nr  (EF_REG2 / 4)
 55 #elif defined(__arm__)
 56 #include <asm/ptrace.h>         /* for PTRACE_SET_SYSCALL */
 57 #define reg_syscall_nr  _offsetof(struct user, regs.uregs[7])
 58 # if defined(__ARM_EABI__)
 59 # define reg_retval_nr  _offsetof(struct user, regs.uregs[0])
 60 # endif
 61 #elif defined(__PPC__)
 62 #define reg_syscall_nr  _offsetof(struct user, regs.gpr[0])
 63 #define reg_retval_nr   _offsetof(struct user, regs.gpr[3])
 64 #else
 65 #error tracing is not supported on this architecture
 66 #endif
 67 
 68 enum mode {
 69         UTRACE,
 70         SECCOMP_TRACE,
 71 } mode = UTRACE;
 72 
 73 struct tracee {
 74         struct uloop_process proc;
 75         int in_syscall;
 76 };
 77 
 78 static struct tracee tracer;
 79 static int syscall_count[SYSCALL_COUNT];
 80 static int violation_count;
 81 static struct blob_buf b;
 82 static int debug;
 83 char *json = NULL;
 84 int ptrace_restart;
 85 
 86 static void set_syscall(const char *name, int val)
 87 {
 88         int i;
 89 
 90         for (i = 0; i < SYSCALL_COUNT; i++) {
 91                 int sc = syscall_index_to_number(i);
 92                 if (syscall_name(sc) && !strcmp(syscall_name(sc), name)) {
 93                         syscall_count[i] = val;
 94                         return;
 95                 }
 96         }
 97 }
 98 
 99 struct syscall {
100         int syscall;
101         int count;
102 };
103 
104 static int cmp_count(const void *a, const void *b)
105 {
106         return ((struct syscall*)b)->count - ((struct syscall*)a)->count;
107 }
108 
109 static void print_syscalls(int policy, const char *json)
110 {
111         void *c;
112         int i;
113 
114         if (mode == UTRACE) {
115                 set_syscall("rt_sigaction", 1);
116                 set_syscall("sigreturn", 1);
117                 set_syscall("rt_sigreturn", 1);
118                 set_syscall("exit_group", 1);
119                 set_syscall("exit", 1);
120         }
121 
122         struct syscall sorted[SYSCALL_COUNT];
123 
124         for (i = 0; i < SYSCALL_COUNT; i++) {
125                 sorted[i].syscall = syscall_index_to_number(i);
126                 sorted[i].count = syscall_count[i];
127         }
128 
129         qsort(sorted, SYSCALL_COUNT, sizeof(sorted[0]), cmp_count);
130 
131         blob_buf_init(&b, 0);
132         c = blobmsg_open_array(&b, "whitelist");
133 
134         for (i = 0; i < SYSCALL_COUNT; i++) {
135                 int sc = sorted[i].syscall;
136                 if (!sorted[i].count)
137                         break;
138                 if (syscall_name(sc)) {
139                         if (debug)
140                                 printf("syscall %d (%s) was called %d times\n",
141                                        sc, syscall_name(sc), sorted[i].count);
142                         blobmsg_add_string(&b, NULL, syscall_name(sc));
143                 } else {
144                         ULOG_ERR("no name found for syscall(%d)\n", sc);
145                 }
146         }
147         blobmsg_close_array(&b, c);
148         blobmsg_add_u32(&b, "policy", policy);
149         if (json) {
150                 FILE *fp = fopen(json, "w");
151                 if (fp) {
152                         fprintf(fp, "%s", blobmsg_format_json_indent(b.head, true, 0));
153                         fclose(fp);
154                         ULOG_INFO("saving syscall trace to %s\n", json);
155                 } else {
156                         ULOG_ERR("failed to open %s\n", json);
157                 }
158         } else {
159                 printf("%s\n",
160                         blobmsg_format_json_indent(b.head, true, 0));
161         }
162 
163 }
164 
165 static void report_seccomp_vialation(pid_t pid, unsigned syscall)
166 {
167         char buf[200];
168         snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid);
169         int f = open(buf, O_RDONLY);
170         int r = read(f, buf, sizeof(buf) - 1);
171         if (r >= 0)
172                 buf[r] = 0;
173         else
174                 strcpy(buf, "unknown?");
175         close(f);
176 
177         if (violation_count < INT_MAX)
178                 violation_count++;
179         int i = syscall_index(syscall);
180         if (i >= 0) {
181                 syscall_count[i]++;
182                 ULOG_ERR("%s[%u] tried to call non-whitelisted syscall: %s (see %s)\n",
183                          buf, pid,  syscall_name(syscall), json);
184         } else {
185                 ULOG_ERR("%s[%u] tried to call non-whitelisted syscall: %d (see %s)\n",
186                          buf, pid,  syscall, json);
187         }
188 }
189 
190 static void tracer_cb(struct uloop_process *c, int ret)
191 {
192         struct tracee *tracee = container_of(c, struct tracee, proc);
193         int inject_signal = 0;
194 
195         /* We explicitely check for events in upper 16 bits, because
196          * musl (as opposed to glibc) does not report
197          * PTRACE_EVENT_STOP as WIFSTOPPED */
198         if (WIFSTOPPED(ret) || (ret >> 16)) {
199                 if (WSTOPSIG(ret) & 0x80) {
200                         if (!tracee->in_syscall) {
201                                 int syscall = ptrace(PTRACE_PEEKUSER, c->pid, reg_syscall_nr);
202                                 int i = syscall_index(syscall);
203                                 if (i >= 0) {
204                                         syscall_count[i]++;
205                                         if (debug)
206                                                 fprintf(stderr, "%s()\n", syscall_name(syscall));
207                                 } else if (debug) {
208                                         fprintf(stderr, "syscal(%d)\n", syscall);
209                                 }
210                         }
211                         tracee->in_syscall = !tracee->in_syscall;
212                 } else if ((ret >> 8) == (SIGTRAP | (PTRACE_EVENT_FORK << 8)) ||
213                            (ret >> 8) == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) ||
214                            (ret >> 8) == (SIGTRAP | (PTRACE_EVENT_CLONE << 8))) {
215                         struct tracee *child = calloc(1, sizeof(struct tracee));
216 
217                         unsigned long msg;
218                         ptrace(PTRACE_GETEVENTMSG, c->pid, 0, &msg);
219                         child->proc.pid = msg;
220                         child->proc.cb = tracer_cb;
221                         ptrace(ptrace_restart, child->proc.pid, 0, 0);
222                         uloop_process_add(&child->proc);
223                         if (debug)
224                                 fprintf(stderr, "Tracing new child %d\n", child->proc.pid);
225                 } else if ((ret >> 16) == PTRACE_EVENT_STOP) {
226                         /* Nothing special to do here */
227                 } else if ((ret >> 8) == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) {
228                         int syscall = ptrace(PTRACE_PEEKUSER, c->pid, reg_syscall_nr);
229 #if defined(__arm__)
230                         ptrace(PTRACE_SET_SYSCALL, c->pid, 0, -1);
231                         ptrace(PTRACE_POKEUSER, c->pid, reg_retval_nr, -ENOSYS);
232 #else
233                         ptrace(PTRACE_POKEUSER, c->pid, reg_syscall_nr, -1);
234 #endif
235                         report_seccomp_vialation(c->pid, syscall);
236                 } else {
237                         inject_signal = WSTOPSIG(ret);
238                         if (debug)
239                                 fprintf(stderr, "Injecting signal %d into pid %d\n",
240                                         inject_signal, tracee->proc.pid);
241                 }
242         } else if (WIFEXITED(ret) || (WIFSIGNALED(ret) && WTERMSIG(ret))) {
243                 if (tracee == &tracer) {
244                         uloop_end(); /* Main process exit */
245                 } else {
246                         if (debug)
247                                 fprintf(stderr, "Child %d exited\n", tracee->proc.pid);
248                         free(tracee);
249                 }
250                 return;
251         }
252 
253         ptrace(ptrace_restart, c->pid, 0, inject_signal);
254         uloop_process_add(c);
255 }
256 
257 static void sigterm_handler(int signum)
258 {
259         /* When we receive SIGTERM, we forward it to the tracee. After
260          * the tracee exits, trace_cb() will be called and make us
261          * exit too. */
262         kill(tracer.proc.pid, SIGTERM);
263 }
264 
265 
266 int main(int argc, char **argv, char **envp)
267 {
268         int status, ch, policy = EPERM;
269         pid_t child;
270 
271         /* When invoked via seccomp-trace symlink, work as seccomp
272          * violation logger rather than as syscall tracer */
273         if (strstr(argv[0], "seccomp-trace"))
274                 mode = SECCOMP_TRACE;
275 
276         while ((ch = getopt(argc, argv, "f:p:")) != -1) {
277                 switch (ch) {
278                 case 'f':
279                         json = optarg;
280                         break;
281                 case 'p':
282                         policy = atoi(optarg);
283                         break;
284                 }
285         }
286 
287         if (!json)
288                 json = getenv("SECCOMP_FILE");
289 
290         argc -= optind;
291         argv += optind;
292 
293         if (!argc)
294                 return -1;
295 
296         if (getenv("TRACE_DEBUG"))
297                 debug = 1;
298         unsetenv("TRACE_DEBUG");
299 
300         child = fork();
301 
302         if (child == 0) {
303                 char **_argv = calloc(argc + 1, sizeof(char *));
304                 char **_envp;
305                 char *preload = NULL;
306                 const char *old_preload = getenv("LD_PRELOAD");
307                 int newenv = 0;
308                 int envc = 0;
309                 int ret;
310 
311                 memcpy(_argv, argv, argc * sizeof(char *));
312 
313                 while (envp[envc++])
314                         ;
315 
316                 _envp = calloc(envc + 2, sizeof(char *));
317                 switch (mode) {
318                 case UTRACE:
319                         preload = "/lib/libpreload-trace.so";
320                         newenv = 1;
321                         break;
322                 case SECCOMP_TRACE:
323                         preload = "/lib/libpreload-seccomp.so";
324                         newenv = 2;
325                         if (asprintf(&_envp[1], "SECCOMP_FILE=%s", json ? json : "") < 0)
326                                 ULOG_ERR("failed to allocate SECCOMP_FILE env: %m\n");
327 
328                         kill(getpid(), SIGSTOP);
329                         break;
330                 }
331                 if (asprintf(&_envp[0], "LD_PRELOAD=%s%s%s", preload,
332                              old_preload ? ":" : "",
333                               old_preload ? old_preload : "") < 0)
334                         ULOG_ERR("failed to allocate LD_PRELOAD env: %m\n");
335 
336                 memcpy(&_envp[newenv], envp, envc * sizeof(char *));
337 
338                 ret = execve(_argv[0], _argv, _envp);
339                 ULOG_ERR("failed to exec %s: %m\n", _argv[0]);
340 
341                 free(_argv);
342                 free(_envp);
343                 return ret;
344         }
345 
346         if (child < 0)
347                 return -1;
348 
349         waitpid(child, &status, WUNTRACED);
350         if (!WIFSTOPPED(status)) {
351                 ULOG_ERR("failed to start %s\n", *argv);
352                 return -1;
353         }
354 
355         /* Initialize uloop to catch all ptrace stops from now on. */
356         uloop_init();
357 
358         int ptrace_options = PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE;
359         switch (mode) {
360         case UTRACE:
361                 ptrace_options |= PTRACE_O_TRACESYSGOOD;
362                 ptrace_restart = PTRACE_SYSCALL;
363                 break;
364         case SECCOMP_TRACE:
365                 ptrace_options |= PTRACE_O_TRACESECCOMP;
366                 ptrace_restart = PTRACE_CONT;
367                 break;
368         }
369         if (ptrace(PTRACE_SEIZE, child, 0, ptrace_options) == -1) {
370                 ULOG_ERR("PTRACE_SEIZE: %m\n");
371                 return -1;
372         }
373         if (ptrace(ptrace_restart, child, 0, SIGCONT) == -1) {
374                 ULOG_ERR("ptrace_restart: %m\n");
375                 return -1;
376         }
377 
378         tracer.proc.pid = child;
379         tracer.proc.cb = tracer_cb;
380         uloop_process_add(&tracer.proc);
381         signal(SIGTERM, sigterm_handler); /* Override uloop's SIGTERM handler */
382         uloop_run();
383         uloop_done();
384 
385 
386         switch (mode) {
387         case UTRACE:
388                 if (!json)
389                         if (asprintf(&json, "/tmp/%s.%u.json", basename(*argv), child) < 0)
390                                 ULOG_ERR("failed to allocate output path: %m\n");
391                 break;
392         case SECCOMP_TRACE:
393                 if (!violation_count)
394                         return 0;
395                 if (asprintf(&json, "/tmp/%s.%u.violations.json", basename(*argv), child) < 0)
396                         ULOG_ERR("failed to allocate violations output path: %m\n");
397                 break;
398         }
399         print_syscalls(policy, json);
400         return 0;
401 }
402 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt