Skip to content

security: add peer credential validation to macOS ovpnagent - #442

Open
shaggyinsomniac wants to merge 2 commits into
OpenVPN:masterfrom
shaggyinsomniac:security/ovpnagent-peer-validation
Open

security: add peer credential validation to macOS ovpnagent#442
shaggyinsomniac wants to merge 2 commits into
OpenVPN:masterfrom
shaggyinsomniac:security/ovpnagent-peer-validation

Conversation

@shaggyinsomniac

Copy link
Copy Markdown

Summary

The macOS ovpnagent daemon runs as root and listens on a Unix domain socket
(/var/run/agent_ovpnconnect.sock) with mode 0777. The allow_client()
method returns true unconditionally, meaning any local process —
regardless of user or code signature — can connect and issue privileged
commands
.

Impact

An unprivileged local process can:

Endpoint Effect
POST /tun-setup Configure system-wide routes and DNS as root; receive the tun fd via SCM_RIGHTS (full traffic interception)
POST /add-bypass-route Modify the kernel routing table
GET /tun-destroy Tear down any active VPN tunnel

Root Cause

// Before: accepts everyone
bool allow_client(AsioPolySock::Base &sock) override
{
    return true;
}
// Socket world-writable
config->unix_mode = 0777;

Fix

  1. Peer credential check in allow_client() using the existing
    peercreds() and root_or_self_uid() helpers from
    openvpn/common/peercred.hpp — these were already available but unused.

  2. Socket mode restricted from 0777 to 0600.

Testing

Verified on macOS 27.0 (Apple Silicon):

  • Before fix: unprivileged process connects, POST /add-bypass-route returns
    200 OK, kernel routing table modified (verified via netstat -rn)
  • After fix: unprivileged connection rejected, socket only accessible to owner

Notes

This does not affect the Linux agent, which uses a different socket permission
model. The fix uses only infrastructure already present in the codebase.

…d access

The macOS ovpnagent listens on a Unix domain socket with mode 0777 and
accepts all connections without verifying the peer's identity. Any local
process can connect and issue privileged commands including:

- POST /tun-setup: configure system routing and DNS as root, receive
  the tun file descriptor via SCM_RIGHTS
- POST /add-bypass-route: modify the kernel routing table
- GET /tun-destroy: tear down active VPN tunnels

This change:
1. Validates the connecting process's UID via LOCAL_PEERCRED in
   allow_client(), rejecting connections that are not from root or
   the same user the agent runs as
2. Restricts the Unix socket mode from 0777 to 0600

The peercreds() and root_or_self_uid() infrastructure already exists
in openvpn/common/peercred.hpp but was not being used by the macOS agent.
@schwabe

schwabe commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

I agree that this is something we need to fix but I think the fix completely misunderstand the purpose of ovpnagent. This is done to allow privilege separation to allow an unprivileged user space OpenVPN process to do these commands.

Having this open for everyone is not a good idea as that allows everyone to mess with the network configuration. But this fix will restrict ovpnagent to root only which defeats the purpose as well.:

sudo ./ovpn3/core/openvpn/ovpnagent/mac/agent_macos --user arne --group arne

OpenVPN Agent (Mac) 0.1.1 [OpenSSL]
Fri Aug 28 11:52:42.942 2026 HTTP Listen: http-listen /var/run/ovpnagent.sock UnixStream 1
Fri Aug 28 11:52:42.943 2026 GID set to 'arne'
Fri Aug 28 11:52:42.943 2026 UID set to 'arne'
[11:48]arne@styx:~% ls -l  /var/run/ovpnagent.sock 
srw-------  1 root  daemon  0 28 Aug. 11:52 /var/run/ovpnagent.sock=

Restores socket mode to 0666 so unprivileged clients can connect again.
allow_client() now accepts root unconditionally, and non-root peers only
when their UID is listed in /etc/openvpn/ovpnagent.allowed_uids (numeric
UID or username, one per line). The special entry "all" restores the
legacy unrestricted behaviour for deployments that want it. Missing or
unreadable allowlist file means root-only access.
@shaggyinsomniac

Copy link
Copy Markdown
Author

Fair point, you're right — restricting the agent to root-only defeats the whole reason it exists. Sorry about that, I was too focused on closing the open access and broke the actual use case.

I've pushed a rework (ee20602) that keeps unprivileged clients working:

  • The socket mode is back to 0666, so a normal user-space client can connect like before.
  • allow_client() now checks the peer credentials from the socket and accepts:
    • root unconditionally, and
    • non-root peers whose UID is listed in /etc/openvpn/ovpnagent.allowed_uids (one numeric UID or username per line, # comments allowed).
  • If that file contains the special entry all, the UID gate is off entirely — that's the escape hatch for anyone who wants the old behaviour back.
  • If the file is missing or unreadable, the default is root-only. So the vulnerable "everyone can talk to the agent" situation is gone out of the box, and enabling access for a regular user is a one-line config instead of a code change.

One thing I'd like your input on: the default. I went with root-only until the admin opts users in via the allowlist file, since that's the safe failure mode. But I can see an argument for auto-allowing the current console user (the agent is really only ever talking to the logged-in user's client on a desktop). Happy to switch to whichever you think fits the project better — or a different config mechanism entirely if a file in /etc/openvpn isn't the right place for this.

Built and ran the unit test suite locally on this revision (CoreTests passes, agent_macos target builds clean).

@schwabe

schwabe commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Your answer reads like it was completely AI generated.

If that file contains the special entry all, the UID gate is off entirely

You are propsoing to fix a security problem and then also propose to keep the security in the program at the same time? That sounds like either the behaviour is fine and can kept as is or it is not fine because it is a security problem and needs to be fixed. This is a contradiction in my opinion.

@InputOutputZ

Copy link
Copy Markdown

A side note, I just tested on Tahoe 26.6.2, and noticed the .socket files no longer are created in /var/run, not sure if they are but hidden from all users including root while I tried to list using sudo prefix but no difference? is this intended behaviour?
/var/run/agent_ovpnconnect.sock
/var/run/ovpnagent.sock

Anyhow, the ovpnagent still works, and thanks for the PR.

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.

3 participants