summaryrefslogtreecommitdiff
path: root/usr.bin/file
diff options
context:
space:
mode:
authorNicholas Marriott <nicm@cvs.openbsd.org>2015-04-27 13:52:18 +0000
committerNicholas Marriott <nicm@cvs.openbsd.org>2015-04-27 13:52:18 +0000
commit95a8c19a29953a1f61d224de74cd1b9da3149e80 (patch)
tree63f8fa7e6db8559e62ae8d33471022537f4a8bae /usr.bin/file
parent04c4247c60d69370c7af59b160eb8e607b41942a (diff)
Use a systrace(4) sandbox with a short whitelist of allowed syscalls for
the file(1) child process. Based on similar code in ssh sandbox-systrace.c. Idea and help from deraadt@.
Diffstat (limited to 'usr.bin/file')
-rw-r--r--usr.bin/file/Makefile6
-rw-r--r--usr.bin/file/file.c22
-rw-r--r--usr.bin/file/file.h5
-rw-r--r--usr.bin/file/sandbox.c157
4 files changed, 168 insertions, 22 deletions
diff --git a/usr.bin/file/Makefile b/usr.bin/file/Makefile
index 3fb202b86a9..5ce922e7acc 100644
--- a/usr.bin/file/Makefile
+++ b/usr.bin/file/Makefile
@@ -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
diff --git a/usr.bin/file/file.c b/usr.bin/file/file.c
index 43dd000140d..7f7cabe5f4e 100644
--- a/usr.bin/file/file.c
+++ b/usr.bin/file/file.c
@@ -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) {
diff --git a/usr.bin/file/file.h b/usr.bin/file/file.h
index 5e70f4ea4e4..05c4de5176b 100644
--- a/usr.bin/file/file.h
+++ b/usr.bin/file/file.h
@@ -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
index 00000000000..70826f6c8c3
--- /dev/null
+++ b/usr.bin/file/sandbox.c
@@ -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);
+}