utrace: Trace processes across forks
authorMichal Sojka <sojkam1@fel.cvut.cz>
Tue, 12 Sep 2017 11:12:38 +0000 (13:12 +0200)
committerJohn Crispin <john@phrozen.org>
Thu, 28 Sep 2017 06:26:56 +0000 (08:26 +0200)
Without this change, utrace can trace only a single process. When the
process forks, syscalls of its children do not appear in utrace
output. This is a problem, because seccomp filters are inherited by
children and therefore filters generated by utrace may lack legitimate
syscalls.

This commit enables utrace to trace processes across forks. The
functionality can be demonstrated by the following examples:

    utrace /bin/touch /tmp/xxx

produces:

    {
            "whitelist": [
                    "rt_sigaction",
                    "rt_sigreturn",
                    "exit",
                    "getuid",
                    "exit_group",
                    "utimensat"
            ],
            "policy": 1
    }

The command:

    utrace /bin/sh -c 'touch /tmp/xxx'

without this commit produces:

    {
            "whitelist": [
                    "stat",
                    "rt_sigaction",
                    "rt_sigprocmask",
                    "rt_sigreturn",
                    "getpid",
                    "fork",
                    "exit",
                    "wait4",
                    "uname",
                    "getcwd",
                    "getuid",
                    "getppid",
                    "exit_group"
            ],
            "policy": 1
    }

but with this commit, the output is the following:

    {
            "whitelist": [
                    "read",
                    "open",
                    "close",
                    "stat",
                    "fstat",
                    "mmap",
                    "mprotect",
                    "rt_sigaction",
                    "rt_sigprocmask",
                    "rt_sigreturn",
                    "getpid",
                    "fork",
                    "execve",
                    "exit",
                    "wait4",
                    "uname",
                    "fcntl",
                    "getcwd",
                    "getuid",
                    "getppid",
                    "arch_prctl",
                    "gettid",
                    "set_tid_address",
                    "exit_group",
                    "utimensat"
            ],
            "policy": 1
    }

Note that in addition to utimensat syscall from touch, this output
contains more syscalls than what is in the union of single-process sh
and touch traces. The reason is that single-process traces do not
include syscalls from dynamic linker (due to preload trick), but the
trace of forked processes includes the dynamic linker syscalls. This
is unavoidable, because dynamic linker of the forked processes will be
subject to seccomp filters of the parent process.

Signed-off-by: Michal Sojka <sojkam1@fel.cvut.cz>
trace/trace.c

index 35bc548d08ac1f2c4782920135ab0c06157aae4a..f882c2e0d5a14a2d5e110634d9456e87eb3c4daf 100644 (file)
        fprintf(stderr, "utrace: "fmt, ## __VA_ARGS__); \
 } while (0)
 
-static struct uloop_process tracer;
+struct tracee {
+       struct uloop_process proc;
+       int in_syscall;
+};
+
+static struct tracee tracer;
 static int *syscall_count;
 static struct blob_buf b;
 static int syscall_max;
-static int in_syscall;
 static int debug;
 
 static int max_syscall = ARRAY_SIZE(syscall_names);
@@ -143,25 +147,45 @@ static void print_syscalls(int policy, const char *json)
 
 static void tracer_cb(struct uloop_process *c, int ret)
 {
-       if (WIFSTOPPED(ret) && WSTOPSIG(ret) & 0x80) {
-               if (!in_syscall) {
-                       int syscall = ptrace(PTRACE_PEEKUSER, c->pid, reg_syscall_nr);
-
-                       if (syscall < syscall_max) {
-                               syscall_count[syscall]++;
-                               if (debug)
-                                       fprintf(stderr, "%s()\n", syscall_names[syscall]);
-                       } else if (debug) {
-                               fprintf(stderr, "syscal(%d)\n", syscall);
+       struct tracee *tracee = container_of(c, struct tracee, proc);
+
+       if (WIFSTOPPED(ret)) {
+               if (WSTOPSIG(ret) & 0x80) {
+                       if (!tracee->in_syscall) {
+                               int syscall = ptrace(PTRACE_PEEKUSER, c->pid, reg_syscall_nr);
+
+                               if (syscall < syscall_max) {
+                                       syscall_count[syscall]++;
+                                       if (debug)
+                                               fprintf(stderr, "%s()\n", syscall_names[syscall]);
+                               } else if (debug) {
+                                       fprintf(stderr, "syscal(%d)\n", syscall);
+                               }
                        }
+                       tracee->in_syscall = !tracee->in_syscall;
+               } else if ((ret >> 8) == (SIGTRAP | (PTRACE_EVENT_FORK << 8))) {
+                       struct tracee *child = calloc(1, sizeof(struct tracee));
+
+                       ptrace(PTRACE_GETEVENTMSG, c->pid, 0, &child->proc.pid);
+                       child->proc.cb = tracer_cb;
+                       ptrace(PTRACE_SYSCALL, child->proc.pid, 0, 0);
+                       uloop_process_add(&child->proc);
+                       if (debug)
+                               fprintf(stderr, "Tracing new child %d\n", child->proc.pid);
                }
-               in_syscall = !in_syscall;
        } else if (WIFEXITED(ret)) {
-               uloop_end();
+               if (tracee == &tracer) {
+                       uloop_end(); /* Main process exit */
+               } else {
+                       if (debug)
+                               fprintf(stderr, "Child %d exited\n", tracee->proc.pid);
+                       free(tracee);
+               }
                return;
        }
+
        ptrace(PTRACE_SYSCALL, c->pid, 0, 0);
-       uloop_process_add(&tracer);
+       uloop_process_add(c);
 }
 
 int main(int argc, char **argv, char **envp)
@@ -228,13 +252,15 @@ int main(int argc, char **argv, char **envp)
                return -1;
        }
 
-       ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);
+       ptrace(PTRACE_SETOPTIONS, child, 0,
+              PTRACE_O_TRACESYSGOOD |
+              PTRACE_O_TRACEFORK);
        ptrace(PTRACE_SYSCALL, child, 0, 0);
 
        uloop_init();
-       tracer.pid = child;
-       tracer.cb = tracer_cb;
-       uloop_process_add(&tracer);
+       tracer.proc.pid = child;
+       tracer.proc.cb = tracer_cb;
+       uloop_process_add(&tracer.proc);
        uloop_run();
        uloop_done();