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.
| 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. |
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.