diff --git a/compat/pidfile.c b/compat/pidfile.c index 2242bd26..795bf380 100644 --- a/compat/pidfile.c +++ b/compat/pidfile.c @@ -1,8 +1,8 @@ -/* $NetBSD: pidfile.c,v 1.16 2021/08/01 15:29:29 andvar Exp $ */ +/* $NetBSD: pidfile.c,v 1.18 2026/05/31 11:31:02 roy Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause - * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc. + * Copyright (c) 1999, 2016, 2026 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -31,6 +31,8 @@ */ #include +#include +#include #include #include @@ -38,31 +40,87 @@ #include #include #include -#include #include +#include #include #include -#include /* for flock(2) */ -#include "config.h" -#include "defs.h" +#ifdef PIDFILE_LOCAL +#include "pidfile.h" +#else +#include +#endif + +#ifdef __RCSID +__RCSID("$NetBSD: pidfile.c,v 1.18 2026/05/31 11:31:02 roy Exp $"); +#endif + +static char *pf_path; +static int pf_fd = -1; +static bool pf_removeable = true; + +/* Reads a pid from a file descriptor */ +static pid_t +pidfile_readfd(int fd) +{ + char buf[16], *eptr; + int error; + ssize_t n; + pid_t pid = -1; + + if (lseek(fd, 0, SEEK_SET) == -1) + return -1; + + n = read(fd, buf, sizeof(buf) - 1); + if (n == -1) + return -1; -static pid_t pidfile_pid; -static char pidfile_path[PATH_MAX]; -static int pidfile_fd = -1; + buf[n] = '\0'; + pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error); + if (error != 0 && !(error == ENOTSUP && *eptr == '\n')) + return -1; -/* Closes pidfile resources. + return pid; +} + +int +pidfile_fd(void) +{ + + return pf_fd; +} + +const char * +pidfile_path(void) +{ + + return pf_path; +} + +void +pidfile_unremoveable(void) +{ + + pf_removeable = false; +} + +/* Releases pidfile resources. * * Returns 0 on success, otherwise -1. */ -static int -pidfile_close(void) +int +pidfile_unlock(void) { int error; - pidfile_pid = 0; - error = close(pidfile_fd); - pidfile_fd = -1; - pidfile_path[0] = '\0'; + if (pf_fd == -1) { + error = -1; + errno = EBADF; + } else { + error = close(pf_fd); + pf_fd = -1; + } + free(pf_path); + pf_path = NULL; return error; } @@ -71,29 +129,31 @@ pidfile_close(void) * The pidfile is truncated because we may have dropped permissions * or entered a chroot and thus unable to unlink it. * - * Returns 0 on truncation success, otherwise -1. */ + * Returns 0 on success, otherwise -1. */ int pidfile_clean(void) { int error; + pid_t pid; - if (pidfile_fd == -1) { + if (pf_fd == -1) { errno = EBADF; return -1; } - if (pidfile_pid != getpid()) + pid = pidfile_readfd(pf_fd); + if (pid == -1) + error = errno; + else if (pid != getpid()) error = EPERM; - else if (ftruncate(pidfile_fd, 0) == -1) + else if (ftruncate(pf_fd, 0) == -1) error = errno; - else { -#ifndef HAVE_PLEDGE /* Avoid a pledge violating segfault. */ - (void)unlink(pidfile_path); -#endif + else if (pf_removeable && unlink(pf_path) == -1) + error = errno; + else error = 0; - } - (void) pidfile_close(); + (void)pidfile_unlock(); if (error != 0) { errno = error; @@ -107,7 +167,7 @@ static void pidfile_cleanup(void) { - pidfile_clean(); + (void)pidfile_clean(); } /* Constructs a name for a pidfile in the default location (/var/run). @@ -116,19 +176,14 @@ pidfile_cleanup(void) * * Returns 0 on success, otherwise -1. */ static int -pidfile_varrun_path(char *path, size_t len, const char *bname) +pidfile_varrun_path(char **path, const char *bname) { if (bname == NULL) - bname = PACKAGE; + bname = getprogname(); /* _PATH_VARRUN includes trailing / */ - if ((size_t)snprintf(path, len, "%s%s.pid", _PATH_VARRUN, bname) >= len) - { - errno = ENAMETOOLONG; - return -1; - } - return 0; + return asprintf(path, "%s%s.pid", _PATH_VARRUN, bname); } /* Returns the process ID inside path on success, otherwise -1. @@ -136,40 +191,35 @@ pidfile_varrun_path(char *path, size_t len, const char *bname) pid_t pidfile_read(const char *path) { - char dpath[PATH_MAX], buf[16], *eptr; - int fd, error; - ssize_t n; - pid_t pid; + char *dpath = NULL; + int fd; + pid_t pid = -1; - if (path == NULL && pidfile_path[0] != '\0') - path = pidfile_path; + if (path == NULL && pf_path != NULL) + path = pf_path; if (path == NULL || strchr(path, '/') == NULL) { - if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1) - return -1; + if (pidfile_varrun_path(&dpath, path) == -1) + goto out; path = dpath; } - if ((fd = open(path, O_RDONLY | O_NONBLOCK)) == -1) - return -1; - n = read(fd, buf, sizeof(buf) - 1); - error = errno; - (void) close(fd); - if (n == -1) { - errno = error; - return -1; - } - buf[n] = '\0'; - pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error); - if (error && !(error == ENOTSUP && *eptr == '\n')) { - errno = error; - return -1; - } + if (pf_fd != -1 && strcmp(path, pf_path) == 0) + fd = pf_fd; + else if ((fd = open(path, O_RDONLY | O_NONBLOCK)) == -1) + goto out; + + pid = pidfile_readfd(fd); + if (fd != pf_fd) + (void)close(fd); + +out: + free(dpath); return pid; } /* Locks the pidfile specified by path and writes the process pid to it. - * The new pidfile is "registered" in the global variables pidfile_fd, - * pidfile_path and pidfile_pid so that any further call to pidfile_lock(3) + * The new pidfile is "registered" in the global variables pf_fd, + * pf_path and so that any further call to pidfile_lock(3) * can check if we are recreating the same file or a new one. * * Returns 0 on success, otherwise the pid of the process who owns the @@ -177,57 +227,58 @@ pidfile_read(const char *path) pid_t pidfile_lock(const char *path) { - char dpath[PATH_MAX]; + char *dpath = NULL; static bool registered_atexit = false; + pid_t pid = -1; /* Register for cleanup with atexit. */ if (!registered_atexit) { if (atexit(pidfile_cleanup) == -1) - return -1; + goto out; registered_atexit = true; } if (path == NULL || strchr(path, '/') == NULL) { - if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1) - return -1; + if (pidfile_varrun_path(&dpath, path) == -1) + goto out; path = dpath; } /* If path has changed (no good reason), clean up the old pidfile. */ - if (pidfile_fd != -1 && strcmp(pidfile_path, path) != 0) - pidfile_clean(); + if (pf_fd != -1 && strcmp(pf_path, path) != 0) + (void)pidfile_clean(); - if (pidfile_fd == -1) { + if (pf_fd == -1) { int fd, opts; - opts = O_WRONLY | O_CREAT | O_NONBLOCK; + opts = O_RDWR | O_CREAT | O_NONBLOCK; #ifdef O_CLOEXEC opts |= O_CLOEXEC; #endif #ifdef O_EXLOCK - opts |= O_EXLOCK; + opts |= O_EXLOCK; #endif if ((fd = open(path, opts, 0644)) == -1) goto return_pid; + #ifndef O_CLOEXEC if ((opts = fcntl(fd, F_GETFD)) == -1 || - fcntl(fd, F_SETFL, opts | FD_CLOEXEC) == -1) - { + fcntl(fd, F_SETFD, opts | FD_CLOEXEC) == -1) { int error = errno; - (void) close(fd); + (void)close(fd); errno = error; - return -1; + goto out; } #endif #ifndef O_EXLOCK if (flock(fd, LOCK_EX | LOCK_NB) == -1) { int error = errno; - (void) close(fd); + (void)close(fd); if (error != EAGAIN) { errno = error; - return -1; + goto out; } fd = -1; } @@ -235,8 +286,6 @@ pidfile_lock(const char *path) return_pid: if (fd == -1) { - pid_t pid; - if (errno == EAGAIN) { /* The pidfile is locked, return the process ID * it contains. @@ -246,27 +295,56 @@ pidfile_lock(const char *path) } else pid = -1; - return pid; + goto out; } - pidfile_fd = fd; - strlcpy(pidfile_path, path, sizeof(pidfile_path)); - } - pidfile_pid = getpid(); + pf_fd = fd; + if (path == dpath) { + pf_path = dpath; + dpath = NULL; + } else { + pf_path = strdup(path); + if (pf_path == NULL) { + int error = errno; + + (void)close(pf_fd); + pf_fd = -1; + errno = error; + goto out; + } + } + } /* Truncate the file, as we could be re-writing it. * Then write the process ID. */ - if (ftruncate(pidfile_fd, 0) == -1 || - lseek(pidfile_fd, 0, SEEK_SET) == -1 || - dprintf(pidfile_fd, "%d\n", (int)pidfile_pid) == -1) - { + if (ftruncate(pf_fd, 0) == -1 || lseek(pf_fd, 0, SEEK_SET) == -1 || + dprintf(pf_fd, "%ld\n", (long)getpid()) == -1) { int error = errno; - pidfile_cleanup(); + (void)pidfile_clean(); errno = error; - return -1; + goto out; } /* Hold the fd open to persist the lock. */ - return 0; + pid = 0; + +out: + free(dpath); + return pid; +} + +/* The old function. + * Historical behaviour is that pidfile is not re-written + * if path has not changed. + * + * Returns 0 on success, otherwise -1. + * As such we have no way of knowing the process ID who owns the lock. */ +int +pidfile(const char *path) +{ + pid_t pid; + + pid = pidfile_lock(path); + return pid == 0 ? 0 : -1; } diff --git a/compat/pidfile.h b/compat/pidfile.h index c71b0ff6..610225cf 100644 --- a/compat/pidfile.h +++ b/compat/pidfile.h @@ -1,6 +1,6 @@ /*- * SPDX-License-Identifier: BSD-2-Clause - * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc. + * Copyright (c) 1999, 2016, 2026 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -31,10 +31,18 @@ #ifndef PIDFILE_H #define PIDFILE_H -#include +#include +#include "config.h" + +int pidfile(const char *); int pidfile_clean(void); pid_t pidfile_lock(const char *); pid_t pidfile_read(const char *); +int pidfile_unlock(void); + +int pidfile_fd(void); +const char * pidfile_path(void); +void pidfile_unremoveable(void); #endif diff --git a/configure b/configure index 44ba0d02..b041bfbb 100755 --- a/configure +++ b/configure @@ -1039,12 +1039,12 @@ elif [ "$PRIVSEP" = yes ]; then fi if [ -z "$PIDFILE_LOCK" ]; then - printf "Testing for pidfile_lock ... " + printf "Testing for pidfile_unlock ... " cat <_pidfile.c #include #include int main(void) { - return (int)pidfile_lock(NULL); + return (int)pidfile_unlock(); } EOF # We only want to link to libutil if it exists in /lib @@ -1065,8 +1065,9 @@ EOF rm -rf _pidfile.* _pidfile fi if [ "$PIDFILE_LOCK" = no ]; then - echo "COMPAT_SRCS+= compat/pidfile.c" >>$CONFIG_MK + echo "CPPFLAGS+= -DPIDFILE_LOCAL" >>$CONFIG_MK echo "#include \"compat/pidfile.h\"" >>$CONFIG_H + echo "COMPAT_SRCS+= compat/pidfile.c" >>$CONFIG_MK else echo "#define HAVE_UTIL_H" >>$CONFIG_H if [ -n "$LIBUTIL" ]; then diff --git a/src/bpf-bsd.c b/src/bpf-bsd.c index 1f1a63f8..a8f479a0 100644 --- a/src/bpf-bsd.c +++ b/src/bpf-bsd.c @@ -36,6 +36,7 @@ #include #include #include +#include #include "bpf.h" #ifdef __sun diff --git a/src/bpf-linux.c b/src/bpf-linux.c index 2dc34111..d0bf0671 100644 --- a/src/bpf-linux.c +++ b/src/bpf-linux.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "bpf.h" diff --git a/src/common.c b/src/common.c index f04b44c5..764209cb 100644 --- a/src/common.c +++ b/src/common.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "common.h" #include "dhcpcd.h" diff --git a/src/dhcpcd.c b/src/dhcpcd.c index 1fa7c3ab..f065c733 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -2289,11 +2289,15 @@ main(int argc, char **argv, char **envp) default: per = ""; } - snprintf(ctx.pidfile, sizeof(ctx.pidfile), PIDFILE, - ifname, per, "."); + if (asprintf(&ctx.pidfile, PIDFILE, ifname, per, ".") == -1) { + logerr("%s: asprintf", __func__); + goto exit_failure; + } } else { - snprintf(ctx.pidfile, sizeof(ctx.pidfile), PIDFILE, "", - "", ""); + if (asprintf(&ctx.pidfile, PIDFILE, "", "", "") == -1) { + logerr("%s: asprintf", __func__); + goto exit_failure; + } ctx.options |= DHCPCD_MANAGER; /* @@ -2582,6 +2586,18 @@ main(int argc, char **argv, char **envp) #endif goto exit_failure; } +#ifdef PRIVSEP_RIGHTS + { + cap_rights_t rights; + + cap_rights_init(&rights, CAP_READ, CAP_SEEK, CAP_FTRUNCATE); + if (cap_rights_limit(pidfile_fd(), &rights) == -1 && + errno != ENOSYS) { + logerr("%s: cap_rights_limit", __func__); + goto exit_failure; + } + } +#endif #endif os_init(); @@ -2817,6 +2833,7 @@ main(int argc, char **argv, char **envp) } #endif + free(ctx.pidfile); eloop_free(ctx.eloop); logclose(); free(ctx.logfile); @@ -2826,9 +2843,5 @@ main(int argc, char **argv, char **envp) setproctitle_fini(); #endif -#ifdef USE_SIGNALS - if (ctx.options & (DHCPCD_FORKED | DHCPCD_PRIVSEP)) - _exit(i); /* so atexit won't remove our pidfile */ -#endif return i; } diff --git a/src/dhcpcd.h b/src/dhcpcd.h index 626261f2..d2fae30b 100644 --- a/src/dhcpcd.h +++ b/src/dhcpcd.h @@ -116,7 +116,7 @@ TAILQ_HEAD(if_head, interface); struct passwd; struct dhcpcd_ctx { - char pidfile[sizeof(PIDFILE) + IF_NAMESIZE + 1]; + char *pidfile; char vendor[256]; int fork_fd; /* FD for the fork init signal pipe */ const char *cffile; diff --git a/src/privsep-control.c b/src/privsep-control.c index 76f6df79..3f58c55b 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "control.h" #include "dhcpcd.h" diff --git a/src/privsep-linux.c b/src/privsep-linux.c index 600e3362..9496fbdd 100644 --- a/src/privsep-linux.c +++ b/src/privsep-linux.c @@ -337,6 +337,9 @@ static struct sock_filter ps_seccomp_filter[] = { #ifdef __NR_fstat64 SECCOMP_ALLOW(__NR_fstat64), #endif +#ifdef __NR_ftruncate + SECCOMP_ALLOW(__NR_ftruncate), +#endif #ifdef __NR_gettimeofday SECCOMP_ALLOW(__NR_gettimeofday), #endif @@ -364,6 +367,9 @@ static struct sock_filter ps_seccomp_filter[] = { /* SECCOMP BPF is newer than nl80211 so we don't need SIOCGIWESSID * which lives in the impossible to include linux/wireless.h header */ #endif +#ifdef __NR_lseek + SECCOMP_ALLOW(__NR_lseek), +#endif #ifdef __NR_madvise /* needed for musl */ SECCOMP_ALLOW(__NR_madvise), #endif diff --git a/src/privsep.c b/src/privsep.c index 33d8aeb5..409bf4ac 100644 --- a/src/privsep.c +++ b/src/privsep.c @@ -392,8 +392,11 @@ ps_startprocess(struct ps_process *psp, return pid; } - /* If we are not the root process, close un-needed stuff. */ + /* Close things we no longer need */ + pidfile_unlock(); eloop_closefdwaiter(ctx->eloop); + + /* Close more if we are not root */ if (ctx->ps_root != psp) { ps_root_close(ctx); #ifdef PLUGIN_DEV @@ -420,7 +423,6 @@ ps_startprocess(struct ps_process *psp, goto errexit; } - pidfile_clean(); ps_freeprocesses(ctx, psp); if (ctx->ps_root != psp) { @@ -622,6 +624,9 @@ ps_managersandbox(struct dhcpcd_ctx *ctx, const char *_pledge) return -1; } + /* We can no longer unlink the pidfile. */ + pidfile_unremoveable(); + #ifdef PRIVSEP_RIGHTS if ((ctx->pf_inet_fd != -1 && ps_rights_limit_ioctl(ctx->pf_inet_fd) == -1) ||