[LEDE-DEV,procd,06/17] utrace: Trace processes across forks

Message ID 20170912111250.31576-9-sojkam1@fel.cvut.cz
State Accepted
Headers show
Series
  • [LEDE-DEV,procd,01/17] utrace: Fix environment initialization
Related show

Commit Message

Michal Sojka Sept. 12, 2017, 11:12 a.m.
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 | 64 +++++++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 45 insertions(+), 19 deletions(-)

Patch

diff --git a/trace/trace.c b/trace/trace.c
index 35bc548..f882c2e 100644
--- a/trace/trace.c
+++ b/trace/trace.c
@@ -57,11 +57,15 @@ 
 	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();