OpenBSD's file(1) implementation was written by nicm@, first introduced in 5.8, the inital design included a privileged parent process which forked an unprivileged child which would handle potentially unsafe file parsing.
It also had 'sandboxing' using systrace(4), which required complex parent/child monitoring (SIGSTOP/START) to attach a policy to the child process. The goal was to make running file(1) safter, as it is often blindly run as root by users and build scripts alike. Today, file(1) uses pledge(2) in the unprivileged child, and the parent handles the initial opening and passing of fds using imsg, but otherwise it just wait(4)'s until the process exits. The diff below attempts to simplify the design, removing the parent/child abstractions entirely and dropping privs in the parent after opening the magic(5) patterns and input. This was brought up during awolk@'s #openbsd-daily readthrough. Make sense, or unnecessary churn? :-) -Bryan. Index: Makefile =================================================================== RCS file: /cvs/src/usr.bin/file/Makefile,v retrieving revision 1.16 diff -u -p -u -r1.16 Makefile --- Makefile 4 Oct 2015 07:25:59 -0000 1.16 +++ Makefile 27 Jun 2017 03:19:51 -0000 @@ -5,9 +5,6 @@ SRCS= file.c magic-dump.c magic-load.c text.c xmalloc.c MAN= file.1 magic.5 -LDADD= -lutil -DPADD= ${LIBUTIL} - CDIAGFLAGS+= -Wno-long-long -Wall -W -Wnested-externs -Wformat=2 CDIAGFLAGS+= -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations CDIAGFLAGS+= -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare Index: file.c =================================================================== RCS file: /cvs/src/usr.bin/file/file.c,v retrieving revision 1.59 diff -u -p -u -r1.59 file.c --- file.c 18 Apr 2017 14:16:48 -0000 1.59 +++ file.c 27 Jun 2017 03:19:51 -0000 @@ -29,12 +29,10 @@ #include <errno.h> #include <fcntl.h> #include <getopt.h> -#include <imsg.h> #include <libgen.h> #include <limits.h> #include <pwd.h> #include <stdlib.h> -#include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> @@ -43,27 +41,16 @@ #include "magic.h" #include "xmalloc.h" -struct input_msg { - int idx; - - struct stat sb; - int error; - - char link_path[PATH_MAX]; - int link_error; - int link_target; -}; - -struct input_ack { - int idx; -}; - struct input_file { struct magic *m; - struct input_msg *msg; const char *path; - int fd; + struct stat sb; + int fd, error; + + char link_path[PATH_MAX]; + int link_error; + int link_target; void *base; size_t size; @@ -75,15 +62,13 @@ extern char *__progname; __dead void usage(void); -static int prepare_message(struct input_msg *, int, const char *); -static void send_message(struct imsgbuf *, void *, size_t, int); -static int read_message(struct imsgbuf *, struct imsg *, pid_t); +static void prepare_input(struct input_file *, const char *); -static void read_link(struct input_msg *, const char *); +static void read_link(struct input_file *, const char *); -static __dead void child(int, pid_t, int, char **); +static void privdrop(void); -static void test_file(struct input_file *, size_t); +static void test_file(struct input_file *, struct magic *); static int try_stat(struct input_file *); static int try_empty(struct input_file *); @@ -120,14 +105,11 @@ usage(void) int main(int argc, char **argv) { - int opt, pair[2], fd, idx; + int opt, idx, nargs; char *home; struct passwd *pw; - struct imsgbuf ibuf; - struct imsg imsg; - struct input_msg msg; - struct input_ack *ack; - pid_t pid, parent; + struct magic *m; + struct input_file *inf; tzset(); @@ -193,71 +175,44 @@ main(int argc, char **argv) if (magicfp == NULL) err(1, "%s", magicpath); - parent = getpid(); - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) - err(1, "socketpair"); - switch (pid = fork()) { - case -1: - err(1, "fork"); - case 0: - close(pair[0]); - child(pair[1], parent, argc, argv); + m = magic_load(magicfp, magicpath, cflag || Wflag); + if (cflag) { + magic_dump(m); + exit(0); } - close(pair[1]); - - fclose(magicfp); - magicfp = NULL; - if (cflag) - goto wait_for_child; + inf = xcalloc(argc, sizeof *inf); + for (idx = 0; idx < argc; idx++) + prepare_input(&inf[idx], argv[idx]); - imsg_init(&ibuf, pair[0]); - for (idx = 0; idx < argc; idx++) { - fd = prepare_message(&msg, idx, argv[idx]); - send_message(&ibuf, &msg, sizeof msg, fd); + privdrop(); - if (read_message(&ibuf, &imsg, pid) == 0) - break; - if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof *ack) - errx(1, "message too small"); - ack = imsg.data; - if (ack->idx != idx) - errx(1, "index not expected"); - imsg_free(&imsg); - } - -wait_for_child: - close(pair[0]); - while (wait(NULL) == -1 && errno != ECHILD) { - if (errno != EINTR) - err(1, "wait"); - } - _exit(0); /* let the child flush */ + for (idx = 0; idx < argc; idx++) + test_file(&inf[idx], m); + fclose(magicfp); + magicfp = NULL; } -static int -prepare_message(struct input_msg *msg, int idx, const char *path) +static void +prepare_input(struct input_file *inf, const char *path) { int fd, mode, error; - memset(msg, 0, sizeof *msg); - msg->idx = idx; - if (strcmp(path, "-") == 0) { - if (fstat(STDIN_FILENO, &msg->sb) == -1) { - msg->error = errno; - return (-1); + if (fstat(STDIN_FILENO, &inf->sb) == -1) { + inf->error = errno; + inf->fd = -1; } - return (STDIN_FILENO); + inf->fd = STDIN_FILENO; } if (Lflag) - error = stat(path, &msg->sb); + error = stat(path, &inf->sb); else - error = lstat(path, &msg->sb); + error = lstat(path, &inf->sb); if (error == -1) { - msg->error = errno; - return (-1); + inf->error = errno; + inf->fd = -1; } /* @@ -265,7 +220,7 @@ prepare_message(struct input_msg *msg, i * but in fact we don't need them, so just don't open directories or * symlinks (which could be to directories). */ - mode = msg->sb.st_mode; + mode = inf->sb.st_mode; if (!S_ISDIR(mode) && !S_ISLNK(mode)) { fd = open(path, O_RDONLY|O_NONBLOCK); if (fd == -1 && (errno == ENFILE || errno == EMFILE)) @@ -273,46 +228,13 @@ prepare_message(struct input_msg *msg, i } else fd = -1; if (S_ISLNK(mode)) - read_link(msg, path); - return (fd); - + read_link(inf, path); + inf->fd = fd; + inf->path = path; } static void -send_message(struct imsgbuf *ibuf, void *msg, size_t msglen, int fd) -{ - if (imsg_compose(ibuf, -1, -1, 0, fd, msg, msglen) != 1) - err(1, "imsg_compose"); - if (imsg_flush(ibuf) != 0) - err(1, "imsg_flush"); -} - -static int -read_message(struct imsgbuf *ibuf, struct imsg *imsg, pid_t from) -{ - int n; - - while ((n = imsg_read(ibuf)) == -1 && errno == EAGAIN) - /* nothing */ ; - if (n == -1) - err(1, "imsg_read"); - if (n == 0) - return (0); - - if ((n = imsg_get(ibuf, imsg)) == -1) - err(1, "imsg_get"); - if (n == 0) - return (0); - - if ((pid_t)imsg->hdr.pid != from) - errx(1, "PIDs don't match"); - - return (n); - -} - -static void -read_link(struct input_msg *msg, const char *path) +read_link(struct input_file *inf, const char *path) { struct stat sb; char lpath[PATH_MAX]; @@ -322,25 +244,25 @@ read_link(struct input_msg *msg, const c size = readlink(path, lpath, sizeof lpath - 1); if (size == -1) { - msg->link_error = errno; + inf->link_error = errno; return; } lpath[size] = '\0'; if (*lpath == '/') - strlcpy(msg->link_path, lpath, sizeof msg->link_path); + strlcpy(inf->link_path, lpath, sizeof inf->link_path); else { copy = xstrdup(path); root = dirname(copy); if (*root == '\0' || strcmp(root, ".") == 0 || strcmp (root, "/") == 0) - strlcpy(msg->link_path, lpath, sizeof msg->link_path); + strlcpy(inf->link_path, lpath, sizeof inf->link_path); else { - used = snprintf(msg->link_path, sizeof msg->link_path, + used = snprintf(inf->link_path, sizeof inf->link_path, "%s/%s", root, lpath); - if (used < 0 || (size_t)used >= sizeof msg->link_path) { - msg->link_error = ENAMETOOLONG; + if (used < 0 || (size_t)used >= sizeof inf->link_path) { + inf->link_error = ENAMETOOLONG; free(copy); return; } @@ -350,23 +272,15 @@ read_link(struct input_msg *msg, const c } if (!Lflag && stat(path, &sb) == -1) - msg->link_target = errno; + inf->link_target = errno; } -static __dead void -child(int fd, pid_t parent, int argc, char **argv) +static void +privdrop(void) { struct passwd *pw; - struct magic *m; - struct imsgbuf ibuf; - struct imsg imsg; - struct input_msg *msg; - struct input_ack ack; - struct input_file inf; - int i, idx; - size_t len, width = 0; - if (pledge("stdio getpw recvfd id", NULL) == -1) + if (pledge("stdio getpw id", NULL) == -1) err(1, "pledge"); if (geteuid() == 0) { @@ -381,50 +295,9 @@ child(int fd, pid_t parent, int argc, ch err(1, "setresuid"); } - if (pledge("stdio recvfd", NULL) == -1) + if (pledge("stdio", NULL) == -1) err(1, "pledge"); - m = magic_load(magicfp, magicpath, cflag || Wflag); - if (cflag) { - magic_dump(m); - exit(0); - } - - for (i = 0; i < argc; i++) { - len = strlen(argv[i]) + 1; - if (len > width) - width = len; - } - - imsg_init(&ibuf, fd); - for (;;) { - if (read_message(&ibuf, &imsg, parent) == 0) - break; - if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof *msg) - errx(1, "message too small"); - msg = imsg.data; - - idx = msg->idx; - if (idx < 0 || idx >= argc) - errx(1, "index out of range"); - - memset(&inf, 0, sizeof inf); - inf.m = m; - inf.msg = msg; - - inf.path = argv[idx]; - inf.fd = imsg.fd; - - test_file(&inf, width); - - if (imsg.fd != -1) - close(imsg.fd); - imsg_free(&imsg); - - ack.idx = idx; - send_message(&ibuf, &ack, sizeof ack, -1); - } - exit(0); } static void * @@ -461,14 +334,14 @@ load_file(struct input_file *inf) { size_t used; - if (inf->msg->sb.st_size == 0 && S_ISREG(inf->msg->sb.st_mode)) + if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode)) return (0); /* empty file */ - if (inf->msg->sb.st_size == 0 || inf->msg->sb.st_size > FILE_READ_SIZE) + if (inf->sb.st_size == 0 || inf->sb.st_size > FILE_READ_SIZE) inf->size = FILE_READ_SIZE; else - inf->size = inf->msg->sb.st_size; + inf->size = inf->sb.st_size; - if (!S_ISREG(inf->msg->sb.st_mode)) + if (!S_ISREG(inf->sb.st_mode)) goto try_read; inf->base = mmap(NULL, inf->size, PROT_READ, MAP_PRIVATE, inf->fd, 0); @@ -491,13 +364,13 @@ try_read: static int try_stat(struct input_file *inf) { - if (inf->msg->error != 0) { + if (inf->error != 0) { xasprintf(&inf->result, "cannot stat '%s' (%s)", inf->path, - strerror(inf->msg->error)); + strerror(inf->error)); return (1); } if (sflag || strcmp(inf->path, "-") == 0) { - switch (inf->msg->sb.st_mode & S_IFMT) { + switch (inf->sb.st_mode & S_IFMT) { case S_IFIFO: if (strcmp(inf->path, "-") != 0) break; @@ -508,29 +381,29 @@ try_stat(struct input_file *inf) } } - if (iflag && (inf->msg->sb.st_mode & S_IFMT) != S_IFREG) { + if (iflag && (inf->sb.st_mode & S_IFMT) != S_IFREG) { xasprintf(&inf->result, "application/x-not-regular-file"); return (1); } - switch (inf->msg->sb.st_mode & S_IFMT) { + switch (inf->sb.st_mode & S_IFMT) { case S_IFDIR: xasprintf(&inf->result, "directory"); return (1); case S_IFLNK: - if (inf->msg->link_error != 0) { + if (inf->link_error != 0) { xasprintf(&inf->result, "unreadable symlink '%s' (%s)", - inf->path, strerror(inf->msg->link_error)); + inf->path, strerror(inf->link_error)); return (1); } - if (inf->msg->link_target == ELOOP) + if (inf->link_target == ELOOP) xasprintf(&inf->result, "symbolic link in a loop"); - else if (inf->msg->link_target != 0) { + else if (inf->link_target != 0) { xasprintf(&inf->result, "broken symbolic link to '%s'", - inf->msg->link_path); + inf->link_path); } else { xasprintf(&inf->result, "symbolic link to '%s'", - inf->msg->link_path); + inf->link_path); } return (1); case S_IFSOCK: @@ -538,13 +411,13 @@ try_stat(struct input_file *inf) return (1); case S_IFBLK: xasprintf(&inf->result, "block special (%ld/%ld)", - (long)major(inf->msg->sb.st_rdev), - (long)minor(inf->msg->sb.st_rdev)); + (long)major(inf->sb.st_rdev), + (long)minor(inf->sb.st_rdev)); return (1); case S_IFCHR: xasprintf(&inf->result, "character special (%ld/%ld)", - (long)major(inf->msg->sb.st_rdev), - (long)minor(inf->msg->sb.st_rdev)); + (long)major(inf->sb.st_rdev), + (long)minor(inf->sb.st_rdev)); return (1); case S_IFIFO: xasprintf(&inf->result, "fifo (named pipe)"); @@ -571,16 +444,16 @@ try_access(struct input_file *inf) { char tmp[256] = ""; - if (inf->msg->sb.st_size == 0 && S_ISREG(inf->msg->sb.st_mode)) + if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode)) return (0); /* empty file */ if (inf->fd != -1) return (0); - if (inf->msg->sb.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) + if (inf->sb.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) strlcat(tmp, "writable, ", sizeof tmp); - if (inf->msg->sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) + if (inf->sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) strlcat(tmp, "executable, ", sizeof tmp); - if (S_ISREG(inf->msg->sb.st_mode)) + if (S_ISREG(inf->sb.st_mode)) strlcat(tmp, "regular file, ", sizeof tmp); strlcat(tmp, "no read permission", sizeof tmp); @@ -653,11 +526,13 @@ try_unknown(struct input_file *inf) } static void -test_file(struct input_file *inf, size_t width) +test_file(struct input_file *inf, struct magic *m) { char *label; int stop; + inf->m = m; + stop = 0; if (!stop) stop = try_stat(inf); @@ -681,7 +556,7 @@ test_file(struct input_file *inf, size_t xasprintf(&label, "/dev/stdin:"); else xasprintf(&label, "%s:", inf->path); - printf("%-*s %s\n", (int)width, label, inf->result); + printf("%s %s\n", label, inf->result); free(label); } free(inf->result);