On Thu, Sep 07, 2017 at 12:27:18AM -0400, Bryan Steele wrote:

> 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

Small correction for the record:

done by otto@ and cancacar@ while being prodded almost gently by deraadt@

        -Otto


> 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