Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4c10010
Add support for Darwin / macOS
rsmarples Apr 6, 2026
e920e7f
Add reallocarray
rsmarples Apr 6, 2026
441de7a
Csat away a compile warning.
rsmarples Apr 6, 2026
eb8dc1e
Solve initgroups compile warning on all OS.
rsmarples Apr 6, 2026
837d25f
Fixup xsetfd
rsmarples Apr 6, 2026
c9c891b
Remove debug
rsmarples Apr 6, 2026
bf8283a
lua: correct message type
rsmarples Apr 6, 2026
f6cd3bc
remove old svc_run
rsmarples Apr 6, 2026
d04a15a
Remove dup define
rsmarples Apr 6, 2026
dd14a87
lua: improve hostname lookup
rsmarples Apr 6, 2026
3bdae24
lua: fix payload len guard
rsmarples Apr 6, 2026
d2034e5
lua: more fixes
rsmarples Apr 6, 2026
44b1998
use blocking sockets
rsmarples Apr 6, 2026
e1c159c
Fix returning service data
rsmarples Apr 7, 2026
19d1d3e
Abort eloop if service watcher fails.
rsmarples Apr 7, 2026
5da02fe
Add macos to github actions
rsmarples Apr 7, 2026
7e6dad8
Fix macos arm64
rsmarples Apr 7, 2026
c450118
Just build on latest OS
rsmarples Apr 7, 2026
8a67a9a
Fix diagnostic message
rsmarples Apr 7, 2026
65a2602
Use latest checkout
rsmarples Apr 7, 2026
bdf0823
Brew and pkgconf are already installed.
rsmarples Apr 7, 2026
6a590c6
Latest checkout
rsmarples Apr 7, 2026
ea815b0
Don't exit on hangup as we already have
rsmarples Apr 7, 2026
f7ee065
eloop: rationalise not setting cloexec on macos
rsmarples Apr 7, 2026
f18a138
lua: ensure we have space to copyout hostname.
rsmarples Apr 7, 2026
b346ca9
Fix xsetfd, close eloop fd with epoll and correct a diagnositic message.
rsmarples Apr 7, 2026
d10a94f
Fix on bsd
rsmarples Apr 7, 2026
0853b1f
Fix again, lol
rsmarples Apr 7, 2026
2824fd2
reallocarray: fix errno and avoid div by zero.
rsmarples Apr 7, 2026
be6a688
Build on macos-15 and macos-26
rsmarples Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ config.mk
config.h
config.log

*.dSYM/**
*.o
*.So
*.soo
*.so

*.tar.xz
Expand Down
2 changes: 0 additions & 2 deletions config-null.mk
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
# This space left intentionally blank

DHCPCD_SRCS+= dhcpcd-embedded.c
54 changes: 39 additions & 15 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -327,13 +327,16 @@ EOF
else
echo "no"
fi
rm -rf _test.c _test
rm -rf _test.* _test
fi
else
echo "CPPFLAGS+= -DNDEBUG" >>$CONFIG_MK
fi

case "$OS" in
darwin*)
echo "LDFLAGS_SO+= -Wl,-undefined,dynamic_lookup" >>$CONFIG_MK
;;
freebsd*|kfreebsd*)
# FreeBSD hide some newer POSIX APIs behind _GNU_SOURCE ...
echo "CPPFLAGS+= -D_GNU_SOURCE" >>$CONFIG_MK
Expand Down Expand Up @@ -381,7 +384,7 @@ _CC=false
if $XCC _test.c -o _test >/dev/null 2>&3; then
[ -x _test ] && _CC=true
fi
rm -f _test.c _test
rm -rf _test.* _test
if ! $_CC; then
echo $XCC
echo "$CC does not create executables" >&2
Expand All @@ -394,8 +397,7 @@ $CC --version | $SED -e '1!d'
if [ -z "$DHCPSD_USER" ]; then
printf "Detecting a suitable user for dhcpsd ... "
for x in _dhcpsd dhcpsd _dhcpd dhcpd; do
home=$(getent passwd $x 2>/dev/null | cut -d: -f6)
if [ -d "$home" ]; then
if id "$x" >/dev/null 2>&1; then
DHCPSD_USER="$x"
echo "$DHCPSD_USER"
break
Expand Down Expand Up @@ -430,7 +432,7 @@ if $XCC _capsicum.c -o _capsicum -lcasper -lcap_net 2>&3; then
else
echo "no"
fi
rm -f _capsicum.c _capsicum
rm -rf _capsicum.* _capsicum
fi

if [ -z "$SANDBOX" ]; then
Expand All @@ -448,7 +450,7 @@ EOF
else
echo "no"
fi
rm -f _pledge.c _pledge
rm -rf _pledge.* _pledge
fi

abort=false
Expand All @@ -474,7 +476,7 @@ else
echo "libc support for getifaddrs is required - aborting" >&2
abort=true
fi
rm -f _getifaddrs.c _getifaddrs
rm -rf _getifaddrs.* _getifaddrs
$abort && exit 1

printf "Testing for clock_gettime ... "
Expand All @@ -495,7 +497,7 @@ else
echo "libc support for clock_getttime is required - aborting" >&2
abort=true
fi
rm -f _clock_gettime.c _clock_gettime
rm -rf _clock_gettime.* _clock_gettime
$abort && exit 1

if [ -z "$ARC4RANDOM" ]; then
Expand All @@ -512,7 +514,7 @@ EOF
ARC4RANDOM=no
fi
echo "$ARC4RANDOM"
rm -f _arc4random.c _arc4random
rm -rf _arc4random.* _arc4random
fi
if [ "$ARC4RANDOM" = no ]; then
echo "COMPAT_SRCS+= compat/arc4random.c" >>$CONFIG_MK
Expand All @@ -536,7 +538,7 @@ EOF
fi
echo "$CLOSEFROM"
fi
rm -f _closefrom.c _closefrom
rm -rf _closefrom.* _closefrom
if [ "$CLOSEFROM" = no ]; then
echo "COMPAT_SRCS+= compat/closefrom.c" >>$CONFIG_MK
echo "#include \"compat/closefrom.h\"" >>$CONFIG_H
Expand All @@ -561,7 +563,7 @@ echo "$IOCTL_REQ"
if [ "$IOCTL_REQ" != "unsigned long" ]; then
echo "#define IOCTL_REQUEST_TYPE $IOCTL_REQ" >>$CONFIG_H
fi
rm -f _ioctl.c _ioctl
rm -rf _ioctl.* _ioctl

printf "Testing for inet_ntoa ... "
cat <<EOF >_inet_ntoa.c
Expand All @@ -586,9 +588,31 @@ else
echo "libc support for inet_ntoa is required - aborting" >&2
abort=true
fi
rm -f _inet_ntoa.c _inet_ntoa
rm -rf _inet_ntoa.* _inet_ntoa
$abort && exit 1

if [ -z "$SETPROCTITLE" ]; then
printf "Testing for setproctitle ... "
cat << EOF >_setproctitle.c
#include <stdlib.h>
#include <unistd.h>
int main(void) {
setproctitle("foo");
return 0;
}
EOF
if $XCC _setproctitle.c -o _setproctitle 2>&3; then
SETPROCTITLE=yes
else
SETPROCTITLE=no
fi
echo "$SETPROCTITLE"
rm -rf _setproctitle.* _setproctitle
fi
if [ "$SETPROCTITLE" != no ]; then
echo "#define HAVE_SETPROCTITLE" >>$CONFIG_H
fi

if [ -z "$STRLCPY" ]; then
printf "Testing for strlcpy ... "
cat <<EOF >_strlcpy.c
Expand All @@ -605,7 +629,7 @@ EOF
STRLCPY=no
fi
echo "$STRLCPY"
rm -f _strlcpy.c _strlcpy
rm -rf _strlcpy.* _strlcpy
fi
if [ "$STRLCPY" = no ]; then
echo "COMPAT_SRCS+= compat/strlcpy.c" >>$CONFIG_MK
Expand All @@ -629,7 +653,7 @@ EOF
RBTREE=no
fi
echo "$RBTREE"
rm -f _rbtree.c _rbtree
rm -rf _rbtree.* _rbtree
fi
if [ "$RBTREE" = no ]; then
echo "VENDOR_SRCS+= vendor/rbtree.c" >>$CONFIG_MK
Expand Down Expand Up @@ -659,7 +683,7 @@ EOF
REALLOCARRAY=no
fi
echo "$REALLOCARRAY"
rm -f _reallocarray.c _reallocarray
rm -rf _reallocarray.* _reallocarray
fi
if [ "$REALLOCARRAY" = no ]; then
echo "COMPAT_SRCS+= compat/reallocarray.c" >>$CONFIG_MK
Expand Down
74 changes: 61 additions & 13 deletions src/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -461,12 +461,40 @@ sa_pton(struct sockaddr *sa, const char *src)
return inet_pton(sa->sa_family, src, addr);
}

#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
static int
xsetfd(int fd, int flags)
{
int xflags, oflags;

oflags = fcntl(fd, F_GETFD);
if (oflags == -1)
return -1;

xflags = oflags;

#ifndef HAVE_SOCK_CLOEXEC
if (flags & SOCK_CLOEXEC)
xflags |= FD_CLOEXEC;
#endif
#ifndef HAVE_SOCK_NONBLOCK
if (flags & SOCK_NONBLOCK)
xflags |= O_NONBLOCK;
#endif

if (xflags == oflags)
return 0;

return fcntl(fd, F_SETFD, xflags);
}
#endif
Comment thread
rsmarples marked this conversation as resolved.

int
xsocket(int domain, int type, int protocol)
{
int s;
#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
int xflags, xtype = type;
int xtype = type;
#endif

#ifndef HAVE_SOCK_CLOEXEC
Expand All @@ -481,24 +509,44 @@ xsocket(int domain, int type, int protocol)
if ((s = socket(domain, type, protocol)) == -1)
return -1;

#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
if (xtype != type && xsetfd(s, xtype) == -1) {
close(s);
return -1;
}
#endif

return s;
}

int
xsocketpair(int domain, int type, int protocol, int fdset[2])
{
int s;
#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
int xtype = type;
#endif

#ifndef HAVE_SOCK_CLOEXEC
if ((xtype & SOCK_CLOEXEC) &&
((xflags = fcntl(s, F_GETFD)) == -1 ||
fcntl(s, F_SETFD, xflags | FD_CLOEXEC) == -1))
goto out;
if (xtype & SOCK_CLOEXEC)
type &= ~SOCK_CLOEXEC;
#endif
#ifndef HAVE_SOCK_NONBLOCK
if ((xtype & SOCK_NONBLOCK) &&
((xflags = fcntl(s, F_GETFL)) == -1 ||
fcntl(s, F_SETFL, xflags | O_NONBLOCK) == -1))
goto out;
if (xtype & SOCK_NONBLOCK)
type &= ~SOCK_NONBLOCK;
#endif

return s;
if ((s = socketpair(domain, type, protocol, fdset)) == -1)
return -1;

#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
out:
close(s);
return -1;
if (xtype != type &&
(xsetfd(fdset[0], xtype) == -1 || xsetfd(fdset[1], xtype) == -1)) {
close(fdset[0]);
close(fdset[1]);
return -1;
}
#endif

return s;
}
1 change: 1 addition & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,5 @@ hash_fnv1a(const void *key, size_t len)
#define SOCK_CXNB SOCK_CLOEXEC | SOCK_NONBLOCK
#endif
int xsocket(int, int, int);
int xsocketpair(int, int, int, int[2]);
#endif
11 changes: 5 additions & 6 deletions src/dhcpsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ dhcpsd_dropperms(int do_chroot)
UNUSED(do_chroot);
#endif

if (initgroups(DHCPSD_USER, pw->pw_gid) == -1 ||
if (initgroups(DHCPSD_USER, (int)pw->pw_gid) == -1 ||
setgid(pw->pw_gid) == -1 || setuid(pw->pw_uid) == -1) {
logerr("%s: error dropping privileges", __func__);
return -1;
Expand Down Expand Up @@ -197,8 +197,7 @@ dhcpsd_fork(struct ctx *ctx)
cap_rights_t rights;
#endif

if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK,
0, fork_fd) == -1) {
if (xsocketpair(PF_LOCAL, SOCK_STREAM | SOCK_CXNB, 0, fork_fd) == -1) {
logerr("socketpair");
return -1;
}
Expand Down Expand Up @@ -250,7 +249,7 @@ dhcpsd_fork(struct ctx *ctx)
}
break;
default:
#ifdef BSD
#ifdef HAVE_SETPROCTITLE
setproctitle("[launcher]");
#endif
ctx->ctx_options &= ~DHCPSD_MAIN;
Expand Down Expand Up @@ -489,7 +488,7 @@ main(int argc, char **argv)
if (dhcpsd_dropperms(1) == -1)
goto exit;

#ifdef BSD
#ifdef HAVE_SETPROCTITLE
setproctitle("DHCP Server Daemon");
#endif

Expand Down Expand Up @@ -606,7 +605,7 @@ main(int argc, char **argv)
dhcp_free(ctx.ctx_dhcp);
eloop_free(ctx.ctx_eloop);
ctx.ctx_eloop = NULL;
svc_free(ctx.ctx_unpriv);
srv_free(ctx.ctx_unpriv);
#ifdef HAVE_CASPER
if (ctx.ctx_capnet)
cap_close(ctx.ctx_capnet);
Expand Down
4 changes: 2 additions & 2 deletions src/dhcpsd.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct ifaddrs;
struct dhcp_ctx;
struct eloop;
struct if_head;
struct svc_ctx;
struct srv_ctx;
struct plugin;
#ifdef HAVE_CASPER
typedef struct cap_channel cap_channel_t;
Expand All @@ -47,7 +47,7 @@ struct ctx {
struct eloop *ctx_eloop;
struct if_head *ctx_ifaces;

struct svc_ctx *ctx_unpriv;
struct srv_ctx *ctx_unpriv;
struct plugin *ctx_plugins;
size_t ctx_nplugins;

Expand Down
8 changes: 5 additions & 3 deletions src/eloop.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
* On Linux use epoll(7)
* Everywhere else use ppoll(2)
*/
#ifdef BSD
#if defined(BSD) || defined(__APPLE__)
#include <sys/event.h>
#define USE_KQUEUE
#if defined(__NetBSD__)
Expand Down Expand Up @@ -625,6 +625,8 @@ eloop_open(struct eloop *eloop)
fd = kqueue1(O_CLOEXEC);
#elif defined(KQUEUE_CLOEXEC)
fd = kqueuex(KQUEUE_CLOEXEC);
#elif defined(USE_KQUEUE) && defined(__APPLE__)
fd = kqueue();
Comment on lines +628 to +633
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that all fork() calls in the codebase are followed by exec() variants
# to confirm the CLOEXEC omission is safe

echo "=== Searching for fork() calls and their context ==="
rg -n -A10 '\bfork\s*\(\s*\)' --type=c

echo ""
echo "=== Checking for exec family calls ==="
rg -n '\b(execl|execle|execlp|execv|execve|execvp|execvpe)\s*\(' --type=c

Repository: NetworkConfiguration/dhcpsd

Length of output: 1449


🏁 Script executed:

# Check if eloop.c exists and examine the file
find . -name "eloop.c" -type f

Repository: NetworkConfiguration/dhcpsd

Length of output: 84


🏁 Script executed:

# Get more context around the fork() calls to see if exec is called
# Let's examine src/dhcpsd.c more closely around the fork calls
head -n 280 src/dhcpsd.c | tail -n 70

Repository: NetworkConfiguration/dhcpsd

Length of output: 1725


🏁 Script executed:

# Search for exec calls more broadly (might be escaped or formatted differently)
rg -n 'exec' --type=c | head -20

Repository: NetworkConfiguration/dhcpsd

Length of output: 140


🏁 Script executed:

# Read src/eloop.c lines 620-640 to see the full context
sed -n '620,640p' src/eloop.c

Repository: NetworkConfiguration/dhcpsd

Length of output: 675


macOS kqueue FD without CLOEXEC — unsafe assumption about fork/exec pattern.

The comment contains two issues:

  1. It states kqueue is "automatically closed on fork," which is backwards — kqueue FDs are inherited across fork() and only closed on exec() when CLOEXEC is set.
  2. The claim that "eloop consumers fork and exec" does not match actual usage: the codebase uses fork() without exec() (child processes continue as daemons via setsid() and eloop_forked()).

Since child processes inherit the kqueue FD without CLOEXEC, and no exec() call closes it, the FD remains open in forked children, potentially causing resource leaks or unexpected behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/eloop.c` around lines 628 - 633, The current comment and behavior around
kqueue() are incorrect: kqueue FDs are inherited across fork() and not
automatically closed, and macOS does allow setting FD_CLOEXEC; update the code
that calls kqueue() (the fd = kqueue() site) to immediately set the
close-on-exec flag using fcntl(fd, F_SETFD, FD_CLOEXEC) and check for errors,
and replace the comment to state that we set FD_CLOEXEC to prevent the
descriptor from leaking into children (and note that fork-only children inherit
FDs unless FD_CLOEXEC is set).

#elif defined(USE_KQUEUE)
int flags;

Expand Down Expand Up @@ -871,7 +873,7 @@ eloop_waitfd(int fd)
struct pollfd pfd = { .fd = fd, .events = POLLIN };
int err;

err = ppoll(&pfd, 1, NULL, NULL);
err = poll(&pfd, 1, -1);
if (err == -1 || err == 0)
return err;

Expand Down Expand Up @@ -1092,4 +1094,4 @@ eloop_start(struct eloop *eloop)
}

return eloop->exitcode;
}
}
Loading
Loading