Skip to content

privsep: Change IPC to use SOCK_STREAM#604

Open
rsmarples wants to merge 3 commits intomasterfrom
sock_stream
Open

privsep: Change IPC to use SOCK_STREAM#604
rsmarples wants to merge 3 commits intomasterfrom
sock_stream

Conversation

@rsmarples
Copy link
Copy Markdown
Member

macOS does not support SOCK_SEQPACKET.

All our messages use a fixed header which includes the lengths of all parts sent.
We can use these limits with MSG_WAITALL on blocking sockets in place of MSG_EOR to get the same effect.

Start of the work for #524.

rsmarples added 3 commits May 5, 2026 21:02
macOS does not support SOCK_SEQPACKET.

All our messages use a fixed header which includes the
lengths of all parts sent.
We can use these limits with MSG_WAITALL on blocking sockets
in place of MSG_EOR to get the same effect.

Start of the work for #524.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

Warning

Rate limit exceeded

@rsmarples has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 30 minutes and 8 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8bd3b7fa-030b-4be1-b051-7f08c85fba18

📥 Commits

Reviewing files that changed from the base of the PR and between 1a95498 and 691ee9e.

📒 Files selected for processing (2)
  • src/logerr.c
  • src/privsep.c

Walkthrough

IPC between dhcpcd components was migrated from packet-oriented SOCK_SEQPACKET with MSG_EOR boundaries to stream-oriented SOCK_STREAM with explicit length-prefixed framing; send/receive code, log framing, and socketpair creation were updated accordingly across privsep, root, dhcpcd, and logging code paths.

Changes

IPC Stream Migration

Layer / File(s) Summary
Message Format (logging)
src/logerr.c, src/logerr.h
Log messages reworked from (pri, pid, payload) to (pri, pid, mlen, payload); vlogmessage prefixes payload length and logreadfd reads mlen then mlen bytes and NUL-terminates.
Socket Type Transition
src/dhcpcd.c, src/privsep.c, src/privsep-root.c
Socketpairs created using `SOCK_STREAM
Send Path Framing
src/privsep.c, src/dhcpcd.c, src/privsep-root.c
ps_sendpsmmsg and exit/signal send paths extended to build iovec with header/name/control/payload segments, accumulate ps_datalen, and call sendmsg with flags=0 (removing MSG_EOR usage).
Receive Path Framing
src/privsep.c, src/privsep-root.c
ps_recvpsmsg and privsep-root reading replaced with fixed header + MSG_WAITALL reads into a ps_msghdr/payload buffer, computing total data length, validating sizes, and reconstituting messages for dispatch.
Shutdown / Log Drain
src/privsep-root.c
Added poll-based log draining during privileged root shutdown and adjusted error/send fallback paths to use flags=0.
Comments / Documentation
src/logerr.h, src/privsep-bpf.c, src/privsep-control.c, src/privsep-inet.c
Comments updated to reflect expected SOCK_STREAM descriptors instead of SEQPACKET.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Launcher
    participant Manager
    participant PrivsepRoot
    participant LogReader
    Launcher->>Manager: open SOCK_STREAM socketpair (CLOEXEC)
    Manager->>PrivsepRoot: send framed header (ps_msghdr) + payload (sendmsg flags=0)
    PrivsepRoot->>PrivsepRoot: recv header with MSG_WAITALL, read payload, validate
    PrivsepRoot->>LogReader: send log (pri,pid,mlen,payload) over stream
    LogReader->>PrivsepRoot: ACK / drain on shutdown (poll/read until EOF)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: converting IPC to use SOCK_STREAM instead of SOCK_SEQPACKET, which is the central objective across all modified files.
Description check ✅ Passed The description directly explains the motivation (macOS incompatibility with SOCK_SEQPACKET) and the solution approach (using SOCK_STREAM with MSG_WAITALL), which aligns with the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sock_stream

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/privsep.c (1)

1069-1131: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Unbounded payload read into a fixed stack buffer.

ps_data is uint8_t ps_data[PS_BUFLEN], but dlen (Line 1121) is computed from peer-supplied fields (ps_namelen + ps_controllen + cmsg_padlen + ps_datalen) and then passed straight to recv(fd, ps_data, dlen, MSG_WAITALL) without checking dlen <= sizeof(ps_data). Under the previous SOCK_SEQPACKET, recvmsg would have surfaced oversized messages via MSG_TRUNC; with the new SOCK_STREAM + MSG_WAITALL framing, any peer (or buggy local sender) that reports a dlen larger than PS_BUFLEN overruns the stack buffer.

Two smaller issues in the same block:

  • When recv returns -1, the subsequent errno = EINVAL overwrites the real error cause from the kernel.
  • The arithmetic forming dlen is socklen_t + socklen_t + socklen_t + size_t; on platforms where the sum can overflow size_t an attacker-trusted-but-buggy peer could pass the bounds check trivially.
🛡️ Suggested guards
-	cmsg_padlen = CALC_CMSG_PADLEN(psm.ps_controllen,
-	    psm.ps_namelen);
-	dlen = psm.ps_namelen + psm.ps_controllen + cmsg_padlen + psm.ps_datalen;
-	if (dlen != 0) {
-		len = recv(fd, ps_data, dlen, MSG_WAITALL);
-		if ((size_t)len != dlen) {
-			errno = EINVAL;
-			return -1;
-		}
-	}
+	cmsg_padlen = CALC_CMSG_PADLEN(psm.ps_controllen,
+	    psm.ps_namelen);
+	dlen = (size_t)psm.ps_namelen + (size_t)psm.ps_controllen +
+	    (size_t)cmsg_padlen + psm.ps_datalen;
+	if (dlen > sizeof(ps_data)) {
+		errno = EMSGSIZE;
+		return -1;
+	}
+	if (dlen != 0) {
+		len = recv(fd, ps_data, dlen, MSG_WAITALL);
+		if (len == -1)
+			return -1;
+		if ((size_t)len != dlen) {
+			errno = EINVAL;
+			return -1;
+		}
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/privsep.c` around lines 1069 - 1131, The code reads a peer-controlled
dlen into a fixed stack buffer ps_data[PS_BUFLEN] without bounds or overflow
checks and clobbers real errno on recv failure; fix by validating and
capping/calculating dlen safely before any recv: compute each component
(ps_namelen, ps_controllen, ps_datalen, cmsg_padlen) as size_t and check each
addition for overflow (e.g., ensure running_sum + next <= PS_BUFLEN and detect
overflow by comparing running_sum + next >= running_sum), then ensure final dlen
<= PS_BUFLEN before calling recv into ps_data; when recv returns -1, do not
overwrite errno (propagate/return -1 immediately), and only set errno = EINVAL
when recv succeeds but returns an unexpected short read; adjust checks around
the recv(fd, ps_data, dlen, MSG_WAITALL) and references to
CALC_CMSG_PADLEN/ps_unrollmsg/ps_data/PS_BUFLEN accordingly.
src/privsep-root.c (1)

94-117: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Two regressions in the new MSG_WAITALL read path.

Now that the channel is SOCK_STREAM, the code reads exactly psr_error->psr_datalen bytes regardless of the caller's buffer — the previous SOCK_SEQPACKET framing implicitly truncated oversized payloads. There are two issues:

  1. Line 105: missing NULL check after malloc. If malloc fails, pc->psr_data is NULL and the subsequent recv(fd, NULL, ...) is undefined behaviour. The error path at error: also dereferences pc->psr_data only for free, so the failure is silently masked.
  2. Line 107: no bound check against the caller-provided buffer in the non-malloc path. When pc->psr_mallocdata == false, pc->psr_datalen is the caller's capacity (e.g. ps_root_readerror(ctx, NULL, 0) or ps_root_filemtime's sizeof(time_t)). A peer that reports a larger psr_datalen will overrun the caller's buffer (or be written through NULL).
🛡️ Suggested guard
 	if (psr_error->psr_datalen == 0)
 		return len;

-	if (pc->psr_mallocdata)
+	if (pc->psr_mallocdata) {
 		pc->psr_data = malloc(psr_error->psr_datalen);
+		if (pc->psr_data == NULL)
+			PSR_ERROR(errno);
+	} else if (psr_error->psr_datalen > pc->psr_datalen ||
+	    pc->psr_data == NULL) {
+		PSR_ERROR(EMSGSIZE);
+	}

 	len = recv(fd, pc->psr_data, psr_error->psr_datalen, MSG_WAITALL);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/privsep-root.c` around lines 94 - 117, The recv path must validate
allocation and avoid overrunning caller buffers: after seeing
psr_error->psr_datalen > 0, if pc->psr_mallocdata is true call malloc and
immediately check the return and handle OOM (log/return error) before calling
recv; only free pc->psr_data on the error path if malloc actually succeeded. If
pc->psr_mallocdata is false, do not blindly recv psr_error->psr_datalen into
pc->psr_data — instead compare the incoming psr_error->psr_datalen to the
caller-provided capacity stored on pc (the field used when not mallocing) and
only read up to that capacity; if the peer claims more bytes than the caller can
hold, either return an error (EBADMSG) and consume/discard the excess bytes from
the socket to keep the stream in sync, or read the excess into a temporary
discard buffer before returning an error. Update the code paths around recv,
pc->psr_data, psr_error->psr_datalen and pc->psr_mallocdata accordingly.
src/dhcpcd.c (1)

2787-2790: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Drop MSG_EOR flag from stream socket send — inconsistent with migration.

ctx.fork_fd is SOCK_STREAM (line 2472), and the two other send calls on it (lines 387, 1501) use flags 0. The recent migration to SOCK_STREAM replaced MSG_EOR (appropriate for SOCK_SEQPACKET only) with MSG_WAITALL or flags 0. This call at line 2788 was missed during the migration and will spuriously log send errors on platforms that reject MSG_EOR for stream sockets per POSIX semantics.

Proposed fix
 `#ifdef` USE_SIGNALS
 	/* If still attached, detach from the launcher */
 	if (ctx.options & DHCPCD_STARTED && ctx.fork_fd != -1) {
-		if (send(ctx.fork_fd, &i, sizeof(i), MSG_EOR) == -1)
+		if (send(ctx.fork_fd, &i, sizeof(i), 0) == -1)
 			logerr("%s: send", __func__);
 	}
 `#endif`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dhcpcd.c` around lines 2787 - 2790, The send call that writes to
ctx.fork_fd uses MSG_EOR which is only valid for datagram/seqpacket sockets;
since ctx.fork_fd is a SOCK_STREAM and other sends to it (in functions/locations
using ctx.fork_fd) pass flags 0, change this send(...) invocation to use flags 0
(i.e., remove MSG_EOR) so it matches the other stream-socket sends and avoids
spurious send errors; update the send invocation around the code using
ctx.fork_fd and the surrounding error handling (logerr("%s: send", __func__))
remains the same.
src/logerr.c (1)

218-242: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

MSG_EOR is incompatible with SOCK_STREAM and there is a critical stack buffer overflow in the receive path.

src/logerr.h line 79 describes this channel as SOCK_STREAM, created in src/privsep-root.c line 834 with SOCK_STREAM | SOCK_CLOEXEC. Per POSIX, MSG_EOR is defined for SOCK_SEQPACKET; Linux will reject it with EOPNOTSUPP on SOCK_STREAM sockets. Since the receive path already uses explicit mlen framing, remove MSG_EOR.

More critically, there is a stack buffer overflow at src/logerr.c line 436. The sender clamps mlen to sizeof(buf) when mlen > sizeof(buf), but the receiver only rejects when mlen > sizeof(buf) (line 422), allowing mlen == sizeof(buf) to pass through. When mlen == LOGERR_SYSLOGBUF (1036), the line buf[mlen] = '\0' writes one past the end of the 1036-byte buffer, corrupting the stack.

Fix line 422 to reject mlen >= sizeof(buf), or alternatively adjust the sender to use >= in the clamp check so mlen stays strictly less than buffer size.

Proposed fixes

Option A (receiver validation):

-	if (mlen > sizeof(buf)) {
+	if (mlen >= sizeof(buf)) {

Option B (sender clamping, also fixes MSG_EOR):

-		if (mlen > sizeof(buf))
+		if (mlen >= sizeof(buf))
 			mlen = sizeof(buf);
-		len = (int)sendmsg(ctx->log_fd, &msg, MSG_EOR);
+		len = (int)sendmsg(ctx->log_fd, &msg, 0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/logerr.c` around lines 218 - 242, In the send path (the block using
ctx->log_fd, buf, mlen and calling sendmsg) remove the MSG_EOR flag passed to
sendmsg (MSG_EOR is invalid for SOCK_STREAM) and ensure the sender still clamps
mlen correctly; and in the receive path fix the off-by-one validation by
changing the mlen check from "mlen > sizeof(buf)" to "mlen >= sizeof(buf)" (so
mlen equal to LOGERR_SYSLOGBUF is rejected), preventing the buf[mlen] = '\0'
overflow; reference sendmsg/MSG_EOR, LOGERR_SYSLOGBUF, the local buf and mlen
variables to locate the changes.
🧹 Nitpick comments (1)
src/privsep-root.c (1)

925-949: 💤 Low value

Log-drain on shutdown looks reasonable; minor note.

Using poll() with a 1 ms timeout works around the blocking socket without adding non-blocking state. Two small things to consider: a) poll() may return -1 with EINTR and would silently terminate the drain — acceptable on a shutdown path but worth a comment; b) the loop has no upper bound on iterations, so a misbehaving peer that keeps writing log records could delay shutdown. Given this only runs in the privileged-root shutdown path with a trusted peer, both are minor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/privsep-root.c` around lines 925 - 949, The poll loop draining
ctx->ps_log_root_fd can prematurely exit on poll() returning -1 due to EINTR and
currently has no upper bound on iterations; update the loop around
poll(&pfd,1,1) in the shutdown path that calls logreadfd so that if poll()
returns -1 and errno == EINTR it continues (rather than breaking), and add an
iteration cap or overall timeout (e.g., max_loops counter or deadline checked
with clock_gettime) to ensure the loop cannot spin indefinitely if the peer
keeps writing; after the cap/timeout, fall back to closing ctx->ps_log_root_fd
as the current cleanup does.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/logerr.c`:
- Around line 422-436: The code allows mlen == sizeof(buf) leading to buf[mlen]
= '\0' writing one byte past the stack buffer (buf is char
buf[LOGERR_SYSLOGBUF]) after recv; fix by either rejecting that case (change the
earlier check from mlen > sizeof(buf) to mlen >= sizeof(buf)) so recv never
returns a full-size length, or (if you want to accept full-size payloads) change
the defensive NUL write to set buf[mlen-1] = '\0' but only after verifying mlen
> 0; update the logic around recv, mlen and the NUL-termination to avoid
out-of-bounds writes (references: buf, LOGERR_SYSLOGBUF, mlen, recv).

---

Outside diff comments:
In `@src/dhcpcd.c`:
- Around line 2787-2790: The send call that writes to ctx.fork_fd uses MSG_EOR
which is only valid for datagram/seqpacket sockets; since ctx.fork_fd is a
SOCK_STREAM and other sends to it (in functions/locations using ctx.fork_fd)
pass flags 0, change this send(...) invocation to use flags 0 (i.e., remove
MSG_EOR) so it matches the other stream-socket sends and avoids spurious send
errors; update the send invocation around the code using ctx.fork_fd and the
surrounding error handling (logerr("%s: send", __func__)) remains the same.

In `@src/logerr.c`:
- Around line 218-242: In the send path (the block using ctx->log_fd, buf, mlen
and calling sendmsg) remove the MSG_EOR flag passed to sendmsg (MSG_EOR is
invalid for SOCK_STREAM) and ensure the sender still clamps mlen correctly; and
in the receive path fix the off-by-one validation by changing the mlen check
from "mlen > sizeof(buf)" to "mlen >= sizeof(buf)" (so mlen equal to
LOGERR_SYSLOGBUF is rejected), preventing the buf[mlen] = '\0' overflow;
reference sendmsg/MSG_EOR, LOGERR_SYSLOGBUF, the local buf and mlen variables to
locate the changes.

In `@src/privsep-root.c`:
- Around line 94-117: The recv path must validate allocation and avoid
overrunning caller buffers: after seeing psr_error->psr_datalen > 0, if
pc->psr_mallocdata is true call malloc and immediately check the return and
handle OOM (log/return error) before calling recv; only free pc->psr_data on the
error path if malloc actually succeeded. If pc->psr_mallocdata is false, do not
blindly recv psr_error->psr_datalen into pc->psr_data — instead compare the
incoming psr_error->psr_datalen to the caller-provided capacity stored on pc
(the field used when not mallocing) and only read up to that capacity; if the
peer claims more bytes than the caller can hold, either return an error
(EBADMSG) and consume/discard the excess bytes from the socket to keep the
stream in sync, or read the excess into a temporary discard buffer before
returning an error. Update the code paths around recv, pc->psr_data,
psr_error->psr_datalen and pc->psr_mallocdata accordingly.

In `@src/privsep.c`:
- Around line 1069-1131: The code reads a peer-controlled dlen into a fixed
stack buffer ps_data[PS_BUFLEN] without bounds or overflow checks and clobbers
real errno on recv failure; fix by validating and capping/calculating dlen
safely before any recv: compute each component (ps_namelen, ps_controllen,
ps_datalen, cmsg_padlen) as size_t and check each addition for overflow (e.g.,
ensure running_sum + next <= PS_BUFLEN and detect overflow by comparing
running_sum + next >= running_sum), then ensure final dlen <= PS_BUFLEN before
calling recv into ps_data; when recv returns -1, do not overwrite errno
(propagate/return -1 immediately), and only set errno = EINVAL when recv
succeeds but returns an unexpected short read; adjust checks around the recv(fd,
ps_data, dlen, MSG_WAITALL) and references to
CALC_CMSG_PADLEN/ps_unrollmsg/ps_data/PS_BUFLEN accordingly.

---

Nitpick comments:
In `@src/privsep-root.c`:
- Around line 925-949: The poll loop draining ctx->ps_log_root_fd can
prematurely exit on poll() returning -1 due to EINTR and currently has no upper
bound on iterations; update the loop around poll(&pfd,1,1) in the shutdown path
that calls logreadfd so that if poll() returns -1 and errno == EINTR it
continues (rather than breaking), and add an iteration cap or overall timeout
(e.g., max_loops counter or deadline checked with clock_gettime) to ensure the
loop cannot spin indefinitely if the peer keeps writing; after the cap/timeout,
fall back to closing ctx->ps_log_root_fd as the current cleanup does.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e13c450-2f8b-4b87-97a6-bfa472c135f8

📥 Commits

Reviewing files that changed from the base of the PR and between a4b78bf and 6ea17d7.

📒 Files selected for processing (8)
  • src/dhcpcd.c
  • src/logerr.c
  • src/logerr.h
  • src/privsep-bpf.c
  • src/privsep-control.c
  • src/privsep-inet.c
  • src/privsep-root.c
  • src/privsep.c

Comment thread src/logerr.c Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/privsep.c (1)

904-913: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix the iovec bounds check before it writes past iov[].

The guard is off by one. When m.msg_iovlen reaches __arraycount(iov), the > check still passes and iovp->iov_base writes to iov[7], which is out of bounds.

💡 Suggested fix
 		for (i = 0; i < (int)msg->msg_iovlen; i++) {
-			if ((size_t)(m.msg_iovlen++) > __arraycount(iov)) {
+			if ((size_t)m.msg_iovlen >= __arraycount(iov)) {
 				errno = ENOBUFS;
 				return -1;
 			}
+			m.msg_iovlen++;
 			iovp->iov_base = msg->msg_iov[i].iov_base;
 			iovp->iov_len = msg->msg_iov[i].iov_len;
 			iovp++;
 			psm->ps_datalen += msg->msg_iov[i].iov_len;
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/privsep.c` around lines 904 - 913, The iovec bounds check is off-by-one
and lets writes past the iov[] buffer; in the loop that copies msg->msg_iov into
the local iov (using m.msg_iovlen, iovp and ps_datalen) replace the current
check that uses post-increment (m.msg_iovlen++) and ">" with a pre-check like
"if ((size_t)m.msg_iovlen >= __arraycount(iov)) { errno = ENOBUFS; return -1; }"
and then increment m.msg_iovlen only after the check (or change the comparison
to ">=" without post-increment) so you never write to iov out of bounds before
returning.
src/privsep-root.c (1)

183-195: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Send the full root reply frame, not just the first fragment.

Both sendmsg calls here assume a single write completes the whole header+payload reply. On SOCK_STREAM, a short write is legal; if it happens, the caller side in ps_root_readerrorcb() can block indefinitely or lose framing because it reads the header and payload with MSG_WAITALL. Please wrap this in a send-all helper that advances the iovecs until every byte is written.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/privsep-root.c` around lines 183 - 195, The current sendmsg usage in
functions around sendmsg(fd, &msg, 0) (including the error-reply path that sets
psr.psr_result/psr_errno and calls sendmsg again) assumes a single write will
send the entire iovec; on SOCK_STREAM a short write can occur and breaks framing
in ps_root_readerrorcb(). Fix by creating and calling a send_all helper (e.g.
sendmsg_all) that loops until all bytes in msg.msg_iov/msg.msg_iovlen are
consumed: call sendmsg repeatedly, advance the iovecs (adjust iov_base and
iov_len or skip finished iov entries and decrement msg.msg_iovlen), handle
EINTR/EAGAIN by retrying, propagate EPIPE/other errors back, and replace both
direct sendmsg(fd,&msg,0) calls with this helper so the full root reply frame
(header+payload) is always transmitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/dhcpcd.c`:
- Around line 387-388: The three uses of send that write an int (e.g.,
send(ctx->fork_fd, &exit_code, sizeof(exit_code), 0)) can perform short writes
and must be replaced with a helper that loops until all bytes are sent; add a
send_int_full(int fd, int value) helper that writes sizeof(int) bytes (handling
EINTR and partial writes, returning error on failure) and use it wherever the
code currently calls send for an int (the spots writing exit_code via
ctx->fork_fd and the other two send sites referenced in the review). Ensure
callers check the helper's return and call logerr(__func__) on failure as
before.

In `@src/logerr.c`:
- Around line 235-240: The sendmsg call can short-write on SOCK_STREAM and must
be wrapped in a write-all loop: after building struct iovec iov[] and struct
msghdr msg (as shown), repeatedly call sendmsg(ctx->log_fd, &msg, 0) until the
total bytes sent equals the full framed record length; on each partial write
advance the iovecs (adjust iov[i].iov_base and iov[i].iov_len or reduce
msg.msg_iovlen) to reflect remaining data, retry on EINTR, handle EAGAIN
appropriately, and only return once the entire record is written so logreadfd
(which uses MSG_WAITALL) stays in sync.
- Around line 231-234: The sender sets mlen = (size_t)len + 1 after vsnprintf,
but when vsnprintf returns sizeof(buf)-1 this yields mlen == sizeof(buf) which
the receiver rejects; fix by capping mlen to sizeof(buf)-1 instead of
sizeof(buf). Update the logic around vsnprintf/len/mlen (the code that assigns
mlen and iov[3].iov_len) so that after computing mlen = (size_t)len + 1 you do
if (mlen > sizeof(buf) - 1) mlen = sizeof(buf) - 1; then assign iov[3].iov_len =
mlen to keep the max log-frame length consistent with the receiver.

In `@src/privsep.c`:
- Line 916: The single sendmsg(fd, &m, 0) can result in a short write on the
SOCK_STREAM and desynchronize ps_recvpsmsg(); replace it with a full-write loop
that retries until the entire composed frame (the sum of bytes in m.msg_iov /
m.msg_iovlen) is written. On each iteration call sendmsg(fd, &m, 0), treat -1
with EINTR as a retry, treat other -1 as an error, and if sendmsg returns a
positive count less than the total advance the msghdr's iov pointer/lengths (or
rebuild m.msg_iov to reflect the consumed bytes) and subtract the written bytes,
then loop until all bytes are sent; keep using the same msghdr structure and fd
used at the current sendmsg call so ps_recvpsmsg() stays synchronized.

---

Outside diff comments:
In `@src/privsep-root.c`:
- Around line 183-195: The current sendmsg usage in functions around sendmsg(fd,
&msg, 0) (including the error-reply path that sets psr.psr_result/psr_errno and
calls sendmsg again) assumes a single write will send the entire iovec; on
SOCK_STREAM a short write can occur and breaks framing in ps_root_readerrorcb().
Fix by creating and calling a send_all helper (e.g. sendmsg_all) that loops
until all bytes in msg.msg_iov/msg.msg_iovlen are consumed: call sendmsg
repeatedly, advance the iovecs (adjust iov_base and iov_len or skip finished iov
entries and decrement msg.msg_iovlen), handle EINTR/EAGAIN by retrying,
propagate EPIPE/other errors back, and replace both direct sendmsg(fd,&msg,0)
calls with this helper so the full root reply frame (header+payload) is always
transmitted.

In `@src/privsep.c`:
- Around line 904-913: The iovec bounds check is off-by-one and lets writes past
the iov[] buffer; in the loop that copies msg->msg_iov into the local iov (using
m.msg_iovlen, iovp and ps_datalen) replace the current check that uses
post-increment (m.msg_iovlen++) and ">" with a pre-check like "if
((size_t)m.msg_iovlen >= __arraycount(iov)) { errno = ENOBUFS; return -1; }" and
then increment m.msg_iovlen only after the check (or change the comparison to
">=" without post-increment) so you never write to iov out of bounds before
returning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54385ecb-4854-47b0-89e6-583eddb0cc74

📥 Commits

Reviewing files that changed from the base of the PR and between 6ea17d7 and 1a95498.

📒 Files selected for processing (4)
  • src/dhcpcd.c
  • src/logerr.c
  • src/privsep-root.c
  • src/privsep.c

Comment thread src/dhcpcd.c
Comment on lines +387 to 388
if (send(ctx->fork_fd, &exit_code, sizeof(exit_code), 0) == -1)
logerr(__func__);
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 | ⚡ Quick win

Make the fork-control writes send the whole int.

Line 1908 now waits for exactly sizeof(int) bytes on a SOCK_STREAM socket. These three send sites still assume that one call writes the full value, but a short write is legal and would leave the launcher/manager channel out of sync during daemonise, signal forwarding, or shutdown. A tiny send_int_full() helper would make this robust.

Also applies to: 1501-1502, 2788-2789

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dhcpcd.c` around lines 387 - 388, The three uses of send that write an
int (e.g., send(ctx->fork_fd, &exit_code, sizeof(exit_code), 0)) can perform
short writes and must be replaced with a helper that loops until all bytes are
sent; add a send_int_full(int fd, int value) helper that writes sizeof(int)
bytes (handling EINTR and partial writes, returning error on failure) and use it
wherever the code currently calls send for an int (the spots writing exit_code
via ctx->fork_fd and the other two send sites referenced in the review). Ensure
callers check the helper's return and call logerr(__func__) on failure as
before.

Comment thread src/logerr.c
Comment thread src/logerr.c
Comment on lines 235 to 240
struct msghdr msg = {
.msg_iov = iov,
.msg_iovlen = sizeof(iov) / sizeof(iov[0]),
};
len = (int)sendmsg(ctx->log_fd, &msg, MSG_EOR);
len = (int)sendmsg(ctx->log_fd, &msg, 0);
}
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 | 🏗️ Heavy lift

Write the entire framed log record before returning.

sendmsg on SOCK_STREAM can complete with a short write. If that happens here, logreadfd() will block or desynchronize because it now relies on exact framing with MSG_WAITALL. This path needs a write-all loop that advances the iovecs until the full record is sent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/logerr.c` around lines 235 - 240, The sendmsg call can short-write on
SOCK_STREAM and must be wrapped in a write-all loop: after building struct iovec
iov[] and struct msghdr msg (as shown), repeatedly call sendmsg(ctx->log_fd,
&msg, 0) until the total bytes sent equals the full framed record length; on
each partial write advance the iovecs (adjust iov[i].iov_base and iov[i].iov_len
or reduce msg.msg_iovlen) to reflect remaining data, retry on EINTR, handle
EAGAIN appropriately, and only return once the entire record is written so
logreadfd (which uses MSG_WAITALL) stays in sync.

Comment thread src/privsep.c
}

len = sendmsg(fd, &m, MSG_EOR);
len = sendmsg(fd, &m, 0);
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 | 🔴 Critical | 🏗️ Heavy lift

The new stream transport needs a full-write loop here.

ps_recvpsmsg() now reconstructs frames by reading an exact header and then an exact payload. A short sendmsg on this SOCK_STREAM fd will silently truncate a message and permanently desynchronize the IPC channel. This helper needs to keep sending until the entire composed frame has been written.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/privsep.c` at line 916, The single sendmsg(fd, &m, 0) can result in a
short write on the SOCK_STREAM and desynchronize ps_recvpsmsg(); replace it with
a full-write loop that retries until the entire composed frame (the sum of bytes
in m.msg_iov / m.msg_iovlen) is written. On each iteration call sendmsg(fd, &m,
0), treat -1 with EINTR as a retry, treat other -1 as an error, and if sendmsg
returns a positive count less than the total advance the msghdr's iov
pointer/lengths (or rebuild m.msg_iov to reflect the consumed bytes) and
subtract the written bytes, then loop until all bytes are sent; keep using the
same msghdr structure and fd used at the current sendmsg call so ps_recvpsmsg()
stays synchronized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant