From 03ed85e124d59134107086d60805466cbd0321f3 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 27 Apr 2015 13:52:17 +0000 Subject: [PATCH] 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@. --- usr.bin/file/Makefile | 6 +- usr.bin/file/file.c | 22 ++---- usr.bin/file/file.h | 5 +- usr.bin/file/sandbox.c | 157 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 usr.bin/file/sandbox.c 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 @@ -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 @@ -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 + * + * 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 +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#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); +} -- 2.20.1