Use a systrace(4) sandbox with a short whitelist of allowed syscalls for
authornicm <nicm@openbsd.org>
Mon, 27 Apr 2015 13:52:17 +0000 (13:52 +0000)
committernicm <nicm@openbsd.org>
Mon, 27 Apr 2015 13:52:17 +0000 (13:52 +0000)
the file(1) child process. Based on similar code in ssh sandbox-systrace.c.
Idea and help from deraadt@.

usr.bin/file/Makefile
usr.bin/file/file.c
usr.bin/file/file.h
usr.bin/file/sandbox.c [new file with mode: 0644]

index 3fb202b..5ce922e 100644 (file)
@@ -1,8 +1,8 @@
-# $OpenBSD: Makefile,v 1.14 2015/04/27 13:41:45 nicm Exp $
+# $OpenBSD: Makefile,v 1.15 2015/04/27 13:52:17 nicm Exp $
 
 PROG=   file
-SRCS=   file.c magic-dump.c magic-load.c magic-test.c magic-common.c text.c \
-       xmalloc.c
+SRCS=   file.c magic-dump.c magic-load.c magic-test.c magic-common.c sandbox.c \
+       text.c xmalloc.c
 MAN=   file.1 magic.5
 
 LDADD= -lutil
index 43dd000..7f7cabe 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: file.c,v 1.35 2015/04/27 13:41:45 nicm Exp $ */
+/* $OpenBSD: file.c,v 1.36 2015/04/27 13:52:17 nicm Exp $ */
 
 /*
  * Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org>
@@ -186,16 +186,15 @@ main(int argc, char **argv)
        }
        if (magicfp == NULL)
                err(1, "%s", magicpath);
+       setvbuf(magicfp, NULL, _IOLBF, 0); /* stops stdio calling fstat */
 
        parent = getpid();
        if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0)
                err(1, "socketpair");
-       switch (pid = fork()) {
-       case 0:
+       pid = sandbox_fork(FILE_USER);
+       if (pid == 0) {
                close(pair[0]);
                child(pair[1], parent, argc, argv);
-       case -1:
-               err(1, "fork");
        }
        close(pair[1]);
 
@@ -329,19 +328,6 @@ child(int fd, pid_t parent, int argc, char **argv)
        struct input_file        inf;
        int                      i, idx;
        size_t                   len, width = 0;
-       struct passwd           *pw;
-
-       if (geteuid() == 0) {
-               pw = getpwnam(FILE_USER);
-               if (pw == NULL)
-                       errx(1, "unknown user %s", FILE_USER);
-               if (setgroups(1, &pw->pw_gid) != 0)
-                       err(1, "setgroups");
-               if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0)
-                       err(1, "setresgid");
-               if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0)
-                       err(1, "setresuid");
-       }
 
        m = magic_load(magicfp, magicpath, cflag || Wflag);
        if (cflag) {
index 5e70f4e..05c4de5 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: file.h,v 1.28 2015/04/27 13:48:06 nicm Exp $ */
+/* $OpenBSD: file.h,v 1.29 2015/04/27 13:52:17 nicm Exp $ */
 
 /*
  * Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org>
@@ -25,6 +25,9 @@
 /* User to drop privileges to in child process. */
 #define FILE_USER "_file"
 
+/* sandbox.c */
+int             sandbox_fork(const char *);
+
 /* text.c */
 const char     *text_get_type(const void *, size_t);
 const char     *text_try_words(const void *, size_t, int);
diff --git a/usr.bin/file/sandbox.c b/usr.bin/file/sandbox.c
new file mode 100644 (file)
index 0000000..70826f6
--- /dev/null
@@ -0,0 +1,157 @@
+/* $OpenBSD: sandbox.c,v 1.1 2015/04/27 13:52:17 nicm Exp $ */
+
+/*
+ * Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#include <dev/systrace.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "file.h"
+#include "magic.h"
+#include "xmalloc.h"
+
+static const struct
+{
+       int syscallnum;
+       int action;
+} allowed_syscalls[] = {
+       { SYS_open, SYSTR_POLICY_NEVER }, /* for strerror */
+
+       { SYS_close, SYSTR_POLICY_PERMIT },
+       { SYS_exit, SYSTR_POLICY_PERMIT },
+       { SYS_getdtablecount, SYSTR_POLICY_PERMIT },
+       { SYS_getentropy, SYSTR_POLICY_PERMIT },
+       { SYS_getpid, SYSTR_POLICY_PERMIT },
+       { SYS_getrlimit, SYSTR_POLICY_PERMIT },
+       { SYS_issetugid, SYSTR_POLICY_PERMIT },
+       { SYS_madvise, SYSTR_POLICY_PERMIT },
+       { SYS_mmap, SYSTR_POLICY_PERMIT },
+       { SYS_mprotect, SYSTR_POLICY_PERMIT },
+       { SYS_mquery, SYSTR_POLICY_PERMIT },
+       { SYS_munmap, SYSTR_POLICY_PERMIT },
+       { SYS_read, SYSTR_POLICY_PERMIT },
+       { SYS_recvmsg, SYSTR_POLICY_PERMIT },
+       { SYS_sendmsg, SYSTR_POLICY_PERMIT },
+       { SYS_sigprocmask, SYSTR_POLICY_PERMIT },
+       { SYS_write, SYSTR_POLICY_PERMIT },
+
+       { -1, -1 }
+};
+
+static int
+sandbox_find(int syscallnum)
+{
+       int     i;
+
+       for (i = 0; allowed_syscalls[i].syscallnum != -1; i++) {
+               if (allowed_syscalls[i].syscallnum == syscallnum)
+                       return (allowed_syscalls[i].action);
+       }
+       return (SYSTR_POLICY_KILL);
+}
+
+static int
+sandbox_child(const char *user)
+{
+       struct passwd   *pw;
+
+       /*
+        * If we don't set streams to line buffered explicitly, stdio uses
+        * isatty() which means ioctl() - too nasty to let through the systrace
+        * policy.
+        */
+       setvbuf(stdout, NULL, _IOLBF, 0);
+       setvbuf(stderr, NULL, _IOLBF, 0);
+
+       if (geteuid() == 0) {
+               pw = getpwnam(user);
+               if (pw == NULL)
+                       errx(1, "unknown user %s", user);
+               if (setgroups(1, &pw->pw_gid) != 0)
+                       err(1, "setgroups");
+               if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0)
+                       err(1, "setresgid");
+               if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0)
+                       err(1, "setresuid");
+       }
+
+       if (kill(getpid(), SIGSTOP) != 0)
+               err(1, "kill(SIGSTOP)");
+       return (0);
+}
+
+int
+sandbox_fork(const char *user)
+{
+       pid_t                    pid;
+       int                      status, devfd, fd, i;
+       struct systrace_policy   policy;
+
+       switch (pid = fork()) {
+       case -1:
+               err(1, "fork");
+       case 0:
+               return (sandbox_child(user));
+       }
+
+       do
+               pid = waitpid(pid, &status, WUNTRACED);
+       while (pid == -1 && errno == EINTR);
+       if (!WIFSTOPPED(status))
+               errx(1, "child not stopped");
+
+       devfd = open("/dev/systrace", O_RDONLY);
+       if (devfd == -1)
+               err(1, "open(\"/dev/systrace\")");
+       if (ioctl(devfd, STRIOCCLONE, &fd) == -1)
+               err(1, "ioctl(STRIOCCLONE)");
+       close(devfd);
+
+       if (ioctl(fd, STRIOCATTACH, &pid) == -1)
+               err(1, "ioctl(STRIOCATTACH)");
+
+       memset(&policy, 0, sizeof policy);
+       policy.strp_op = SYSTR_POLICY_NEW;
+       policy.strp_maxents = SYS_MAXSYSCALL;
+       if (ioctl(fd, STRIOCPOLICY, &policy) == -1)
+               err(1, "ioctl(STRIOCPOLICY/NEW)");
+       policy.strp_op = SYSTR_POLICY_ASSIGN;
+       policy.strp_pid = pid;
+       if (ioctl(fd, STRIOCPOLICY, &policy) == -1)
+               err(1, "ioctl(STRIOCPOLICY/ASSIGN)");
+
+       for (i = 0; i < SYS_MAXSYSCALL; i++) {
+               policy.strp_op = SYSTR_POLICY_MODIFY;
+               policy.strp_code = i;
+               policy.strp_policy = sandbox_find(i);
+               if (ioctl(fd, STRIOCPOLICY, &policy) == -1)
+                       err(1, "ioctl(STRIOCPOLICY/MODIFY)");
+       }
+
+       if (kill(pid, SIGCONT) != 0)
+               err(1, "kill(SIGCONT)");
+       return (pid);
+}