From: Alexander Bluhm Subject: syslogd separate parent program To: tech@openbsd.org Date: Sun, 7 Jun 2026 18:29:25 +0200 Hi, syslogd forks and execs its parent process to keep privileged parts separated. This parent process can be easily implemented as a separate program. Then its process image is smaller, especially without additional libs. The new parent got its own main() and a minimal debug log function. ok? bluhm Index: usr.sbin/syslogd/Makefile =================================================================== RCS file: /data/mirror/openbsd/cvs/src/usr.sbin/syslogd/Makefile,v diff -u -p -r1.9 Makefile --- usr.sbin/syslogd/Makefile 13 Jan 2022 10:34:07 -0000 1.9 +++ usr.sbin/syslogd/Makefile 6 Jun 2026 20:42:37 -0000 @@ -1,10 +1,14 @@ # $OpenBSD: Makefile,v 1.9 2022/01/13 10:34:07 martijn Exp $ -PROG= syslogd -SRCS= evbuffer_tls.c log.c parsemsg.c privsep.c privsep_fdpass.c ringbuf.c \ - syslogd.c ttymsg.c -MAN= syslogd.8 syslog.conf.5 -LDADD= -levent -ltls -lssl -lcrypto -DPADD= ${LIBEVENT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} +PROGS= syslogd syslogd-parent + +SRCS_syslogd= evbuffer_tls.c log.c parsemsg.c privsep.c \ + privsep_fdpass.c ringbuf.c syslogd.c ttymsg.c +LDADD_syslogd= -levent -ltls -lssl -lcrypto +DPADD_syslogd= ${LIBEVENT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} + +SRCS_syslogd-parent= parent.c privsep.c privsep_fdpass.c + +MAN= syslogd.8 syslog.conf.5 .include Index: usr.sbin/syslogd/parent.c =================================================================== RCS file: usr.sbin/syslogd/parent.c diff -N usr.sbin/syslogd/parent.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ usr.sbin/syslogd/parent.c 7 Jun 2026 16:13:52 -0000 @@ -0,0 +1,136 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Alexander Bluhm + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "syslogd.h" + +static int verbose; +static char *debug_ebuf; + +/* parent process is re-execed with reduced arguments, others ignored */ + +static __dead void +usage(void) +{ + (void)fprintf(stderr, + "usage: syslogd-parent [-dn] [-f config_file] -P child_pid\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + char *ConfFile = _PATH_LOGCONF; + int Debug = 0; + int PrivChild = 0; + int NoDNS = 0; + const char *errstr; + int ch; + + while ((ch = getopt(argc, argv, + "46a:C:c:dFf:hK:k:m:nP:p:rS:s:T:U:uVZ")) != -1) { + switch (ch) { + case '4': + case '6': + case 'a': + case 'C': + case 'c': + case 'd': /* debug */ + Debug++; + break; + case 'F': + break; + case 'f': /* configuration file */ + ConfFile = optarg; + break; + case 'h': + case 'K': + case 'k': + case 'm': + break; + case 'n': /* don't do DNS lookups */ + NoDNS = 1; + break; + case 'P': /* used internally, exec the parent */ + PrivChild = strtonum(optarg, 2, INT_MAX, &errstr); + if (errstr) + errx(1, "priv child %s: %s", errstr, optarg); + break; + case 'p': + case 'r': + case 'S': + case 's': + case 'T': + case 'U': + case 'u': + case 'V': + case 'Z': + break; + default: + usage(); + } + } + if (argc != optind) + usage(); + if (PrivChild < 2) + errx(1, "parent requires -P child_pid"); + + log_init(Debug, LOG_SYSLOG); + priv_exec(ConfFile, NoDNS, PrivChild, argc, argv); + + /* NOTREACHED */ + return 1; +} + +void +log_init(int n_debug, int fac) +{ + verbose = n_debug; + + if (debug_ebuf == NULL) + if ((debug_ebuf = malloc(ERRBUFSIZE)) == NULL) + err(1, "allocate debug buffer"); + debug_ebuf[0] = '\0'; +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + int saved_errno; + + if (verbose) { + saved_errno = errno; + va_start(ap, emsg); + vsnprintf(debug_ebuf, ERRBUFSIZE, emsg, ap); + fprintf(stderr, "[priv]: %s\n", debug_ebuf); + fflush(stderr); + va_end(ap); + errno = saved_errno; + } + debug_ebuf[0] = '\0'; +} Index: usr.sbin/syslogd/privsep.c =================================================================== RCS file: /data/mirror/openbsd/cvs/src/usr.sbin/syslogd/privsep.c,v diff -u -p -r1.77 privsep.c --- usr.sbin/syslogd/privsep.c 12 Oct 2023 22:36:54 -0000 1.77 +++ usr.sbin/syslogd/privsep.c 7 Jun 2026 16:14:05 -0000 @@ -96,10 +96,10 @@ static int may_read(int, void *, size_t static struct passwd *pw; void -priv_init(int lockfd, int nullfd, int argc, char *argv[]) +priv_init(int debug, int lockfd, int nullfd, int argc, char *argv[]) { int i, socks[2]; - char *execpath, childnum[11], **privargv; + char childnum[11], **privargv; /* Create sockets */ if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) @@ -132,14 +132,10 @@ priv_init(int lockfd, int nullfd, int ar } close(socks[1]); - if (strchr(argv[0], '/') == NULL) - execpath = argv[0]; - else if ((execpath = realpath(argv[0], NULL)) == NULL) - err(1, "realpath %s", argv[0]); if (chdir("/") != 0) err(1, "chdir /"); - if (!Debug) { + if (!debug) { close(lockfd); dup2(nullfd, STDIN_FILENO); dup2(nullfd, STDOUT_FILENO); @@ -156,18 +152,18 @@ priv_init(int lockfd, int nullfd, int ar snprintf(childnum, sizeof(childnum), "%d", child_pid); if ((privargv = reallocarray(NULL, argc + 3, sizeof(char *))) == NULL) err(1, "alloc priv argv failed"); - privargv[0] = execpath; + privargv[0] = "/usr/sbin/syslogd-parent"; for (i = 1; i < argc; i++) privargv[i] = argv[i]; privargv[i++] = "-P"; privargv[i++] = childnum; privargv[i++] = NULL; - execvp(privargv[0], privargv); + execv(privargv[0], privargv); err(1, "exec priv '%s' failed", privargv[0]); } __dead void -priv_exec(char *conf, int numeric, int child, int argc, char *argv[]) +priv_exec(const char *conf, int numeric, int child, int argc, char *argv[]) { int i, fd, sock, cmd, addr_len, result, restart; size_t path_len, protoname_len, hostname_len, servname_len; @@ -235,7 +231,7 @@ priv_exec(char *conf, int numeric, int c sigaction(SIGCHLD, &sa, NULL); setproctitle("[priv]"); - log_debug("[priv]: fork+exec done"); + log_debug("fork+exec done"); sigemptyset(&sigmask); if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1) @@ -253,7 +249,7 @@ priv_exec(char *conf, int numeric, int c break; switch (cmd) { case PRIV_OPEN_TTY: - log_debug("[priv]: msg PRIV_OPEN_TTY received"); + log_debug("msg PRIV_OPEN_TTY received"); /* Expecting: length, path */ must_read(sock, &path_len, sizeof(size_t)); if (path_len == 0 || path_len > sizeof(path)) @@ -271,7 +267,7 @@ priv_exec(char *conf, int numeric, int c case PRIV_OPEN_LOG: case PRIV_OPEN_PIPE: - log_debug("[priv]: msg PRIV_OPEN_%s received", + log_debug("msg PRIV_OPEN_%s received", cmd == PRIV_OPEN_PIPE ? "PIPE" : "LOG"); /* Expecting: length, path */ must_read(sock, &path_len, sizeof(size_t)); @@ -296,7 +292,7 @@ priv_exec(char *conf, int numeric, int c break; case PRIV_OPEN_UTMP: - log_debug("[priv]: msg PRIV_OPEN_UTMP received"); + log_debug("msg PRIV_OPEN_UTMP received"); fd = open(_PATH_UTMP, O_RDONLY|O_NONBLOCK); send_fd(sock, fd); if (fd == -1) @@ -306,7 +302,7 @@ priv_exec(char *conf, int numeric, int c break; case PRIV_OPEN_CONFIG: - log_debug("[priv]: msg PRIV_OPEN_CONFIG received"); + log_debug("msg PRIV_OPEN_CONFIG received"); stat(conf, &cf_info); fd = open(conf, O_RDONLY|O_NONBLOCK); send_fd(sock, fd); @@ -317,7 +313,7 @@ priv_exec(char *conf, int numeric, int c break; case PRIV_CONFIG_MODIFIED: - log_debug("[priv]: msg PRIV_CONFIG_MODIFIED received"); + log_debug("msg PRIV_CONFIG_MODIFIED received"); if (stat(conf, &cf_stat) == -1 || timespeccmp(&cf_info.st_mtim, &cf_stat.st_mtim, <) || @@ -335,13 +331,13 @@ priv_exec(char *conf, int numeric, int c if (pledge("stdio rpath wpath cpath dns sendfd id proc exec", NULL) == -1) err(1, "pledge done config"); - log_debug("[priv]: msg PRIV_DONE_CONFIG_PARSE " + log_debug("msg PRIV_DONE_CONFIG_PARSE " "received"); increase_state(STATE_RUNNING); break; case PRIV_GETADDRINFO: - log_debug("[priv]: msg PRIV_GETADDRINFO received"); + log_debug("msg PRIV_GETADDRINFO received"); /* Expecting: len, proto, len, host, len, serv */ must_read(sock, &protoname_len, sizeof(size_t)); if (protoname_len == 0 || @@ -407,7 +403,7 @@ priv_exec(char *conf, int numeric, int c break; case PRIV_GETNAMEINFO: - log_debug("[priv]: msg PRIV_GETNAMEINFO received"); + log_debug("msg PRIV_GETNAMEINFO received"); if (numeric) errx(1, "rejected attempt to getnameinfo"); /* Expecting: length, sockaddr */ @@ -442,7 +438,8 @@ priv_exec(char *conf, int numeric, int c sigaddset(&sigmask, SIGHUP); if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1) err(1, "sigprocmask exec"); - execvp(argv[0], argv); + argv[0] = "/usr/sbin/syslogd"; + execv(argv[0], argv); err(1, "exec restart '%s' failed", argv[0]); } unlink(_PATH_LOGPID); Index: usr.sbin/syslogd/syslogd.c =================================================================== RCS file: /data/mirror/openbsd/cvs/src/usr.sbin/syslogd/syslogd.c,v diff -u -p -r1.287 syslogd.c --- usr.sbin/syslogd/syslogd.c 26 Jun 2025 19:10:13 -0000 1.287 +++ usr.sbin/syslogd/syslogd.c 7 Jun 2026 16:14:05 -0000 @@ -121,7 +121,7 @@ #include "evbuffer_tls.h" #include "parsemsg.h" -char *ConfFile = _PATH_LOGCONF; +const char *ConfFile = _PATH_LOGCONF; const char ctty[] = _PATH_CONSOLE; #define MAXUNAMES 20 /* maximum number of user names */ @@ -226,7 +226,6 @@ int Initialized = 0; /* set when we have int MarkInterval = 20 * 60; /* interval between marks in seconds */ int MarkSeq = 0; /* mark sequence number */ -int PrivChild = 0; /* Exec the privileged parent process */ int Repeat = 0; /* 0 msg repeated, 1 in files only, 2 never */ int SecureMode = 1; /* when true, speak only unix domain socks */ int NoDNS = 0; /* when true, refrain from doing DNS lookups */ @@ -350,7 +349,6 @@ struct filed *find_dup(struct filed *); void printline(char *, char *); void printsys(char *); void current_time(char *); -void usage(void); void wallmsg(struct filed *, struct iovec *); int loghost_parse(char *, char **, char **, char **); int getmsgbufsize(void); @@ -363,6 +361,17 @@ void set_sockbuf(int); void set_keepalive(int); void tailify_replytext(char *, int); +static __dead void +usage(void) +{ + (void)fprintf(stderr, + "usage: syslogd [-46dFhnruVZ] [-a path] [-C CAfile]\n" + "\t[-c cert_file] [-f config_file] [-K CAfile] [-k key_file]\n" + "\t[-m mark_interval] [-p log_socket] [-S listen_address]\n" + "\t[-s reporting_socket] [-T listen_address] [-U bind_address]\n"); + exit(1); +} + int main(int argc, char *argv[]) { @@ -399,7 +408,7 @@ main(int argc, char *argv[]) nbind = nlisten = ntls = 0; while ((ch = getopt(argc, argv, - "46a:C:c:dFf:hK:k:m:nP:p:rS:s:T:U:uVZ")) != -1) { + "46a:C:c:dFf:hK:k:m:np:rS:s:T:U:uVZ")) != -1) { switch (ch) { case '4': /* disable IPv6 */ Family = PF_INET; @@ -446,11 +455,6 @@ main(int argc, char *argv[]) case 'n': /* don't do DNS lookups */ NoDNS = 1; break; - case 'P': /* used internally, exec the parent */ - PrivChild = strtonum(optarg, 2, INT_MAX, &errstr); - if (errstr) - errx(1, "priv child %s: %s", errstr, optarg); - break; case 'p': /* path */ path_unix[0] = optarg; break; @@ -503,9 +507,6 @@ main(int argc, char *argv[]) fatal("dup2 null"); } - if (PrivChild > 1) - priv_exec(ConfFile, NoDNS, PrivChild, argc, argv); - consfile.f_type = F_CONSOLE; (void)strlcpy(consfile.f_un.f_fname, ctty, sizeof(consfile.f_un.f_fname)); @@ -757,7 +758,7 @@ main(int argc, char *argv[]) } /* Privilege separation begins here */ - priv_init(lockpipe[1], nullfd, argc, argv); + priv_init(Debug, lockpipe[1], nullfd, argc, argv); if (pledge("stdio unix inet recvfd", NULL) == -1) err(1, "pledge"); @@ -1662,18 +1663,6 @@ tcpbuf_countmsg(struct bufferevent *bufe i++; } return (i); -} - -void -usage(void) -{ - - (void)fprintf(stderr, - "usage: syslogd [-46dFhnruVZ] [-a path] [-C CAfile]\n" - "\t[-c cert_file] [-f config_file] [-K CAfile] [-k key_file]\n" - "\t[-m mark_interval] [-p log_socket] [-S listen_address]\n" - "\t[-s reporting_socket] [-T listen_address] [-U bind_address]\n"); - exit(1); } /* Index: usr.sbin/syslogd/syslogd.h =================================================================== RCS file: /data/mirror/openbsd/cvs/src/usr.sbin/syslogd/syslogd.h,v diff -u -p -r1.37 syslogd.h --- usr.sbin/syslogd/syslogd.h 12 Oct 2023 22:36:54 -0000 1.37 +++ usr.sbin/syslogd/syslogd.h 7 Jun 2026 16:14:05 -0000 @@ -26,8 +26,8 @@ extern int ZuluTime; /* Privilege separation */ -void priv_init(int, int, int, char **); -__dead void priv_exec(char *, int, int, int, char **); +void priv_init(int, int, int, int, char **); +__dead void priv_exec(const char *, int, int, int, char **); int priv_open_tty(const char *); int priv_open_log(const char *); FILE *priv_open_utmp(void); @@ -52,7 +52,6 @@ int receive_fd(int); #define ERRBUFSIZE 256 void vlogmsg(int pri, const char *, const char *, va_list); __dead void die(int); -extern int Debug; struct ringbuf { char *buf; Index: etc/rc.d/syslogd =================================================================== RCS file: /data/mirror/openbsd/cvs/src/etc/rc.d/syslogd,v diff -u -p -r1.5 syslogd --- etc/rc.d/syslogd 11 Jan 2018 19:52:12 -0000 1.5 +++ etc/rc.d/syslogd 7 Jun 2026 08:58:14 -0000 @@ -6,7 +6,7 @@ daemon="/usr/sbin/syslogd" . /etc/rc.d/rc.subr -pexp="syslogd: \[priv\]" +pexp="syslogd-parent: \[priv\]" rc_pre() { rm -f /dev/log