Sync proc.c from vmd(8) to enabled fork + exec for all processes. This gives
authortobhe <tobhe@openbsd.org>
Sat, 4 Mar 2023 22:22:50 +0000 (22:22 +0000)
committertobhe <tobhe@openbsd.org>
Sat, 4 Mar 2023 22:22:50 +0000 (22:22 +0000)
each process a fresh and unique address space to further improve randomization
of ASLR and stack protector.

ok bluhm@ patrick@

sbin/iked/ca.c
sbin/iked/control.c
sbin/iked/iked.c
sbin/iked/iked.h
sbin/iked/ikev2.c
sbin/iked/proc.c
sbin/iked/types.h

index a82fee0..10e5eb2 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ca.c,v 1.89 2022/11/07 22:39:52 tobhe Exp $   */
+/*     $OpenBSD: ca.c,v 1.90 2023/03/04 22:22:50 tobhe Exp $   */
 
 /*
  * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org>
@@ -98,10 +98,10 @@ struct ca_store {
        uint8_t          ca_privkey_method;
 };
 
-pid_t
+void
 caproc(struct privsep *ps, struct privsep_proc *p)
 {
-       return (proc_run(ps, p, procs, nitems(procs), ca_run, NULL));
+       proc_run(ps, p, procs, nitems(procs), ca_run, NULL);
 }
 
 void
@@ -129,9 +129,13 @@ ca_run(struct privsep *ps, struct privsep_proc *p, void *arg)
 void
 ca_shutdown(struct privsep_proc *p)
 {
-       struct iked             *env = p->p_env;
+       struct iked             *env;
        struct ca_store         *store;
 
+       if (p->p_ps == NULL)
+               return;
+
+       env = p->p_ps->ps_env;
        if (env == NULL)
                return;
        ibuf_release(env->sc_certreq);
@@ -209,7 +213,7 @@ ca_reset(struct privsep *ps)
 int
 ca_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
 {
-       struct iked             *env = p->p_env;
+       struct iked             *env = p->p_ps->ps_env;
        unsigned int             mode;
 
        switch (imsg->hdr.type) {
@@ -244,7 +248,7 @@ ca_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
 int
 ca_dispatch_ikev2(int fd, struct privsep_proc *p, struct imsg *imsg)
 {
-       struct iked     *env = p->p_env;
+       struct iked     *env = p->p_ps->ps_env;
 
        switch (imsg->hdr.type) {
        case IMSG_CERTREQ:
@@ -266,7 +270,7 @@ ca_dispatch_ikev2(int fd, struct privsep_proc *p, struct imsg *imsg)
 int
 ca_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg)
 {
-       struct iked     *env = p->p_env;
+       struct iked     *env = p->p_ps->ps_env;
        struct ca_store *store = env->sc_priv;
 
        switch (imsg->hdr.type) {
index b76a545..2aa3f9a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: control.c,v 1.34 2022/12/04 11:54:31 tobhe Exp $      */
+/*     $OpenBSD: control.c,v 1.35 2023/03/04 22:22:50 tobhe Exp $      */
 
 /*
  * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org>
@@ -55,10 +55,10 @@ static struct privsep_proc procs[] = {
        { "ca",         PROC_CERT, control_dispatch_ca },
 };
 
-pid_t
+void
 control(struct privsep *ps, struct privsep_proc *p)
 {
-       return (proc_run(ps, p, procs, nitems(procs), control_run, NULL));
+       proc_run(ps, p, procs, nitems(procs), control_run, NULL);
 }
 
 void
@@ -69,7 +69,7 @@ control_run(struct privsep *ps, struct privsep_proc *p, void *arg)
         * stdio - for malloc and basic I/O including events.
         * unix - for the control socket.
         */
-       if (pledge("stdio unix", NULL) == -1)
+       if (pledge("stdio unix recvfd", NULL) == -1)
                fatal("pledge");
 }
 
index 210dd99..858d1e2 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: iked.c,v 1.62 2021/12/01 16:42:12 deraadt Exp $       */
+/*     $OpenBSD: iked.c,v 1.63 2023/03/04 22:22:50 tobhe Exp $ */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -66,20 +66,23 @@ usage(void)
 int
 main(int argc, char *argv[])
 {
-       int              c;
-       int              debug = 0, verbose = 0;
-       int              opts = 0;
-       enum natt_mode   natt_mode = NATT_DEFAULT;
-       in_port_t        port = IKED_NATT_PORT;
-       const char      *conffile = IKED_CONFIG;
-       const char      *sock = IKED_SOCKET;
-       const char      *errstr;
-       struct iked     *env = NULL;
-       struct privsep  *ps;
+       int                      c;
+       int                      debug = 0, verbose = 0;
+       int                      opts = 0;
+       enum natt_mode           natt_mode = NATT_DEFAULT;
+       in_port_t                port = IKED_NATT_PORT;
+       const char              *conffile = IKED_CONFIG;
+       const char              *sock = IKED_SOCKET;
+       const char              *errstr, *title = NULL;
+       struct iked             *env = NULL;
+       struct privsep          *ps;
+       enum privsep_procid      proc_id = PROC_PARENT;
+       int                      proc_instance = 0;
+       int                      argc0 = argc;
 
        log_init(1, LOG_DAEMON);
 
-       while ((c = getopt(argc, argv, "6D:df:np:Ss:TtvV")) != -1) {
+       while ((c = getopt(argc, argv, "6D:df:I:nP:p:Ss:TtvV")) != -1) {
                switch (c) {
                case '6':
                        log_warnx("the -6 option is ignored and will be "
@@ -96,10 +99,22 @@ main(int argc, char *argv[])
                case 'f':
                        conffile = optarg;
                        break;
+               case 'I':
+                       proc_instance = strtonum(optarg, 0,
+                           PROC_MAX_INSTANCES, &errstr);
+                       if (errstr)
+                               fatalx("invalid process instance");
+                       break;
                case 'n':
                        debug = 1;
                        opts |= IKED_OPT_NOACTION;
                        break;
+               case 'P':
+                       title = optarg;
+                       proc_id = proc_getid(procs, nitems(procs), title);
+                       if (proc_id == PROC_MAX)
+                               fatalx("invalid process name");
+                       break;
                case 'p':
                        if (natt_mode == NATT_DISABLE)
                                errx(1, "-T and -p are mutually exclusive");
@@ -136,8 +151,10 @@ main(int argc, char *argv[])
                }
        }
 
+       /* log to stderr until daemonized */
+       log_init(debug ? debug : 1, LOG_DAEMON);
+
        argc -= optind;
-       argv += optind;
        if (argc > 0)
                usage();
 
@@ -156,6 +173,7 @@ main(int argc, char *argv[])
                errx(1, "config file exceeds PATH_MAX");
 
        ca_sslinit();
+       group_init();
        policy_init(env);
 
        /* check for root privileges */
@@ -174,16 +192,17 @@ main(int argc, char *argv[])
        if (opts & IKED_OPT_NOACTION)
                ps->ps_noaction = 1;
 
-       if (!debug && daemon(0, 0) == -1)
-               err(1, "failed to daemonize");
-
-       group_init();
+       ps->ps_instance = proc_instance;
+       if (title != NULL)
+               ps->ps_title[proc_id] = title;
 
-       ps->ps_ninstances = 1;
-       proc_init(ps, procs, nitems(procs));
+       /* only the parent returns */
+       proc_init(ps, procs, nitems(procs), debug, argc0, argv, proc_id);
 
        setproctitle("parent");
        log_procinit("parent");
+       if (!debug && daemon(0, 0) == -1)
+               err(1, "failed to daemonize");
 
        event_init();
 
@@ -201,7 +220,7 @@ main(int argc, char *argv[])
        signal_add(&ps->ps_evsigpipe, NULL);
        signal_add(&ps->ps_evsigusr1, NULL);
 
-       proc_listen(ps, procs, nitems(procs));
+       proc_connect(ps);
 
        vroute_init(env);
 
index 01ea6a4..8f84b2a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: iked.h,v 1.208 2022/12/03 22:34:35 tobhe Exp $        */
+/*     $OpenBSD: iked.h,v 1.209 2023/03/04 22:22:50 tobhe Exp $        */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -738,15 +738,22 @@ struct privsep_proc {
        enum privsep_procid      p_id;
        int                     (*p_cb)(int, struct privsep_proc *,
                                    struct imsg *);
-       pid_t                   (*p_init)(struct privsep *,
+       void                    (*p_init)(struct privsep *,
                                    struct privsep_proc *);
        const char              *p_chroot;
+       struct passwd           *p_pw;
        struct privsep          *p_ps;
-       struct iked             *p_env;
        void                    (*p_shutdown)(struct privsep_proc *);
-       unsigned int             p_instance;
 };
 
+struct privsep_fd {
+       enum privsep_procid              pf_procid;
+       unsigned int                     pf_instance;
+};
+
+#define PROC_PARENT_SOCK_FILENO 3
+#define PROC_MAX_INSTANCES      32
+
 struct iked_ocsp_entry {
        TAILQ_ENTRY(iked_ocsp_entry) ioe_entry; /* next request */
        void                    *ioe_ocsp;      /* private ocsp request data */
@@ -869,7 +876,7 @@ struct ipsec_mode {
 void    parent_reload(struct iked *, int, const char *);
 
 /* control.c */
-pid_t   control(struct privsep *, struct privsep_proc *);
+void    control(struct privsep *, struct privsep_proc *);
 int     control_init(struct privsep *, struct control_sock *);
 int     control_listen(struct control_sock *);
 
@@ -1042,7 +1049,7 @@ int vroute_getroute(struct iked *, struct imsg *);
 int vroute_getcloneroute(struct iked *, struct imsg *);
 
 /* ikev2.c */
-pid_t   ikev2(struct privsep *, struct privsep_proc *);
+void    ikev2(struct privsep *, struct privsep_proc *);
 void    ikev2_recv(struct iked *, struct iked_message *);
 void    ikev2_init_ike_sa(struct iked *, void *);
 int     ikev2_policy2id(struct iked_static_id *, struct iked_id *, int);
@@ -1159,7 +1166,7 @@ int        pfkey_socket(struct iked *);
 void    pfkey_init(struct iked *, int fd);
 
 /* ca.c */
-pid_t   caproc(struct privsep *, struct privsep_proc *);
+void    caproc(struct privsep *, struct privsep_proc *);
 int     ca_setreq(struct iked *, struct iked_sa *, struct iked_static_id *,
            uint8_t, uint8_t, uint8_t *, size_t, enum privsep_procid);
 int     ca_setcert(struct iked *, struct iked_sahdr *, struct iked_id *,
@@ -1182,11 +1189,12 @@ void     timer_add(struct iked *, struct iked_timer *, int);
 void    timer_del(struct iked *, struct iked_timer *);
 
 /* proc.c */
-void    proc_init(struct privsep *, struct privsep_proc *, unsigned int);
+void    proc_init(struct privsep *, struct privsep_proc *, unsigned int, int,
+           int, char **, enum privsep_procid);
 void    proc_kill(struct privsep *);
-void    proc_listen(struct privsep *, struct privsep_proc *, size_t);
+void    proc_connect(struct privsep *);
 void    proc_dispatch(int, short event, void *);
-pid_t   proc_run(struct privsep *, struct privsep_proc *,
+void    proc_run(struct privsep *, struct privsep_proc *,
            struct privsep_proc *, unsigned int,
            void (*)(struct privsep *, struct privsep_proc *, void *), void *);
 void    imsg_event_add(struct imsgev *);
@@ -1208,6 +1216,9 @@ struct imsgbuf *
         proc_ibuf(struct privsep *, enum privsep_procid, int);
 struct imsgev *
         proc_iev(struct privsep *, enum privsep_procid, int);
+enum privsep_procid
+        proc_getid(struct privsep_proc *, unsigned int, const char *);
+int     proc_flush_imsg(struct privsep *, enum privsep_procid, int);
 
 /* util.c */
 int     socket_af(struct sockaddr *, in_port_t);
index d35fcc5..d4ff5cb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2.c,v 1.362 2023/02/08 20:10:34 tb Exp $  */
+/*     $OpenBSD: ikev2.c,v 1.363 2023/03/04 22:22:50 tobhe Exp $       */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -196,10 +196,10 @@ static struct privsep_proc procs[] = {
        { "control",    PROC_CONTROL,   ikev2_dispatch_control }
 };
 
-pid_t
+void
 ikev2(struct privsep *ps, struct privsep_proc *p)
 {
-       return (proc_run(ps, p, procs, nitems(procs), ikev2_run, NULL));
+       proc_run(ps, p, procs, nitems(procs), ikev2_run, NULL);
 }
 
 void
@@ -220,7 +220,14 @@ ikev2_run(struct privsep *ps, struct privsep_proc *p, void *arg)
 void
 ikev2_shutdown(struct privsep_proc *p)
 {
-       struct iked             *env = p->p_env;
+       struct iked             *env;
+
+       if (p->p_ps == NULL)
+               return;
+
+       env = p->p_ps->ps_env;
+       if (env == NULL)
+               return;
 
        ibuf_release(env->sc_certreq);
        env->sc_certreq = NULL;
@@ -230,7 +237,7 @@ ikev2_shutdown(struct privsep_proc *p)
 int
 ikev2_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
 {
-       struct iked             *env = p->p_env;
+       struct iked             *env = p->p_ps->ps_env;
        struct iked_sa          *sa, *satmp;
        struct iked_policy      *pol, *old;
 
@@ -306,7 +313,7 @@ ikev2_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
 int
 ikev2_dispatch_cert(int fd, struct privsep_proc *p, struct imsg *imsg)
 {
-       struct iked             *env = p->p_env;
+       struct iked             *env = p->p_ps->ps_env;
        struct iked_sahdr        sh;
        struct iked_sa          *sa;
        uint8_t                  type;
@@ -506,7 +513,7 @@ ikev2_dispatch_cert(int fd, struct privsep_proc *p, struct imsg *imsg)
 int
 ikev2_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg)
 {
-       struct iked             *env = p->p_env;
+       struct iked             *env = p->p_ps->ps_env;
 
        switch (imsg->hdr.type) {
        case IMSG_CTL_RESET_ID:
index 6588f64..a75de44 100644 (file)
@@ -1,7 +1,7 @@
-/*     $OpenBSD: proc.c,v 1.36 2023/02/15 20:44:01 tobhe Exp $ */
+/*     $OpenBSD: proc.c,v 1.37 2023/03/04 22:22:51 tobhe Exp $ */
 
 /*
- * Copyright (c) 2010 - 2014 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2010 - 2016 Reyk Floeter <reyk@openbsd.org>
  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
 #include <sys/socket.h>
 #include <sys/wait.h>
 
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
 #include <signal.h>
+#include <paths.h>
 #include <pwd.h>
 #include <event.h>
 #include <imsg.h>
 
 enum privsep_procid privsep_process;
 
-void    proc_open(struct privsep *, struct privsep_proc *,
-           struct privsep_proc *, size_t);
+void    proc_exec(struct privsep *, struct privsep_proc *, unsigned int, int,
+           int, char **);
+void    proc_setup(struct privsep *, struct privsep_proc *, unsigned int);
+void    proc_open(struct privsep *, int, int);
+void    proc_accept(struct privsep *, int, enum privsep_procid,
+           unsigned int);
 void    proc_close(struct privsep *);
 void    proc_shutdown(struct privsep_proc *);
 void    proc_sig_handler(int, short, void *);
 void    proc_range(struct privsep *, enum privsep_procid, int *, int *);
 int     proc_dispatch_null(int, struct privsep_proc *, struct imsg *);
 
-void
-proc_init(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc)
+enum privsep_procid
+proc_getid(struct privsep_proc *procs, unsigned int nproc,
+    const char *proc_name)
 {
-       unsigned int             i, j, src, dst;
-       struct privsep_pipes    *pp;
+       struct privsep_proc     *p;
+       unsigned int             proc;
 
-       /*
-        * Allocate pipes for all process instances (incl. parent)
-        *
-        * - ps->ps_pipes: N:M mapping
-        * N source processes connected to M destination processes:
-        * [src][instances][dst][instances], for example
-        * [PROC_RELAY][3][PROC_CA][3]
-        *
-        * - ps->ps_pp: per-process 1:M part of ps->ps_pipes
-        * Each process instance has a destination array of socketpair fds:
-        * [dst][instances], for example
-        * [PROC_PARENT][0]
-        */
-       for (src = 0; src < PROC_MAX; src++) {
-               /* Allocate destination array for each process */
-               if ((ps->ps_pipes[src] = calloc(ps->ps_ninstances,
-                   sizeof(struct privsep_pipes))) == NULL)
-                       fatal("proc_init: calloc");
-
-               for (i = 0; i < ps->ps_ninstances; i++) {
-                       pp = &ps->ps_pipes[src][i];
-
-                       for (dst = 0; dst < PROC_MAX; dst++) {
-                               /* Allocate maximum fd integers */
-                               if ((pp->pp_pipes[dst] =
-                                   calloc(ps->ps_ninstances,
-                                   sizeof(int))) == NULL)
-                                       fatal("proc_init: calloc");
+       for (proc = 0; proc < nproc; proc++) {
+               p = &procs[proc];
+               if (strcmp(p->p_title, proc_name))
+                       continue;
 
-                               /* Mark fd as unused */
-                               for (j = 0; j < ps->ps_ninstances; j++)
-                                       pp->pp_pipes[dst][j] = -1;
-                       }
-               }
+               return (p->p_id);
        }
 
-       /*
-        * Setup and run the parent and its children
-        */
-       privsep_process = PROC_PARENT;
-       ps->ps_instances[PROC_PARENT] = 1;
-       ps->ps_title[PROC_PARENT] = "parent";
-       ps->ps_pid[PROC_PARENT] = getpid();
-       ps->ps_pp = &ps->ps_pipes[privsep_process][0];
+       return (PROC_MAX);
+}
 
-       for (i = 0; i < nproc; i++) {
-               /* Default to 1 process instance */
-               if (ps->ps_instances[procs[i].p_id] < 1)
-                       ps->ps_instances[procs[i].p_id] = 1;
-               ps->ps_title[procs[i].p_id] = procs[i].p_title;
-       }
+void
+proc_exec(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc,
+    int debug, int argc, char **argv)
+{
+       unsigned int             proc, nargc, i, proc_i;
+       char                    **nargv;
+       struct privsep_proc     *p;
+       char                     num[32];
+       int                      fd;
+
+       /* Prepare the new process argv. */
+       nargv = calloc(argc + 5, sizeof(char *));
+       if (nargv == NULL)
+               fatal("%s: calloc", __func__);
+
+       /* Copy call argument first. */
+       nargc = 0;
+       nargv[nargc++] = argv[0];
+
+       /* Set process name argument and save the position. */
+       nargv[nargc++] = "-P";
+       proc_i = nargc;
+       nargc++;
+
+       /* Point process instance arg to stack and copy the original args. */
+       nargv[nargc++] = "-I";
+       nargv[nargc++] = num;
+       for (i = 1; i < (unsigned int) argc; i++)
+               nargv[nargc++] = argv[i];
+
+       nargv[nargc] = NULL;
 
-       proc_open(ps, NULL, procs, nproc);
+       for (proc = 0; proc < nproc; proc++) {
+               p = &procs[proc];
+
+               /* Update args with process title. */
+               nargv[proc_i] = (char *)(uintptr_t)p->p_title;
+
+               /* Fire children processes. */
+               for (i = 0; i < ps->ps_instances[p->p_id]; i++) {
+                       /* Update the process instance number. */
+                       snprintf(num, sizeof(num), "%u", i);
+
+                       fd = ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0];
+                       ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0] = -1;
+
+                       switch (fork()) {
+                       case -1:
+                               fatal("%s: fork", __func__);
+                               break;
+                       case 0:
+                               /* First create a new session */
+                               if (setsid() == -1)
+                                       fatal("setsid");
+
+                               /* Prepare parent socket. */
+                               if (fd != PROC_PARENT_SOCK_FILENO) {
+                                       if (dup2(fd, PROC_PARENT_SOCK_FILENO)
+                                           == -1)
+                                               fatal("dup2");
+                               } else if (fcntl(fd, F_SETFD, 0) == -1)
+                                       fatal("fcntl");
+
+                               /* Daemons detach from terminal. */
+                               if (!debug && (fd =
+                                   open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
+                                       (void)dup2(fd, STDIN_FILENO);
+                                       (void)dup2(fd, STDOUT_FILENO);
+                                       (void)dup2(fd, STDERR_FILENO);
+                                       if (fd > 2)
+                                               (void)close(fd);
+                               }
 
-       /* Engage! */
-       for (i = 0; i < nproc; i++)
-               ps->ps_pid[procs[i].p_id] = (*procs[i].p_init)(ps, &procs[i]);
+                               execvp(argv[0], nargv);
+                               fatal("%s: execvp", __func__);
+                               break;
+                       default:
+                               /* Close child end. */
+                               close(fd);
+                               break;
+                       }
+               }
+       }
+       free(nargv);
 }
 
 void
-proc_kill(struct privsep *ps)
+proc_connect(struct privsep *ps)
 {
-       pid_t            pid;
-       unsigned int     i;
+       struct imsgev           *iev;
+       unsigned int             src, dst, inst;
 
-       if (privsep_process != PROC_PARENT)
+       /* Don't distribute any sockets if we are not really going to run. */
+       if (ps->ps_noaction)
                return;
 
-       for (i = 0; i < PROC_MAX; i++) {
-               if (ps->ps_pid[i] == 0)
+       for (dst = 0; dst < PROC_MAX; dst++) {
+               /* We don't communicate with ourselves. */
+               if (dst == PROC_PARENT)
                        continue;
-               killpg(ps->ps_pid[i], SIGTERM);
+
+               for (inst = 0; inst < ps->ps_instances[dst]; inst++) {
+                       iev = &ps->ps_ievs[dst][inst];
+                       imsg_init(&iev->ibuf, ps->ps_pp->pp_pipes[dst][inst]);
+                       event_set(&iev->ev, iev->ibuf.fd, iev->events,
+                           iev->handler, iev->data);
+                       event_add(&iev->ev, NULL);
+               }
        }
 
-       do {
-               pid = waitpid(WAIT_ANY, NULL, 0);
-       } while (pid != -1 || (pid == -1 && errno == EINTR));
+       /* Distribute the socketpair()s for everyone. */
+       for (src = 0; src < PROC_MAX; src++)
+               for (dst = src; dst < PROC_MAX; dst++) {
+                       /* Parent already distributed its fds. */
+                       if (src == PROC_PARENT || dst == PROC_PARENT)
+                               continue;
 
-       proc_close(ps);
+                       proc_open(ps, src, dst);
+               }
 }
 
 void
-proc_open(struct privsep *ps, struct privsep_proc *p,
-    struct privsep_proc *procs, size_t nproc)
+proc_init(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc,
+    int debug, int argc, char **argv, enum privsep_procid proc_id)
 {
+       struct privsep_proc     *p = NULL;
        struct privsep_pipes    *pa, *pb;
+       unsigned int             proc;
+       unsigned int             dst;
        int                      fds[2];
-       unsigned int             i, j, src, proc;
 
-       if (p == NULL)
-               src = privsep_process; /* parent */
-       else
-               src = p->p_id;
+       /* Don't initiate anything if we are not really going to run. */
+       if (ps->ps_noaction)
+               return;
 
-       /*
-        * Open socket pairs for our peers
-        */
-       for (proc = 0; proc < nproc; proc++) {
-               procs[proc].p_ps = ps;
-               procs[proc].p_env = ps->ps_env;
-               if (procs[proc].p_cb == NULL)
-                       procs[proc].p_cb = proc_dispatch_null;
+       if (proc_id == PROC_PARENT) {
+               privsep_process = PROC_PARENT;
+               proc_setup(ps, procs, nproc);
 
-               for (i = 0; i < ps->ps_instances[src]; i++) {
-                       for (j = 0; j < ps->ps_instances[procs[proc].p_id];
-                           j++) {
-                               pa = &ps->ps_pipes[src][i];
-                               pb = &ps->ps_pipes[procs[proc].p_id][j];
-
-                               /* Check if fds are already set by peer */
-                               if (pa->pp_pipes[procs[proc].p_id][j] != -1)
-                                       continue;
+               /*
+                * Create the children sockets so we can use them
+                * to distribute the rest of the socketpair()s using
+                * proc_connect() later.
+                */
+               for (dst = 0; dst < PROC_MAX; dst++) {
+                       /* Don't create socket for ourselves. */
+                       if (dst == PROC_PARENT)
+                               continue;
 
+                       for (proc = 0; proc < ps->ps_instances[dst]; proc++) {
+                               pa = &ps->ps_pipes[PROC_PARENT][0];
+                               pb = &ps->ps_pipes[dst][proc];
                                if (socketpair(AF_UNIX,
-                                   SOCK_STREAM | SOCK_NONBLOCK,
+                                   SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
                                    PF_UNSPEC, fds) == -1)
-                                       fatal("socketpair");
+                                       fatal("%s: socketpair", __func__);
 
-                               pa->pp_pipes[procs[proc].p_id][j] = fds[0];
-                               pb->pp_pipes[src][i] = fds[1];
+                               pa->pp_pipes[dst][proc] = fds[0];
+                               pb->pp_pipes[PROC_PARENT][0] = fds[1];
                        }
                }
+
+               /* Engage! */
+               proc_exec(ps, procs, nproc, debug, argc, argv);
+               return;
        }
+
+       /* Initialize a child */
+       for (proc = 0; proc < nproc; proc++) {
+               if (procs[proc].p_id != proc_id)
+                       continue;
+               p = &procs[proc];
+               break;
+       }
+       if (p == NULL || p->p_init == NULL)
+               fatalx("%s: process %d missing process initialization",
+                   __func__, proc_id);
+
+       p->p_init(ps, p);
+
+       fatalx("failed to initiate child process");
+}
+
+void
+proc_accept(struct privsep *ps, int fd, enum privsep_procid dst,
+    unsigned int n)
+{
+       struct privsep_pipes    *pp = ps->ps_pp;
+       struct imsgev           *iev;
+
+       if (ps->ps_ievs[dst] == NULL) {
+#if DEBUG > 1
+               log_debug("%s: %s src %d %d to dst %d %d not connected",
+                   __func__, ps->ps_title[privsep_process],
+                   privsep_process, ps->ps_instance + 1,
+                   dst, n + 1);
+#endif
+               close(fd);
+               return;
+       }
+
+       if (pp->pp_pipes[dst][n] != -1) {
+               log_warnx("%s: duplicated descriptor", __func__);
+               close(fd);
+               return;
+       } else
+               pp->pp_pipes[dst][n] = fd;
+
+       iev = &ps->ps_ievs[dst][n];
+       imsg_init(&iev->ibuf, fd);
+       event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
+       event_add(&iev->ev, NULL);
 }
 
 void
-proc_listen(struct privsep *ps, struct privsep_proc *procs, size_t nproc)
+proc_setup(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc)
 {
-       unsigned int             i, dst, src, n, m;
+       unsigned int             i, j, src, dst, id;
        struct privsep_pipes    *pp;
 
+       /* Initialize parent title, ps_instances and procs. */
+       ps->ps_title[PROC_PARENT] = "parent";
+
+       for (src = 0; src < PROC_MAX; src++)
+               /* Default to 1 process instance */
+               if (ps->ps_instances[src] < 1)
+                       ps->ps_instances[src] = 1;
+
+       for (src = 0; src < nproc; src++) {
+               procs[src].p_ps = ps;
+               if (procs[src].p_cb == NULL)
+                       procs[src].p_cb = proc_dispatch_null;
+
+               id = procs[src].p_id;
+               ps->ps_title[id] = procs[src].p_title;
+               if ((ps->ps_ievs[id] = calloc(ps->ps_instances[id],
+                   sizeof(struct imsgev))) == NULL)
+                       fatal("%s: calloc", __func__);
+
+               /* With this set up, we are ready to call imsg_init(). */
+               for (i = 0; i < ps->ps_instances[id]; i++) {
+                       ps->ps_ievs[id][i].handler = proc_dispatch;
+                       ps->ps_ievs[id][i].events = EV_READ;
+                       ps->ps_ievs[id][i].proc = &procs[src];
+                       ps->ps_ievs[id][i].data = &ps->ps_ievs[id][i];
+               }
+       }
+
        /*
-        * Close unused pipes
+        * Allocate pipes for all process instances (incl. parent)
+        *
+        * - ps->ps_pipes: N:M mapping
+        * N source processes connected to M destination processes:
+        * [src][instances][dst][instances], for example
+        * [PROC_RELAY][3][PROC_CA][3]
+        *
+        * - ps->ps_pp: per-process 1:M part of ps->ps_pipes
+        * Each process instance has a destination array of socketpair fds:
+        * [dst][instances], for example
+        * [PROC_PARENT][0]
         */
        for (src = 0; src < PROC_MAX; src++) {
-               for (n = 0; n < ps->ps_instances[src]; n++) {
-                       /* Ingore current process */
-                       if (src == (unsigned int)privsep_process &&
-                           n == ps->ps_instance)
-                               continue;
+               /* Allocate destination array for each process */
+               if ((ps->ps_pipes[src] = calloc(ps->ps_instances[src],
+                   sizeof(struct privsep_pipes))) == NULL)
+                       fatal("%s: calloc", __func__);
 
-                       pp = &ps->ps_pipes[src][n];
+               for (i = 0; i < ps->ps_instances[src]; i++) {
+                       pp = &ps->ps_pipes[src][i];
 
                        for (dst = 0; dst < PROC_MAX; dst++) {
-                               if (src == dst)
-                                       continue;
-                               for (m = 0; m < ps->ps_instances[dst]; m++) {
-                                       if (pp->pp_pipes[dst][m] == -1)
-                                               continue;
-
-                                       /* Close and invalidate fd */
-                                       close(pp->pp_pipes[dst][m]);
-                                       pp->pp_pipes[dst][m] = -1;
-                               }
+                               /* Allocate maximum fd integers */
+                               if ((pp->pp_pipes[dst] =
+                                   calloc(ps->ps_instances[dst],
+                                   sizeof(int))) == NULL)
+                                       fatal("%s: calloc", __func__);
+
+                               /* Mark fd as unused */
+                               for (j = 0; j < ps->ps_instances[dst]; j++)
+                                       pp->pp_pipes[dst][j] = -1;
                        }
                }
        }
 
-       src = privsep_process;
-       ps->ps_pp = pp = &ps->ps_pipes[src][ps->ps_instance];
+       ps->ps_pp = &ps->ps_pipes[privsep_process][ps->ps_instance];
+}
 
-       /*
-        * Listen on appropriate pipes
-        */
-       for (i = 0; i < nproc; i++) {
-               dst = procs[i].p_id;
+void
+proc_kill(struct privsep *ps)
+{
+       char            *cause;
+       pid_t            pid;
+       int              len, status;
 
-               if (src == dst)
-                       fatal("proc_listen: cannot peer with oneself");
+       if (privsep_process != PROC_PARENT)
+               return;
 
-               if ((ps->ps_ievs[dst] = calloc(ps->ps_instances[dst],
-                   sizeof(struct imsgev))) == NULL)
-                       fatal("proc_open");
+       proc_close(ps);
 
-               for (n = 0; n < ps->ps_instances[dst]; n++) {
-                       if (pp->pp_pipes[dst][n] == -1)
+       do {
+               pid = waitpid(WAIT_ANY, &status, 0);
+               if (pid <= 0)
+                       continue;
+
+               if (WIFSIGNALED(status)) {
+                       len = asprintf(&cause, "terminated; signal %d",
+                           WTERMSIG(status));
+               } else if (WIFEXITED(status)) {
+                       if (WEXITSTATUS(status) != 0)
+                               len = asprintf(&cause, "exited abnormally");
+                       else
+                               len = 0;
+               } else
+                       len = -1;
+
+               if (len == 0) {
+                       /* child exited OK, don't print a warning message */
+               } else if (len != -1) {
+                       log_warnx("lost child: pid %u %s", pid, cause);
+                       free(cause);
+               } else
+                       log_warnx("lost child: pid %u", pid);
+       } while (pid != -1 || (pid == -1 && errno == EINTR));
+}
+
+void
+proc_open(struct privsep *ps, int src, int dst)
+{
+       struct privsep_pipes    *pa, *pb;
+       struct privsep_fd        pf;
+       int                      fds[2];
+       unsigned int             i, j;
+
+       /* Exchange pipes between process. */
+       for (i = 0; i < ps->ps_instances[src]; i++) {
+               for (j = 0; j < ps->ps_instances[dst]; j++) {
+                       /* Don't create sockets for ourself. */
+                       if (src == dst && i == j)
                                continue;
 
-                       imsg_init(&(ps->ps_ievs[dst][n].ibuf),
-                           pp->pp_pipes[dst][n]);
-                       ps->ps_ievs[dst][n].handler = proc_dispatch;
-                       ps->ps_ievs[dst][n].events = EV_READ;
-                       ps->ps_ievs[dst][n].proc = &procs[i];
-                       ps->ps_ievs[dst][n].data = &ps->ps_ievs[dst][n];
-                       procs[i].p_instance = n;
-
-                       event_set(&(ps->ps_ievs[dst][n].ev),
-                           ps->ps_ievs[dst][n].ibuf.fd,
-                           ps->ps_ievs[dst][n].events,
-                           ps->ps_ievs[dst][n].handler,
-                           ps->ps_ievs[dst][n].data);
-                       event_add(&(ps->ps_ievs[dst][n].ev), NULL);
+                       pa = &ps->ps_pipes[src][i];
+                       pb = &ps->ps_pipes[dst][j];
+                       if (socketpair(AF_UNIX,
+                           SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+                           PF_UNSPEC, fds) == -1)
+                               fatal("%s: socketpair", __func__);
+
+                       pa->pp_pipes[dst][j] = fds[0];
+                       pb->pp_pipes[src][i] = fds[1];
+
+                       pf.pf_procid = src;
+                       pf.pf_instance = i;
+                       if (proc_compose_imsg(ps, dst, j, IMSG_CTL_PROCFD,
+                           -1, pb->pp_pipes[src][i], &pf, sizeof(pf)) == -1)
+                               fatal("%s: proc_compose_imsg", __func__);
+
+                       pf.pf_procid = dst;
+                       pf.pf_instance = j;
+                       if (proc_compose_imsg(ps, src, i, IMSG_CTL_PROCFD,
+                           -1, pa->pp_pipes[dst][j], &pf, sizeof(pf)) == -1)
+                               fatal("%s: proc_compose_imsg", __func__);
+
+                       /*
+                        * We have to flush to send the descriptors and close
+                        * them to avoid the fd ramp on startup.
+                        */
+                       if (proc_flush_imsg(ps, src, i) == -1 ||
+                           proc_flush_imsg(ps, dst, j) == -1)
+                               fatal("%s: imsg_flush", __func__);
                }
        }
 }
@@ -249,7 +444,7 @@ proc_listen(struct privsep *ps, struct privsep_proc *procs, size_t nproc)
 void
 proc_close(struct privsep *ps)
 {
-       unsigned int             src, dst, n, i, j;
+       unsigned int             dst, n;
        struct privsep_pipes    *pp;
 
        if (ps == NULL)
@@ -273,20 +468,6 @@ proc_close(struct privsep *ps)
                }
                free(ps->ps_ievs[dst]);
        }
-
-       /* undo proc_init() */
-       for (src = 0; src < PROC_MAX; src++) {
-               for (i = 0; i < ps->ps_ninstances; i++) {
-                       pp = &ps->ps_pipes[src][i];
-                       for (dst = 0; dst < PROC_MAX; dst++) {
-                               for (j = 0; j < ps->ps_ninstances; j++)
-                                       if (pp->pp_pipes[dst][j] != -1)
-                                               close(pp->pp_pipes[dst][j]);
-                               free(pp->pp_pipes[dst]);
-                       }
-               }
-               free(ps->ps_pipes[src]);
-       }
 }
 
 void
@@ -301,7 +482,7 @@ proc_shutdown(struct privsep_proc *p)
 
        log_info("%s exiting, pid %d", p->p_title, getpid());
 
-       _exit(0);
+       exit(0);
 }
 
 void
@@ -321,51 +502,39 @@ proc_sig_handler(int sig, short event, void *arg)
                /* ignore */
                break;
        default:
-               fatalx("proc_sig_handler: unexpected signal");
+               fatalx("%s: unexpected signal", __func__);
                /* NOTREACHED */
        }
 }
 
-pid_t
+void
 proc_run(struct privsep *ps, struct privsep_proc *p,
     struct privsep_proc *procs, unsigned int nproc,
     void (*run)(struct privsep *, struct privsep_proc *, void *), void *arg)
 {
-       pid_t                    pid;
        struct passwd           *pw;
        const char              *root;
        struct control_sock     *rcs;
-       unsigned int             n;
 
-       if (ps->ps_noaction)
-               return (0);
-
-       proc_open(ps, p, procs, nproc);
-
-       /* Fork child handlers */
-       switch (pid = fork()) {
-       case -1:
-               fatal("proc_run: cannot fork");
-       case 0:
-               log_procinit(p->p_title);
-
-               /* Set the process group of the current process */
-               setpgid(0, 0);
-               break;
-       default:
-               return (pid);
-       }
+       log_procinit(p->p_title);
 
-       pw = ps->ps_pw;
+       /* Set the process group of the current process */
+       setpgid(0, 0);
 
        if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) {
                if (control_init(ps, &ps->ps_csock) == -1)
-                       fatalx(__func__);
+                       fatalx("%s: control_init", __func__);
                TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry)
                        if (control_init(ps, rcs) == -1)
-                               fatalx(__func__);
+                               fatalx("%s: control_init", __func__);
        }
 
+       /* Use non-standard user */
+       if (p->p_pw != NULL)
+               pw = p->p_pw;
+       else
+               pw = ps->ps_pw;
+
        /* Change root directory */
        if (p->p_chroot != NULL)
                root = p->p_chroot;
@@ -373,9 +542,9 @@ proc_run(struct privsep *ps, struct privsep_proc *p,
                root = pw->pw_dir;
 
        if (chroot(root) == -1)
-               fatal("proc_run: chroot");
+               fatal("%s: chroot", __func__);
        if (chdir("/") == -1)
-               fatal("proc_run: chdir(\"/\")");
+               fatal("%s: chdir(\"/\")", __func__);
 
        privsep_process = p->p_id;
 
@@ -384,20 +553,7 @@ proc_run(struct privsep *ps, struct privsep_proc *p,
        if (setgroups(1, &pw->pw_gid) ||
            setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
            setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
-               fatal("proc_run: cannot drop privileges");
-
-       /* Fork child handlers */
-       for (n = 1; n < ps->ps_instances[p->p_id]; n++) {
-               if (fork() == 0) {
-                       ps->ps_instance = p->p_instance = n;
-                       break;
-               }
-       }
-
-#ifdef DEBUG
-       log_debug("%s: %s %d/%d, pid %d", __func__, p->p_title,
-           ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid());
-#endif
+               fatal("%s: cannot drop privileges", __func__);
 
        event_init();
 
@@ -415,24 +571,27 @@ proc_run(struct privsep *ps, struct privsep_proc *p,
        signal_add(&ps->ps_evsigpipe, NULL);
        signal_add(&ps->ps_evsigusr1, NULL);
 
-       proc_listen(ps, procs, nproc);
-
+       proc_setup(ps, procs, nproc);
+       proc_accept(ps, PROC_PARENT_SOCK_FILENO, PROC_PARENT, 0);
        if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) {
                if (control_listen(&ps->ps_csock) == -1)
-                       fatalx(__func__);
+                       fatalx("%s: control_listen", __func__);
                TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry)
                        if (control_listen(rcs) == -1)
-                               fatalx(__func__);
+                               fatalx("%s: control_listen", __func__);
        }
 
+#if DEBUG
+       log_debug("%s: %s %d/%d, pid %d", __func__, p->p_title,
+           ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid());
+#endif
+
        if (run != NULL)
                run(ps, p, arg);
 
        event_dispatch();
 
        proc_shutdown(p);
-
-       return (0);
 }
 
 void
@@ -446,13 +605,14 @@ proc_dispatch(int fd, short event, void *arg)
        ssize_t                  n;
        int                      verbose;
        const char              *title;
+       struct privsep_fd        pf;
 
        title = ps->ps_title[privsep_process];
        ibuf = &iev->ibuf;
 
        if (event & EV_READ) {
                if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
-                       fatal(__func__);
+                       fatal("%s: imsg_read", __func__);
                if (n == 0) {
                        /* this pipe is dead, so remove the event handler */
                        event_del(&iev->ev);
@@ -462,20 +622,26 @@ proc_dispatch(int fd, short event, void *arg)
        }
 
        if (event & EV_WRITE) {
-               if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
-                       fatal(__func__);
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("%s: msgbuf_write", __func__);
+               if (n == 0) {
+                       /* this pipe is dead, so remove the event handler */
+                       event_del(&iev->ev);
+                       event_loopexit(NULL);
+                       return;
+               }
        }
 
        for (;;) {
                if ((n = imsg_get(ibuf, &imsg)) == -1)
-                       fatal(__func__);
+                       fatal("%s: imsg_get", __func__);
                if (n == 0)
                        break;
 
 #if DEBUG > 1
                log_debug("%s: %s %d got imsg %d peerid %d from %s %d",
                    __func__, title, ps->ps_instance + 1,
-                   imsg.hdr.type, imsg.hdr.peerid, p->p_title, p->p_instance);
+                   imsg.hdr.type, imsg.hdr.peerid, p->p_title, imsg.hdr.pid);
 #endif
 
                /*
@@ -496,13 +662,18 @@ proc_dispatch(int fd, short event, void *arg)
                        memcpy(&verbose, imsg.data, sizeof(verbose));
                        log_setverbose(verbose);
                        break;
+               case IMSG_CTL_PROCFD:
+                       IMSG_SIZE_CHECK(&imsg, &pf);
+                       memcpy(&pf, imsg.data, sizeof(pf));
+                       proc_accept(ps, imsg.fd, pf.pf_procid,
+                           pf.pf_instance);
+                       break;
                default:
-                       log_warnx("%s: %s %d got invalid imsg %d peerid %d "
+                       fatalx("%s: %s %d got invalid imsg %d peerid %d "
                            "from %s %d",
                            __func__, title, ps->ps_instance + 1,
                            imsg.hdr.type, imsg.hdr.peerid,
-                           p->p_title, p->p_instance);
-                       fatalx(__func__);
+                           p->p_title, imsg.hdr.pid);
                }
                imsg_free(&imsg);
        }
@@ -584,7 +755,7 @@ proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n,
        proc_range(ps, id, &n, &m);
        for (; n < m; n++) {
                if (imsg_compose_event(&ps->ps_ievs[id][n],
-                   type, peerid, 0, fd, data, datalen) == -1)
+                   type, peerid, ps->ps_instance + 1, fd, data, datalen) == -1)
                        return (-1);
        }
 
@@ -607,7 +778,7 @@ proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n,
        proc_range(ps, id, &n, &m);
        for (; n < m; n++)
                if (imsg_composev_event(&ps->ps_ievs[id][n],
-                   type, peerid, 0, fd, iov, iovcnt) == -1)
+                   type, peerid, ps->ps_instance + 1, fd, iov, iovcnt) == -1)
                        return (-1);
 
        return (0);
@@ -645,3 +816,25 @@ proc_iev(struct privsep *ps, enum privsep_procid id, int n)
        proc_range(ps, id, &n, &m);
        return (&ps->ps_ievs[id][n]);
 }
+
+/* This function should only be called with care as it breaks async I/O */
+int
+proc_flush_imsg(struct privsep *ps, enum privsep_procid id, int n)
+{
+       struct imsgbuf  *ibuf;
+       int              m, ret = 0;
+
+       proc_range(ps, id, &n, &m);
+       for (; n < m; n++) {
+               if ((ibuf = proc_ibuf(ps, id, n)) == NULL)
+                       return (-1);
+               do {
+                       ret = imsg_flush(ibuf);
+               } while (ret == -1 && errno == EAGAIN);
+               if (ret == -1)
+                       break;
+               imsg_event_add(&ps->ps_ievs[id][n]);
+       }
+
+       return (ret);
+}
index d4fc55c..64923db 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: types.h,v 1.51 2022/09/19 20:54:02 tobhe Exp $        */
+/*     $OpenBSD: types.h,v 1.52 2023/03/04 22:22:51 tobhe Exp $        */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -131,7 +131,8 @@ enum imsg_type {
        IMSG_PRIVKEY,
        IMSG_PUBKEY,
        IMSG_CTL_SHOW_CERTSTORE,
-       IMSG_CTL_SHOW_STATS
+       IMSG_CTL_SHOW_STATS,
+       IMSG_CTL_PROCFD,
 };
 
 enum privsep_procid {