Skip to content

control: remove unprivileged socket#680

Open
rsmarples wants to merge 3 commits into
masterfrom
control
Open

control: remove unprivileged socket#680
rsmarples wants to merge 3 commits into
masterfrom
control

Conversation

@rsmarples

Copy link
Copy Markdown
Member

Instead workout credentials from the connected socket by getpeereid(2).
Remove the limit of only one connected control client.

This needs some testing and is only expected to compile on the BSDs right now.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31509dbe-9aa9-418c-ba95-885e267d4576

📥 Commits

Reviewing files that changed from the base of the PR and between af3750d and 8699713.

📒 Files selected for processing (11)
  • src/control.c
  • src/control.h
  • src/dhcpcd.8.in
  • src/dhcpcd.c
  • src/dhcpcd.h
  • src/privsep-control.c
  • src/privsep-control.h
  • src/privsep-root.c
  • src/privsep-root.h
  • src/privsep.c
  • src/privsep.h
✅ Files skipped from review due to trivial changes (3)
  • src/privsep-root.h
  • src/privsep-control.h
  • src/dhcpcd.8.in
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/privsep.h
  • src/privsep-root.c
  • src/dhcpcd.h
  • src/control.h
  • src/dhcpcd.c
  • src/privsep.c
  • src/privsep-control.c
  • src/control.c

Walkthrough

This PR removes the separate unprivileged control socket, switches control access checks to peer credentials, and updates the privsep control and root IPC paths to use a single control socket and FD_PRIV-based privilege classification.

Changes

Unified control socket with credential-based privilege

Layer / File(s) Summary
Control and privsep interface changes
src/control.h, src/privsep.h, src/privsep-control.h, src/privsep-root.h
FD_UNPRIV is replaced by FD_PRIV; control receive/send helpers move to msghdr-based APIs; control_open, control_find, and control_user_ispriv declarations are updated; PS_CTL_EOF is replaced by PS_CTL_PRIV, PS_USER_ISPRIV is added, and the old control-type flag macros are removed.
Control socket recvmsg and privilege check
src/control.c
The control read path now uses recvmsg and getpeereid, classifies privilege through control_user_ispriv or privsep root IPC, parses payloads with control_recvmsg, and changes hangup handling for EPERM.
Single control socket setup and consumer updates
src/control.c, src/dhcpcd.c, src/dhcpcd.h, src/dhcpcd.8.in
Unprivileged control socket state and path handling are removed, single-socket setup/open logic is simplified, dhcpcd control-socket callers use the new interface, and the manpage socket descriptions are updated.
Privsep control proxy dispatch and forwarding
src/privsep-control.c
The privsep control proxy now dispatches commands with control_recvmsg, uses FD_PRIV and FD_LISTEN, broadcasts work data to control fd entries, and sends replies with ps_ctl_sendmsg.
Root-side user privilege IPC
src/privsep-root.c
The root side adds the PS_USER_ISPRIV handler and wrapper to query privilege for a (uid, gid) pair.
Controller process lifecycle handling
src/privsep.c
Controller-related cleanup in process startup and teardown is adjusted so root resources are preserved in the controller case.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Control as src/control.c
  participant PrivsepRoot as src/privsep-root.c
  participant PrivsepCtl as src/privsep-control.c

  Client->>Control: connect and send command
  Control->>Control: recvmsg + getpeereid(uid, gid)
  alt privsep mode
    Control->>PrivsepRoot: ps_root_user_ispriv(uid, gid)
    PrivsepRoot-->>Control: privilege result
    Control->>PrivsepCtl: ps_ctl_sendmsg / ps_ctl_handleargs
  else standalone mode
    Control->>Control: control_user_ispriv(uid, gid)
  end
  Control->>Control: control_recvmsg parses payload
  Control-->>Client: dispatch command or EPERM
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: removing the unprivileged control socket.
Description check ✅ Passed The description is clearly related to the change set, covering socket credentials and the single-client restriction removal.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch control

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

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

2570-2576: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Duplicate "spawned manager process" log.

The same logdebugx(...) now appears both before the start_manager: label (line 2572) and after it (line 2576). On the daemonise fall-through path both fire, logging the manager PID twice; the goto start_manager path only hits 2576. Drop the pre-label copy so both paths log exactly once.

🩹 Remove the duplicate
 	/* We have now forked, setsid, forked once more.
 	 * From this point on, we are the controlling daemon. */
-	logdebugx("spawned manager process on PID %d", (int)getpid());
 
 start_manager:
 
 	logdebugx("spawned manager process on PID %d", (int)getpid());
🤖 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 2570 - 2576, The manager startup debug message is
duplicated around the start_manager label in dhcpcd.c, causing the daemonise
fall-through path to log the same PID twice. Remove the pre-label logdebugx call
and keep the single logdebugx("spawned manager process on PID %d", ...) after
start_manager so both the fall-through and goto start_manager paths emit the
message exactly once.
🧹 Nitpick comments (4)
src/privsep-root.c (1)

328-338: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant reassignment of p.

p is already initialized to data at Line 328, so the p = data; at Line 335 is redundant. Minor cleanup.

♻️ Cleanup
-	uint8_t *p = data;
+	uint8_t *p;
 
 	if (len != sizeof(uid) + sizeof(gid)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	p = data;
🤖 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 328 - 338, Remove the redundant reassignment
of the local pointer in the privilege-separation parsing logic: in the function
that reads `uid` and `gid` from `data`, `p` is already initialized before the
length check, so the later `p = data;` is unnecessary. Keep the existing
`memcpy` flow intact and simply delete the duplicate assignment while preserving
the `uid`/`gid` extraction behavior.
src/privsep-control.c (1)

40-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused PS_CTL_FLAGS_PRIV macro
PS_CTL_FLAGS_PRIV is never referenced; privilege is already carried by PS_CTL_PRIV/PS_CTL in ps_ctl_dispatch, so this macro can be dropped.

🤖 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-control.c` around lines 40 - 42, Remove the unused
PS_CTL_FLAGS_PRIV macro from privsep-control.c and keep the PS_CTL_FD helper
intact. Verify that ps_ctl_dispatch and the existing PS_CTL_PRIV/PS_CTL macros
still provide the privilege handling needed, and delete any related dead
definition so the header/source only contains symbols that are actually
referenced.
src/control.c (2)

453-453: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

listen() backlog uses sizeof(ctx->control_fds).

ctx->control_fds is a TAILQ head, so sizeof yields the struct size (~16), not a meaningful connection backlog. It works but is misleading; consider a named constant.

🤖 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/control.c` at line 453, The backlog passed to listen() in the control
socket setup uses sizeof(ctx->control_fds), which is just the TAILQ head size
and not a meaningful queue limit. Update the listen() call in the control
initialization path to use a clear named constant or defined backlog value
instead of sizeof(ctx->control_fds), keeping the existing fd/control_fds logic
unchanged.

434-454: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Use the SOCK_MODE macro and confirm the world-writable socket is intended.

SOCK_MODE is defined at line 434 (same value, 0666) but control_start1() chmods with the literal 0666, leaving the macro unused. Static analysis also flags the world-writable mode (CWE-732). Under the new credential model any user may connect and privilege is enforced via getpeereid/FD_PRIV, so world-writable is plausibly intentional — but then the ctx->control_group chown no longer restricts who can connect. Please confirm 0666 is desired rather than 0660 + control_group, and either way replace the literal with the macro.

♻️ Use the defined macro
 	if (bind(fd, (struct sockaddr *)&sa, len) == -1 ||
-	    chmod(sa.sun_path, 0666) == -1 ||
+	    chmod(sa.sun_path, SOCK_MODE) == -1 ||
 	    (ctx->control_group &&
 		chown(sa.sun_path, geteuid(), ctx->control_group) == -1) ||
 	    listen(fd, sizeof(ctx->control_fds)) == -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/control.c` around lines 434 - 454, In control_start1(), replace the
hardcoded chmod mode with the existing SOCK_MODE macro so the defined socket
permissions are used consistently. Also verify the intended access model for the
control socket: if world-writable access is deliberate under the new
credential/FD_PRIV checks, keep SOCK_MODE and leave a note or related
adjustment; otherwise tighten the mode to a group-only setting and ensure
ctx->control_group still meaningfully restricts access.

Source: Linters/SAST tools

🤖 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/control.c`:
- Around line 642-676: In control_user_ispriv(), treat an unknown uid from
getpwuid() as non-privileged instead of returning -1, so control_handle_data()
does not drop read-only clients whose user is missing from the passwd database.
Update the early return path in control_user_ispriv() to return 0 for this case,
and make sure any error logging in the caller does not rely on stale errno from
getpwuid() when no real error occurred.

In `@src/privsep-control.c`:
- Around line 111-123: The PS_CTL_PRIV/PS_CTL handling in privsep control is
incorrectly closing the shared proxy work fd when control_recvmsg() fails.
Update the control_new()/control_free() flow so the wrapper is dropped without
closing ctx->ps_ctl->psp_work_fd on the error path, and keep the underlying
shared fd registered for later dispatches; use the PS_CTL_PRIV/PS_CTL case block
and the control_recvmsg() cleanup branch as the places to adjust.

---

Outside diff comments:
In `@src/dhcpcd.c`:
- Around line 2570-2576: The manager startup debug message is duplicated around
the start_manager label in dhcpcd.c, causing the daemonise fall-through path to
log the same PID twice. Remove the pre-label logdebugx call and keep the single
logdebugx("spawned manager process on PID %d", ...) after start_manager so both
the fall-through and goto start_manager paths emit the message exactly once.

---

Nitpick comments:
In `@src/control.c`:
- Line 453: The backlog passed to listen() in the control socket setup uses
sizeof(ctx->control_fds), which is just the TAILQ head size and not a meaningful
queue limit. Update the listen() call in the control initialization path to use
a clear named constant or defined backlog value instead of
sizeof(ctx->control_fds), keeping the existing fd/control_fds logic unchanged.
- Around line 434-454: In control_start1(), replace the hardcoded chmod mode
with the existing SOCK_MODE macro so the defined socket permissions are used
consistently. Also verify the intended access model for the control socket: if
world-writable access is deliberate under the new credential/FD_PRIV checks,
keep SOCK_MODE and leave a note or related adjustment; otherwise tighten the
mode to a group-only setting and ensure ctx->control_group still meaningfully
restricts access.

In `@src/privsep-control.c`:
- Around line 40-42: Remove the unused PS_CTL_FLAGS_PRIV macro from
privsep-control.c and keep the PS_CTL_FD helper intact. Verify that
ps_ctl_dispatch and the existing PS_CTL_PRIV/PS_CTL macros still provide the
privilege handling needed, and delete any related dead definition so the
header/source only contains symbols that are actually referenced.

In `@src/privsep-root.c`:
- Around line 328-338: Remove the redundant reassignment of the local pointer in
the privilege-separation parsing logic: in the function that reads `uid` and
`gid` from `data`, `p` is already initialized before the length check, so the
later `p = data;` is unnecessary. Keep the existing `memcpy` flow intact and
simply delete the duplicate assignment while preserving the `uid`/`gid`
extraction behavior.
🪄 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: 0f1cec4a-8cc9-4b2c-99f5-c3d964783ef1

📥 Commits

Reviewing files that changed from the base of the PR and between 8b31291 and 03a1d7b.

📒 Files selected for processing (11)
  • src/control.c
  • src/control.h
  • src/dhcpcd.8.in
  • src/dhcpcd.c
  • src/dhcpcd.h
  • src/privsep-control.c
  • src/privsep-control.h
  • src/privsep-root.c
  • src/privsep-root.h
  • src/privsep.c
  • src/privsep.h

Comment thread src/control.c
Comment thread src/privsep-control.c
rsmarples added 3 commits July 5, 2026 16:20
Instead workout credentials from the connected socket
by getpeereid(2).
Remove the limit of only one connected control client.

This needs some testing and is only expected to compile on the BSDs
right now.
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