fork+exec model for pflogd(8); moves pcap init to the re-exec'd privsep parent and uses 'legit' fdpassing primitives to send the bpf fd to the unprivileged child process.
I've tried to keep this difff small so that it can go in, there's more work to be done in the future, but this will let things proceed further. Tested with normal usage, late snaplen setting, and also that the optional tcpdump 'expression' feature still works. Remember to use absolute paths if testing. ok? -Bryan. Index: pflogd.c =================================================================== RCS file: /cvs/src/sbin/pflogd/pflogd.c,v retrieving revision 1.54 diff -u -p -u -r1.54 pflogd.c --- sbin/pflogd/pflogd.c 23 Jul 2017 14:28:22 -0000 1.54 +++ sbin/pflogd/pflogd.c 30 Aug 2017 04:25:46 -0000 @@ -54,6 +54,7 @@ pcap_t *hpcap; static FILE *dpcap; int Debug = 0; +static int privchild = 0; static int snaplen = DEF_SNAPLEN; static int cur_snaplen = DEF_SNAPLEN; @@ -73,7 +74,6 @@ void dump_packet(u_char *, const struct void dump_packet_nobuf(u_char *, const struct pcap_pkthdr *, const u_char *); int flush_buffer(FILE *); int if_exists(char *); -int init_pcap(void); void logmsg(int, const char *, ...); void purge_buffer(void); int reset_dump(int); @@ -215,8 +215,6 @@ init_pcap(void) set_pcap_filter(); - cur_snaplen = snaplen = pcap_snapshot(hpcap); - /* lock */ if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) { logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno)); @@ -542,9 +540,7 @@ main(int argc, char **argv) ret = 0; - closefrom(STDERR_FILENO + 1); - - while ((ch = getopt(argc, argv, "Dxd:f:i:s:")) != -1) { + while ((ch = getopt(argc, argv, "Dxd:f:i:P:s:")) != -1) { switch (ch) { case 'D': Debug = 1; @@ -560,6 +556,11 @@ main(int argc, char **argv) case 'i': interface = optarg; break; + case 'P': /* used internally, exec the parent */ + privchild = strtonum(optarg, 2, INT_MAX, &errstr); + if (errstr) + errx(1, "priv child %s: %s", errstr, optarg); + break; case 's': snaplen = strtonum(optarg, 0, PFLOGD_MAXSNAPLEN, &errstr); @@ -567,6 +568,7 @@ main(int argc, char **argv) snaplen = DEF_SNAPLEN; if (errstr) snaplen = PFLOGD_MAXSNAPLEN; + cur_snaplen = snaplen; break; case 'x': Xflag = 1; @@ -606,18 +608,14 @@ main(int argc, char **argv) if (filter == NULL) logmsg(LOG_NOTICE, "Failed to form filter expression"); } + argc += optind; + argv -= optind; - /* initialize pcap before dropping privileges */ - if (init_pcap()) { - logmsg(LOG_ERR, "Exiting, init failure"); - exit(1); - } + if (privchild > 1) + priv_exec(privchild, argc, argv); /* Privilege separation begins here */ - if (priv_init()) { - logmsg(LOG_ERR, "unable to privsep"); - exit(1); - } + priv_init(argc, argv); /* * XXX needs wpath cpath rpath, for try_reset_dump() ? @@ -633,6 +631,9 @@ main(int argc, char **argv) signal(SIGALRM, sig_alrm); signal(SIGHUP, sig_hup); alarm(delay); + + if (priv_init_pcap(snaplen)) + errx(1, "priv_init_pcap failed"); buffer = malloc(PFLOGD_BUFSIZE); Index: pflogd.h =================================================================== RCS file: /cvs/src/sbin/pflogd/pflogd.h,v retrieving revision 1.5 diff -u -p -u -r1.5 pflogd.h --- sbin/pflogd/pflogd.h 10 Oct 2015 22:36:06 -0000 1.5 +++ sbin/pflogd/pflogd.h 30 Aug 2017 04:25:46 -0000 @@ -34,13 +34,15 @@ void logmsg(int priority, const char *message, ...); /* Privilege separation */ -int priv_init(void); +void priv_init(int, char **); +__dead void priv_exec(int, int, char **); +int priv_init_pcap(int); int priv_set_snaplen(int snaplen); int priv_open_log(void); int priv_move_log(void); int priv_pcap_stats(struct pcap_stat *); -pcap_t *pcap_open_live_fd(int fd, int snaplen, char *ebuf); +int init_pcap(void); void set_pcap_filter(void); /* File descriptor send/recv */ void send_fd(int, int); Index: privsep.c =================================================================== RCS file: /cvs/src/sbin/pflogd/privsep.c,v retrieving revision 1.27 diff -u -p -u -r1.27 privsep.c --- sbin/pflogd/privsep.c 12 Aug 2017 16:31:09 -0000 1.27 +++ sbin/pflogd/privsep.c 30 Aug 2017 04:25:46 -0000 @@ -40,6 +40,7 @@ #include "pflogd.h" enum cmd_types { + PRIV_INIT_PCAP, /* init pcap fdpass bpf */ PRIV_SET_SNAPLEN, /* set the snaplength */ PRIV_MOVE_LOG, /* move logfile away */ PRIV_OPEN_LOG, /* open logfile for appending */ @@ -59,18 +60,17 @@ static int set_snaplen(int snap); static int move_log(const char *name); extern char *filename; +extern char *interface; +extern char errbuf[PCAP_ERRBUF_SIZE]; extern pcap_t *hpcap; /* based on syslogd privsep */ -int -priv_init(void) +void +priv_init(int argc, char *argv[]) { - int i, bpfd = -1, socks[2], cmd; - int snaplen, ret, olderrno; + int i, nargc, socks[2]; struct passwd *pw; - - for (i = 1; i < _NSIG; i++) - signal(i, SIG_DFL); + char childnum[11], **privargv; /* Create sockets */ if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) @@ -103,8 +103,45 @@ priv_init(void) err(1, "setresuid() failed"); close(socks[0]); priv_fd = socks[1]; - return 0; + return; } + close(socks[1]); + + if (dup2(socks[0], 3) == -1) + err(1, "dup2 priv sock failed"); + closefrom(4); + + snprintf(childnum, sizeof(childnum), "%d", child_pid); + if ((privargv = reallocarray(NULL, argc + 3, sizeof(char *))) == NULL) + err(1, "alloc priv argv failed"); + nargc = 0; + privargv[nargc++] = argv[0]; + privargv[nargc++] = "-P"; + privargv[nargc++] = childnum; + for (i = 1; i < argc; i++) + privargv[nargc++] = argv[i]; + privargv[nargc] = NULL; + execvp(privargv[0], privargv); + err(1, "exec priv '%s' failed", privargv[0]); +} + +__dead void +priv_exec(int child, int argc, char *argv[]) +{ + int i, fd = -1, bpfd = -1, sock, cmd; + int snaplen, ret, olderrno; + unsigned int buflen; + + if (argc <= 2 || strcmp("-P", argv[1]) != 0) + errx(1, "exec without priv"); + + sock = 3; + closefrom(4); + + child_pid = child; + + for (i = 1; i < _NSIG; i++) + signal(i, SIG_DFL); /* Father */ /* Pass ALRM/TERM/HUP/INT/QUIT through to child, and accept CHLD */ @@ -116,7 +153,6 @@ priv_init(void) signal(SIGCHLD, sig_chld); setproctitle("[priv]"); - close(socks[1]); #if 0 /* This needs to do bpf ioctl */ @@ -125,13 +161,31 @@ BROKEN if (pledge("stdio rpath wpath cpa #endif while (!gotsig_chld) { - if (may_read(socks[0], &cmd, sizeof(int))) + if (may_read(sock, &cmd, sizeof(int))) break; switch (cmd) { + case PRIV_INIT_PCAP: + logmsg(LOG_DEBUG, + "[priv]: msg PRIV_INIT_PCAP received"); + /* initialize pcap */ + if (hpcap != NULL || init_pcap()) { + logmsg(LOG_ERR, "[priv]: Exiting, init failed"); + _exit(1); + } + buflen = hpcap->bufsize; /* BIOCGBLEN for unpriv proc */ + must_write(sock, &buflen, sizeof(unsigned int)); + fd = pcap_fileno(hpcap); + send_fd(sock, fd); + if (fd < 0) { + logmsg(LOG_ERR, "[priv]: Exiting, init failed"); + _exit(1); + } + break; + case PRIV_SET_SNAPLEN: logmsg(LOG_DEBUG, "[priv]: msg PRIV_SET_SNAPLENGTH received"); - must_read(socks[0], &snaplen, sizeof(int)); + must_read(sock, &snaplen, sizeof(int)); ret = set_snaplen(snaplen); if (ret) { @@ -140,7 +194,7 @@ BROKEN if (pledge("stdio rpath wpath cpa snaplen); } - must_write(socks[0], &ret, sizeof(int)); + must_write(sock, &ret, sizeof(int)); break; case PRIV_OPEN_LOG: @@ -155,7 +209,7 @@ BROKEN if (pledge("stdio rpath wpath cpa O_RDWR|O_CREAT|O_APPEND|O_NONBLOCK|O_NOFOLLOW, 0600); olderrno = errno; - send_fd(socks[0], bpfd); + send_fd(sock, bpfd); if (bpfd < 0) logmsg(LOG_NOTICE, "[priv]: failed to open %s: %s", @@ -166,7 +220,7 @@ BROKEN if (pledge("stdio rpath wpath cpa logmsg(LOG_DEBUG, "[priv]: msg PRIV_MOVE_LOG received"); ret = move_log(filename); - must_write(socks[0], &ret, sizeof(int)); + must_write(sock, &ret, sizeof(int)); break; default: @@ -176,7 +230,7 @@ BROKEN if (pledge("stdio rpath wpath cpa } } - _exit(1); + exit(1); } /* this is called from parent */ @@ -229,6 +283,46 @@ move_log(const char *name) logmsg(LOG_NOTICE, "[priv]: log file %s moved to %s", name, ren); + + return (0); +} + + +/* receive bpf fd from privileged proces using fdpass and init pcap */ +int +priv_init_pcap(int snaplen) +{ + int cmd, fd; + unsigned int buflen; + + if (priv_fd < 0) + errx(1, "%s: called from privileged portion", __func__); + + cmd = PRIV_INIT_PCAP; + + must_write(priv_fd, &cmd, sizeof(int)); + must_read(priv_fd, &buflen, sizeof(unsigned int)); + fd = receive_fd(priv_fd); + if (fd < 0) + return (-1); + + /* XXX temporary until pcap_open_live_fd API */ + hpcap = pcap_create(interface, errbuf); + if (hpcap == NULL) + return (-1); + + /* XXX copies from pcap_open_live/pcap_activate */ + hpcap->fd = fd; + pcap_set_snaplen(hpcap, snaplen); + pcap_set_promisc(hpcap, 1); + pcap_set_timeout(hpcap, PCAP_TO_MS); + hpcap->oldstyle = 1; + hpcap->linktype = DLT_PFLOG; + hpcap->bufsize = buflen; /* XXX bpf BIOCGBLEN */ + hpcap->buffer = malloc(hpcap->bufsize); + if (hpcap->buffer == NULL) + return (-1); + hpcap->activated = 1; return (0); }