diff --git a/Makefile b/Makefile index 5117a51..843adbd 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,9 @@ CC ?= cc CFLAGS = -Wall -g -DAUDITPIPE_GET_DROPS TARGETS = bsmtrace -OBJ = pipe.o y.tab.o bsm.o bsmtrace.o conf.o lex.yy.o log.o trigger.o fcache.o +OBJ = pipe.o y.tab.o bsm.o bsmtrace.o conf.o lex.yy.o log.o trigger.o fcache.o privsep_fdpass.o privsep.o PREFIX ?= /usr/local + LIBS = -lbsm CFLAGS += -I /usr/local/include diff --git a/bsm.c b/bsm.c index 8821354..a5eed5d 100644 --- a/bsm.c +++ b/bsm.c @@ -551,7 +551,7 @@ bsm_loop(char *atrail) if (strcmp(opts.aflag, "-") == 0) fp = stdin; else - fp = fopen(opts.aflag, "r"); + fp = priv_auditpipe_open(); if (fp == NULL) bsmtrace_fatal("%s: %s", opts.aflag, strerror(errno)); if (strcmp(opts.aflag, DEFAULT_AUDIT_TRAIL) == 0) diff --git a/bsmtrace.c b/bsmtrace.c index 3e457a9..8fb7ef1 100644 --- a/bsmtrace.c +++ b/bsmtrace.c @@ -153,7 +153,7 @@ main(int argc, char *argv[]) (void) signal(SIGCHLD, SIG_IGN); /* Ignore dying children */ (void) signal(SIGINT, bsmtrace_handle_sigint); set_default_settings(&opts); - while ((ch = getopt(argc, argv, "Fa:Bbdf:hil:np:v")) != -1) { + while ((ch = getopt(argc, argv, "Fa:Bbdf:hil:p:u:v")) != -1) { switch (ch) { case 'B': opts.Bflag = 1; @@ -185,12 +185,20 @@ main(int argc, char *argv[]) case 'v': (void) fprintf(stderr, "%s\n", BSMTRACE_VERSION); exit(0); + case 'u': + opts.uflag = optarg; + break; case 'h': default: usage(argv[0]); /* NOTREACHED */ } } + if (opts.uflag == NULL) { + bsmtrace_fatal("failed to specify privsep user\n"); + } + bsmtrace_write_pidfile(opts.pflag); + log_init_dir(); conf_load(opts.fflag); if (opts.nflag != 0) return (0); @@ -216,9 +224,9 @@ main(int argc, char *argv[]) if (setsid() < 0) bsmtrace_fatal("setsid failed: %s", strerror(errno)); - bsmtrace_write_pidfile(opts.pflag); daemonized = 1; } + priv_init(); bsm_loop(opts.aflag); return (0); } diff --git a/bsmtrace.h b/bsmtrace.h index 72751ea..fe1c30a 100644 --- a/bsmtrace.h +++ b/bsmtrace.h @@ -39,6 +39,8 @@ struct g_conf { char *pflag; char *lflag; int logfd; + char *uflag; + int log_dir_fd; int nflag; }; diff --git a/conf.c b/conf.c index b0f211b..9ac74c6 100644 --- a/conf.c +++ b/conf.c @@ -91,7 +91,7 @@ conf_load(char *path) { FILE *f; - f = fopen(path, "r"); + f = fopen(opts.fflag, "r"); if (f == NULL) bsmtrace_fatal("%s: %s", path, strerror(errno)); conffile = path; diff --git a/includes.h b/includes.h index dfa3960..14a187c 100644 --- a/includes.h +++ b/includes.h @@ -76,3 +76,4 @@ #include "log.h" #include "pipe.h" #include "trigger.h" +#include "privsep.h" diff --git a/log.c b/log.c index dbd13e4..33a7572 100644 --- a/log.c +++ b/log.c @@ -31,11 +31,14 @@ #include "includes.h" #undef SYSLOG_NAMES +int rotate_log; + void log_init_dir(void) { char logpath[128]; struct stat sb; + mode_t flags; if (opts.lflag == NULL) return; @@ -52,14 +55,18 @@ log_init_dir(void) if (access(opts.lflag, W_OK | R_OK | X_OK) != 0) { bsmtrace_fatal("%s: invalid permissions\n", opts.lflag); } - (void) sprintf(logpath, "%s/bsmtrace.log", opts.lflag); - opts.logfd = open(logpath, O_APPEND | O_WRONLY | O_CREAT); + opts.log_dir_fd = open(opts.lflag, O_RDONLY | O_DIRECTORY); + if (opts.log_dir_fd == -1) { + bsmtrace_fatal("failed to open logging directory: %s\n", + strerror(errno)); + } + flags = S_IWUSR | S_IRUSR; + opts.logfd = openat(opts.log_dir_fd, "bsmtrace.log", O_APPEND | O_WRONLY | O_CREAT, flags); if (opts.logfd == -1) { bsmtrace_fatal("open: %s failed: %s\n", logpath, strerror(errno)); } - debug_printf("logging directory and file initialized: %s\n", - logpath); + debug_printf("logging directory and file initialized"); } static char * @@ -126,7 +133,19 @@ log_bsm_txt_file(struct bsm_sequence *bs, struct bsm_record_data *br) ssize_t cc; char *ptr; size_t s; + mode_t flags; + if (rotate_log == 1) { + close(opts.logfd); + flags = S_IWUSR | S_IRUSR; + opts.logfd = openat(opts.log_dir_fd, "bsmtrace.log", + O_APPEND | O_WRONLY | O_CREAT, flags); + if (opts.logfd == -1) { + bsmtrace_fatal("failed to rotate log: %s", + strerror(errno)); + } + rotate_log = 0; + } ptr = parse_bsm_generic(bs, br); if (ptr == NULL) return (-1); @@ -149,9 +168,8 @@ log_bsm_txt_file(struct bsm_sequence *bs, struct bsm_record_data *br) int log_bsm_file(struct bsm_sequence *bs, struct bsm_record_data *br) { - char path[MAXPATHLEN], dir[MAXPATHLEN]; - struct stat sb; - int fd, error; + char path[MAXPATHLEN]; + int fd; struct bsm_state *bm; char *src_basename; @@ -161,19 +179,17 @@ log_bsm_file(struct bsm_sequence *bs, struct bsm_record_data *br) src_basename = strrchr(opts.aflag, '/'); src_basename = (src_basename == NULL) ? opts.aflag : src_basename + 1; } - (void) snprintf(dir, MAXPATHLEN, - "%s/%s", opts.lflag, bs->bs_label); - error = stat(dir, &sb); - if (error < 0 && errno == ENOENT) { - if (mkdir(dir, S_IRWXU) < 0) - bsmtrace_fatal("mkdir failed: %s", dir); - } else if (error < 0) - bsmtrace_fatal("stat failed"); + if (mkdirat(opts.log_dir_fd, bs->bs_label, S_IRWXU) < 0) { + if (errno != EEXIST) { + bsmtrace_fatal("mkdirat failed: %s: %s", bs->bs_label, + strerror(errno)); + } + } (void) sprintf(path, "%s/%d.%d.%lu", - dir, br->br_sec, br->br_usec, random()); - fd = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + bs->bs_label, br->br_sec, br->br_usec, random()); + fd = openat(opts.log_dir_fd, path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fd < 0) - bsmtrace_fatal("open: %s: %s", path, strerror(errno)); + bsmtrace_fatal("openat: %s: %s", path, strerror(errno)); /* * The logic here becomes a bit complex. We need to check to see if * this is a single state sequence, and if it is, log the BSM record diff --git a/privsep.c b/privsep.c new file mode 100644 index 0000000..d163527 --- /dev/null +++ b/privsep.c @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2016 Christian S.J. Peron + * + * 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 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. + */ +/* + * Copyright (c) 2003 Can Erkin Acar + * Copyright (c) 2003 Anil Madhavapeddy + * + * 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 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 +#ifdef __FreeBSD__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "includes.h" +#include "privsep.h" +#include "privsep_fdpass.h" + +#include + +volatile pid_t child_pid = -1; +int priv_fd = -1; +int priv_sep_on = 0; +int __real_open(const char *path, int flags, ...); + +volatile sig_atomic_t gotsig_chld = 0; + +/* Proto-types */ +static void sig_pass_to_chld(int); +static void sig_chld(int); + +static void +sig_chld(int sig) +{ + + gotsig_chld = 1; +} + +/* If priv parent gets a TERM or HUP, pass it through to child instead */ +static void +sig_pass_to_chld(int sig) +{ + int oerrno; + + oerrno = errno; + if (child_pid != -1) + (void) kill(child_pid, sig); + errno = oerrno; +} + +static void +priv_setuid(void) +{ + struct passwd *pwd; + + /* NB: getuid check is not sufficient for but leave it for now */ + if (getuid() != 0) + return; + pwd = getpwnam(opts.uflag); + if (pwd == NULL) { + bsmtrace_fatal("failed to get privsep uid\n"); + } + /* + * Change the permissions associated with the logging directory. + */ + assert(opts.log_dir_fd != 0); + if (fchown(opts.log_dir_fd, pwd->pw_uid, pwd->pw_gid) == -1) { + bsmtrace_fatal("unable to change logging direcotry ownership"); + } + if (initgroups(opts.uflag, pwd->pw_gid) == -1) { + bsmtrace_fatal("initgroups failed: %s\n", + strerror(errno)); + } + if (setgid(pwd->pw_gid) == -1) { + bsmtrace_fatal("setgid failed\n"); + } + if (setuid(pwd->pw_uid) == -1) { + bsmtrace_fatal("setuid failed\n"); + } +} + +static void +child_handle_signal(int sig) +{ + extern int rotate_log; + + switch (sig) { + case SIGHUP: + rotate_log = 1; + debug_printf("caught SIGHUP: will rotate log on next write\n"); + break; + /* + * Other signals? + */ + } +} + +int +priv_init(void) +{ + int i, socks[2], cmd; + + for (i = 1; i < NSIG; i++) + signal(i, SIG_DFL); + /* Create sockets */ + if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) { + (void) fprintf(stderr, "socketpair: %s\n", strerror(errno)); + exit(1); + } + child_pid = fork(); + if (child_pid == -1) { + (void) fprintf(stderr, "fork: %s\n", strerror(errno)); + exit(1); + } + if (child_pid == 0) { + signal(SIGHUP, child_handle_signal); + (void) close(socks[0]); +#ifdef __FreeBSD__ + priv_setuid(); + (void) fprintf(stdout, "Entering capability mode sandbox\n"); + if (cap_enter() == -1) { + (void) fprintf(stderr, "cap_enter failed: %s\n", + strerror(errno)); + exit(1); + } +#endif /* __FreeBSD__ */ +#ifdef linux + priv_setuid(); + (void) fprintf(stdout, "Entering seccomp BPF mode sandbox\n"); + seccomp_activate(); +#endif /* linux */ +#ifdef __APPLE__ + fprintf(stderr, "poor man's sandbox\n"); + if (chdir("/var/empty") == -1) { + bsmtrace_fatal("failed to chdir to /var/empty\n"); + } + if (chroot(".") == -1) { + bsmtrace_fatal("failed to chroot unprivileged process\n"); + } + priv_setuid(); +#endif + priv_fd = socks[1]; + priv_sep_on = 1; + return 0; + } + /* + * Pass ALRM/TERM/HUP/INT/QUIT through to child, and accept CHLD + */ + signal(SIGALRM, sig_pass_to_chld); + signal(SIGTERM, sig_pass_to_chld); + signal(SIGHUP, sig_pass_to_chld); + signal(SIGINT, sig_pass_to_chld); + signal(SIGQUIT, sig_pass_to_chld); + signal(SIGCHLD, sig_chld); + close(socks[1]); + while (!gotsig_chld) { + if (may_read(socks[0], &cmd, sizeof(int))) + break; + switch (cmd) { + case PRIV_GET_CONF_FD: + { + int fd, ecode; + + fd = open(opts.fflag, O_RDONLY); + if (fd == -1) { + (void) fprintf(stderr, "config open: %s\n", strerror(errno)); + ecode = errno; + } + send_fd(socks[0], fd); + if (fd == -1) { + must_write(socks[0], &ecode, sizeof(ecode)); + break; + } + close(fd); + printf("config fd sent\n"); + break; + } + case PRIV_GET_AUDITPIPE_FD: + { + int fd, ecode; + + fd = open(opts.aflag, O_RDONLY); + if (fd == -1) { + (void) fprintf(stderr, "error opening audit pipe: %s\n", + strerror(errno)); + ecode = errno; + } + send_fd(socks[0], fd); + if (fd == -1) { + must_write(socks[0], &ecode, sizeof(ecode)); + break; + } + close(fd); + printf("audit pipe fd sent\n"); + break; + } + case PRIV_GET_LOGDIR_FD: + default: + (void) fprintf(stderr, "got request for unknown priv\n"); + } + } + _exit(1); +} + +/* + * Read all data or return 1 for error. + */ +int +may_read(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while (n > pos) { + res = read(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + return (1); + default: + pos += res; + } + } + return (0); +} + +/* + * Read data with the assertion that it all must come through, or + * else abort the process. Based on atomicio() from openssh. + */ +void +must_read(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while (n > pos) { + res = read(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} + +/* + * Write data with the assertion that it all has to be written, or + * else abort the process. Based on atomicio() from openssh. + */ +void +must_write(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while (n > pos) { + res = write(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} + +/* + * Functions to be used by the non-privleged process + */ + +/* + * Grab a file to the configuration file which was passed in on + * the command line. + */ +FILE * +priv_config_open(void) +{ + FILE *fp; + int cmd, s, e; + + cmd = PRIV_GET_CONF_FD; + must_write(priv_fd, &cmd, sizeof(cmd)); + s = receive_fd(priv_fd); + if (s == -1) { + must_read(priv_fd, &e, sizeof(e)); + errno = e; + return (NULL); + } + fp = fdopen(s, "r"); + if (fp == NULL) { + (void) fprintf(stderr, "fdopen failed: %s\n", + strerror(errno)); + exit(1); + } + return (fp); +} + +/* + * Grab a file descriptor for the auditpipe(4) itself + */ +FILE * +priv_auditpipe_open(void) +{ + int cmd, s, e; + FILE *fp; + + cmd = PRIV_GET_AUDITPIPE_FD; + must_write(priv_fd, &cmd, sizeof(cmd)); + s = receive_fd(priv_fd); + if (s == -1) { + must_read(priv_fd, &e, sizeof(e)); + errno = e; + return (NULL); + } + fp = fdopen(s, "r"); + if (fp == NULL) { + bsmtrace_fatal("failed to open audit pipe: %s\n", + strerror(errno)); + exit(1); + } + return (fp); +} + +/* + * Get a file descriptor to the logging directory that was passed in + * on the command line. bsmtrace will be able to rotate log files + * and create BSM dump files by way of openat(2). This is a much + * more safe alternative than giving bsmtrace access to the global + * file system namespace. + */ +int +priv_get_logdir_fd(void) +{ + int cmd, s, e; + + cmd = PRIV_GET_LOGDIR_FD; + must_write(priv_fd, &cmd, sizeof(cmd)); + s = receive_fd(priv_fd); + if (s == -1) { + must_read(priv_fd, &e, sizeof(e)); + errno = e; + return (-1); + } + return (s); +} diff --git a/privsep.h b/privsep.h new file mode 100644 index 0000000..9dce582 --- /dev/null +++ b/privsep.h @@ -0,0 +1,18 @@ +#ifndef PRIVSEP_DOT_H_ +#define PRIVSEP_DOT_H_ + +enum { + PRIV_NOP, + PRIV_GET_CONF_FD, + PRIV_GET_AUDITPIPE_FD, + PRIV_GET_LOGDIR_FD +}; + +int may_read(int, void *, size_t); +void must_read(int, void *, size_t); +void must_write(int, void *, size_t); +int priv_init(void); +FILE *priv_config_open(void); +FILE *priv_auditpipe_open(void); + +#endif /* PRIVSEP_DOT_H_ */ diff --git a/privsep_fdpass.c b/privsep_fdpass.c new file mode 100644 index 0000000..1e8ab96 --- /dev/null +++ b/privsep_fdpass.c @@ -0,0 +1,123 @@ +/* $OpenBSD: privsep_fdpass.c,v 1.5 2008/03/24 16:11:08 deraadt Exp $ */ + +/* + * Copyright 2001 Niels Provos + * All rights reserved. + * + * Copyright (c) 2002 Matthieu Herrb + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +void +send_fd(int sock, int fd) +{ + struct msghdr msg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + struct cmsghdr *cmsg; + struct iovec vec; + int result = 0; + ssize_t n; + + memset(&msg, 0, sizeof(msg)); + if (fd >= 0) { + msg.msg_control = (caddr_t)&cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = fd; + } else { + result = errno; + } + vec.iov_base = &result; + vec.iov_len = sizeof(int); + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + if ((n = sendmsg(sock, &msg, 0)) == -1) + (void) fprintf(stderr, "sendmsg: %s\n", strerror(errno)); + if (n != sizeof(int)) + (void) fprintf(stderr, "sendmsg: %s\n", strerror(errno)); +} + +int +receive_fd(int sock) +{ + struct msghdr msg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + struct cmsghdr *cmsg; + struct iovec vec; + ssize_t n; + int result; + int fd; + + memset(&msg, 0, sizeof(msg)); + vec.iov_base = &result; + vec.iov_len = sizeof(int); + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + if ((n = recvmsg(sock, &msg, 0)) == -1) + (void) fprintf(stderr, "recvmsg: %s\n", strerror(errno)); + if (n != sizeof(int)) + (void) fprintf(stderr, "recvmsg: %s\n", strerror(errno)); + if (result == 0) { + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg == NULL) { + (void) fprintf(stderr, "%s: no message header", __func__); + return -1; + } + if (cmsg->cmsg_type != SCM_RIGHTS) + (void) fprintf(stderr, "%s: expected type %d got %d", __func__, + SCM_RIGHTS, cmsg->cmsg_type); + fd = (*(int *)CMSG_DATA(cmsg)); + return fd; + } else { + errno = result; + return -1; + } +} diff --git a/privsep_fdpass.h b/privsep_fdpass.h new file mode 100644 index 0000000..b6928ff --- /dev/null +++ b/privsep_fdpass.h @@ -0,0 +1,7 @@ +#ifndef PRIVSEP_FDPASS_DOT_H +#define PRIVSEP_FDPASS_DOT_H + +void send_fd(int, int); +int receive_fd(int); + +#endif