Linux IPC hardening: lantern-group authorization#338
Conversation
There was a problem hiding this comment.
Pull request overview
Hardens Lantern’s Linux IPC by moving from a world-writable socket to a least-privilege model using a dedicated lantern group and per-peer authorization.
Changes:
- Adds
lanterncontrol group concept and usesroot:lanternownership with0660permissions on Linux. - Extends peer identity to include
inControlGroupand updates access checks accordingly. - Adds Linux-specific group membership detection for connecting peers.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| vpn/ipc/usr.go | Extends usr with inControlGroup for authorization decisions. |
| vpn/ipc/socket.go | Sets Linux socket ownership/permissions to root:lantern and 0660. |
| vpn/ipc/middlewares.go | Updates authorization to allow admins or members of the control group. |
| vpn/ipc/conn_nonwindows.go | Determines connecting peer’s group membership (Linux) to drive authorization. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| func peerCanAccess(peer usr) bool { | ||
| return peer.isAdmin | ||
| return peer.isAdmin || peer.inControlGroup |
There was a problem hiding this comment.
This change unintentionally blocks root from accessing the IPC API on Linux: getPeerUser() doesn’t set isAdmin on Linux, and root typically isn’t in the lantern group, so peerCanAccess() will return false even though the socket permissions allow root. Consider explicitly allowing uid 0 (e.g., peer.uid == \"0\") or setting isAdmin for uid 0 in the Linux path.
| return peer.isAdmin || peer.inControlGroup | |
| return peer.isAdmin || peer.inControlGroup || peer.uid == "0" |
|
The socket needs to be world-readable so anyone with |
|
We can support headless CLI without reverting to world-accessible sockets by keeping root:lantern 0660 and managing access via lantern group. That keeps no-sudo UX while preserving least-privilege |
|
That requires creating and managing the control group. If IPC verifies the peer has sudo permissions, I don't see why we would need to also require a control group. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func linuxUserInControlGroup(u *user.User) (bool, error) { | ||
| controlGroupGID, err := controlGroupGID() | ||
| if err != nil { | ||
| return false, err | ||
| } |
There was a problem hiding this comment.
conn_nonwindows.go is built on Android/iOS (//go:build !windows), but the newly added Linux group-check code depends on controlGroupGID() / controlGroupGIDInt() which are only defined in control_group_nonwindows.go (!android && !ios && !windows). This will cause Android/iOS builds of package ipc to fail with an undefined symbol error. Consider moving the Linux-specific helpers (linuxUserInControlGroup and the runtime.GOOS == "linux" branch) into a //go:build linux file, or widening/providing stub implementations for controlGroupGID* on mobile builds.
There was a problem hiding this comment.
The above sounds right to me!
| func controlGroupInfo() (*user.Group, error) { | ||
| group, err := user.LookupGroup(controlGroup) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("lookup %s group: %w", controlGroup, err) |
There was a problem hiding this comment.
When user.LookupGroup(controlGroup) fails (e.g., the lantern group doesn’t exist), this propagates as a hard startup failure. The error message would be more actionable if it explicitly explained how to remediate (create the group and add users, or configure a different group if supported), since this is a new deployment prerequisite.
| return nil, fmt.Errorf("lookup %s group: %w", controlGroup, err) | |
| return nil, fmt.Errorf("lookup %s group: %w. Ensure the %s group exists on this system and contains the appropriate users, or configure a different control group if supported.", controlGroup, err, controlGroup) |
|
Using a control group also means other users with |
|
Relying on canSudo() alone means exposing a root-control socket to all local users and treating middleware auth as the primary boundary, which is not least-privilege. The standard/safer model is deny-by-default at the OS layer and then authorize approved clients on top |
Normally, I would agree with you on this, but in this case it's perfectly fine because the IPC does the authentication using unix peer credentials. Other programs also do that. |
|
Peer creds are good for identity, but that still doesn’t make a world-accessible root IPC socket fine. It increases attack surface (any local user can connect/flood/probe) and removes the first security boundary socket ACLs |
Right, and then we verify that they have Even if any user can connect to it, they won't be able to do anything because the IPC will reject the request. |
|
I asked Claude to analyze this debate because I can't think for myself anymore. Here's what it had to say:
|
|
Another concern is that most distros (including Ubuntu and Debian) require you to log out and log back in for the group membership changes to take effect because they're only evaluated at login. Some distros even require a full reboot. This will lead to an increase of issue reports as there will be a decent amount of users that didn't do that. Also, a much smaller concern, it's a bit inconvenient. 😃 |
Lantern’s Linux app needs to control lanternd without requiring sudo for normal users. This PR secures that path by switching IPC to a least-privilege model (root:lantern, 0660, allow root or users in the lantern group) instead of a world-writable socket.