Hi,

This turned out easier then pflogd thanks to the existing privsep design
work done by deraadt@ and canacar@ many years ago. While tcpdump isn't a
daemon in the traditional sense, it isn't so uncommon for people to have
long running sessions. At least on OpenBSD, this is even safe thanks to
privsep and very strict pledge(2) promises.

This does shuffle things around a bit internally, but I don't think I've
totally broken anything.

Seems to work with some light testing, live captures, -w/-r offline pcap
files.

Comments?

-Bryan.

Index: privsep.c
===================================================================
RCS file: /cvs/src/usr.sbin/tcpdump/privsep.c,v
retrieving revision 1.45
diff -u -p -u -r1.45 privsep.c
--- usr.sbin/tcpdump/privsep.c  14 Jun 2017 20:48:54 -0000      1.45
+++ usr.sbin/tcpdump/privsep.c  7 Sep 2017 03:19:26 -0000
@@ -33,6 +33,7 @@
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <netdb.h>
 #include <paths.h>
 #include <pwd.h>
@@ -132,13 +133,10 @@ static void       logmsg(int, const char *, ..
 int
 priv_init(int argc, char **argv)
 {
-       int bpfd = -1;
-       int i, socks[2], cmd, nflag = 0;
+       int i, nargc, socks[2];
        struct passwd *pw;
-       char *cmdbuf, *infile = NULL;
-       char *RFileName = NULL;
-       char *WFileName = NULL;
        sigset_t allsigs, oset;
+       char childnum[11], **privargv;
 
        closefrom(STDERR_FILENO + 1);
        for (i = 1; i < _NSIG; i++)
@@ -155,7 +153,7 @@ priv_init(int argc, char **argv)
        if (child_pid < 0)
                err(1, "fork() failed");
 
-       if (child_pid) {
+       if (!child_pid) {
                close(socks[0]);
                priv_fd = socks[1];
 
@@ -188,14 +186,48 @@ priv_init(int argc, char **argv)
 
                return (0);
        }
+       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 argc, char *argv[])
+{
+       int bpfd = -1;
+       int i, sock, cmd, nflag = 0;
+       char *cmdbuf, *infile = NULL;
+       char *RFileName = NULL;
+       char *WFileName = NULL;
+       const char *errstr;
+
+       sock = 3;
+
+       closefrom(4);
+       for (i = 1; i < _NSIG; i++)
+               signal(i, SIG_DFL);
 
-       sigprocmask(SIG_SETMASK, &oset, NULL);
        signal(SIGINT, SIG_IGN);
 
        /* parse the arguments for required options */
        opterr = 0;
        while ((i = getopt(argc, argv,
-           "ac:D:deE:fF:i:lLnNOopqr:s:StT:vw:xXy:Y")) != -1) {
+           "ac:D:deE:fF:i:lLnNOopP:qr:s:StT:vw:xXy:Y")) != -1) {
                switch (i) {
                case 'n':
                        nflag++;
@@ -213,12 +245,21 @@ priv_init(int argc, char **argv)
                        infile = optarg;
                        break;
 
+               case 'P':
+                       child_pid = strtonum(optarg, 2, INT_MAX, &errstr);
+                       if (errstr)
+                               errx(1, "priv child %s: %s", errstr, optarg);
+                       break;
+
                default:
                        /* nothing */
                        break;
                }
        }
 
+       if (child_pid < 2)
+               errx(1, "exec without priv");
+
        if (RFileName != NULL) {
                if (strcmp(RFileName, "-") != 0)
                        allowed_ext[STATE_INIT] |= ALLOW(PRIV_OPEN_DUMP);
@@ -245,31 +286,30 @@ priv_init(int argc, char **argv)
                cmdbuf = copy_argv(&argv[optind]);
 
        setproctitle("[priv]");
-       close(socks[1]);
 
        for (;;) {
-               if (may_read(socks[0], &cmd, sizeof(int)))
+               if (may_read(sock, &cmd, sizeof(int)))
                        break;
                switch (cmd) {
                case PRIV_OPEN_BPF:
                        test_state(cmd, STATE_BPF);
-                       impl_open_bpf(socks[0], &bpfd);
+                       impl_open_bpf(sock, &bpfd);
                        break;
                case PRIV_OPEN_DUMP:
                        test_state(cmd, STATE_BPF);
-                       impl_open_dump(socks[0], RFileName);
+                       impl_open_dump(sock, RFileName);
                        break;
                case PRIV_OPEN_OUTPUT:
                        test_state(cmd, STATE_RUN);
-                       impl_open_output(socks[0], WFileName);
+                       impl_open_output(sock, WFileName);
                        break;
                case PRIV_SETFILTER:
                        test_state(cmd, STATE_FILTER);
-                       impl_setfilter(socks[0], cmdbuf, &bpfd);
+                       impl_setfilter(sock, cmdbuf, &bpfd);
                        break;
                case PRIV_INIT_DONE:
                        test_state(cmd, STATE_RUN);
-                       impl_init_done(socks[0], &bpfd);
+                       impl_init_done(sock, &bpfd);
 
                        if (pledge("stdio rpath inet unix dns recvfd bpf", 
NULL) == -1)
                                err(1, "pledge");
@@ -277,45 +317,45 @@ priv_init(int argc, char **argv)
                        break;
                case PRIV_GETHOSTBYADDR:
                        test_state(cmd, STATE_RUN);
-                       impl_gethostbyaddr(socks[0]);
+                       impl_gethostbyaddr(sock);
                        break;
                case PRIV_ETHER_NTOHOST:
                        test_state(cmd, cur_state);
-                       impl_ether_ntohost(socks[0]);
+                       impl_ether_ntohost(sock);
                        break;
                case PRIV_GETRPCBYNUMBER:
                        test_state(cmd, STATE_RUN);
-                       impl_getrpcbynumber(socks[0]);
+                       impl_getrpcbynumber(sock);
                        break;
                case PRIV_GETSERVENTRIES:
                        test_state(cmd, STATE_FILTER);
-                       impl_getserventries(socks[0]);
+                       impl_getserventries(sock);
                        break;
                case PRIV_GETPROTOENTRIES:
                        test_state(cmd, STATE_FILTER);
-                       impl_getprotoentries(socks[0]);
+                       impl_getprotoentries(sock);
                        break;
                case PRIV_LOCALTIME:
                        test_state(cmd, STATE_RUN);
-                       impl_localtime(socks[0]);
+                       impl_localtime(sock);
                        break;
                case PRIV_GETLINES:
                        test_state(cmd, STATE_RUN);
-                       impl_getlines(socks[0]);
+                       impl_getlines(sock);
                        break;
                case PRIV_PCAP_STATS:
                        test_state(cmd, STATE_RUN);
-                       impl_pcap_stats(socks[0], &bpfd);
+                       impl_pcap_stats(sock, &bpfd);
                        break;
                default:
                        logmsg(LOG_ERR, "[priv]: unknown command %d", cmd);
-                       _exit(1);
+                       exit(1);
                        /* NOTREACHED */
                }
        }
 
        /* NOTREACHED */
-       _exit(0);
+       exit(0);
 }
 
 static void
Index: privsep.h
===================================================================
RCS file: /cvs/src/usr.sbin/tcpdump/privsep.h,v
retrieving revision 1.9
diff -u -p -u -r1.9 privsep.h
--- usr.sbin/tcpdump/privsep.h  14 Jun 2017 20:48:54 -0000      1.9
+++ usr.sbin/tcpdump/privsep.h  7 Sep 2017 03:19:26 -0000
@@ -44,6 +44,7 @@ struct ether_addr;
 
 /* Privilege separation */
 int    priv_init(int, char **);
+__dead void priv_exec(int, char **);
 void    priv_init_done(void);
 
 int    setfilter(int, int, char *);
Index: setsignal.c
===================================================================
RCS file: /cvs/src/usr.sbin/tcpdump/setsignal.c,v
retrieving revision 1.6
diff -u -p -u -r1.6 setsignal.c
--- usr.sbin/tcpdump/setsignal.c        14 Oct 2015 04:55:17 -0000      1.6
+++ usr.sbin/tcpdump/setsignal.c        7 Sep 2017 03:19:26 -0000
@@ -36,8 +36,6 @@ setsignal(int sig, void (*func)(int))
        if (sigaction(sig, NULL, &sa) == 0 && sa.sa_handler != SIG_IGN) {
                sa.sa_handler = func;
                sa.sa_flags = SA_RESTART;
-               if (sig == SIGCHLD)
-                       sa.sa_flags |= SA_NOCLDSTOP;
                sigemptyset(&sa.sa_mask);
                sigaction(sig, &sa, NULL);
        }
Index: tcpdump.c
===================================================================
RCS file: /cvs/src/usr.sbin/tcpdump/tcpdump.c,v
retrieving revision 1.79
diff -u -p -u -r1.79 tcpdump.c
--- usr.sbin/tcpdump/tcpdump.c  16 Nov 2016 13:47:27 -0000      1.79
+++ usr.sbin/tcpdump/tcpdump.c  7 Sep 2017 03:19:26 -0000
@@ -85,15 +85,12 @@ char *device = NULL;
 
 int32_t thiszone;              /* seconds offset from gmt to local time */
 
-extern volatile pid_t child_pid;
-
 /* Externs */
 extern void bpf_dump(struct bpf_program *, int);
 extern int esp_init(char *);
 
 /* Forwards */
 void   cleanup(int);
-void   gotchld(int);
 extern __dead void usage(void);
 
 /* Length of saved portion of packet. */
@@ -218,6 +215,10 @@ main(int argc, char **argv)
        else
                program_name = argv[0];
 
+       /* '-P' used internally, exec the parent */
+       if (argc >= 2 && strcmp("-P", argv[1]) == 0)
+               priv_exec(argc, argv);
+
        if (priv_init(argc, argv))
                error("Failed to setup privsep");
 
@@ -533,25 +534,6 @@ cleanup(int signo)
        _exit(0);
 }
 
-void
-gotchld(int signo)
-{
-       pid_t pid;
-       int status;
-       int save_err = errno;
-
-       do {
-               pid = waitpid(child_pid, &status, WNOHANG);
-               if (pid > 0 && (WIFEXITED(status) || WIFSIGNALED(status)))
-                       cleanup(0);
-       } while (pid == -1 && errno == EINTR);
-
-       if (pid == -1)
-               _exit(1);
-
-       errno = save_err;
-}
-
 /* dump the buffer in `emacs-hexl' style */
 void
 default_print_hexl(const u_char *cp, unsigned int length)
@@ -682,7 +664,6 @@ set_slave_signals(void)
 {
        setsignal(SIGTERM, cleanup);
        setsignal(SIGINT, cleanup);
-       setsignal(SIGCHLD, gotchld);
        setsignal(SIGHUP, cleanup);
 }
 

Reply via email to