From: tobhe Date: Sat, 4 Mar 2023 22:22:50 +0000 (+0000) Subject: Sync proc.c from vmd(8) to enabled fork + exec for all processes. This gives X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=a7dbf4aec697eadee0b905bf9852905060c8cc4e;p=openbsd Sync proc.c from vmd(8) to enabled fork + exec for all processes. This gives each process a fresh and unique address space to further improve randomization of ASLR and stack protector. ok bluhm@ patrick@ --- diff --git a/sbin/iked/ca.c b/sbin/iked/ca.c index a82fee0273b..10e5eb29ca4 100644 --- a/sbin/iked/ca.c +++ b/sbin/iked/ca.c @@ -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 @@ -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) { diff --git a/sbin/iked/control.c b/sbin/iked/control.c index b76a545c5fb..2aa3f9a5968 100644 --- a/sbin/iked/control.c +++ b/sbin/iked/control.c @@ -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 @@ -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"); } diff --git a/sbin/iked/iked.c b/sbin/iked/iked.c index 210dd998378..858d1e2b80f 100644 --- a/sbin/iked/iked.c +++ b/sbin/iked/iked.c @@ -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 @@ -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); diff --git a/sbin/iked/iked.h b/sbin/iked/iked.h index 01ea6a496b4..8f84b2aaeff 100644 --- a/sbin/iked/iked.h +++ b/sbin/iked/iked.h @@ -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 @@ -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); diff --git a/sbin/iked/ikev2.c b/sbin/iked/ikev2.c index d35fcc5520e..d4ff5cb41b8 100644 --- a/sbin/iked/ikev2.c +++ b/sbin/iked/ikev2.c @@ -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 @@ -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: diff --git a/sbin/iked/proc.c b/sbin/iked/proc.c index 6588f647bbd..a75de447d19 100644 --- a/sbin/iked/proc.c +++ b/sbin/iked/proc.c @@ -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 + * Copyright (c) 2010 - 2016 Reyk Floeter * Copyright (c) 2008 Pierre-Yves Ritschard * * Permission to use, copy, modify, and distribute this software for any @@ -22,12 +22,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -36,212 +38,405 @@ 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); +} diff --git a/sbin/iked/types.h b/sbin/iked/types.h index d4fc55cba76..64923dbb2a2 100644 --- a/sbin/iked/types.h +++ b/sbin/iked/types.h @@ -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 @@ -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 {