Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion vpn/ipc/conn_nonwindows.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,48 @@ func getConnPeer(conn net.Conn) (p usr, err error) {
}

uidStr := strconv.FormatUint(uint64(uid), 10)
peer, err := getPeerUser(uid, uidStr)
if err != nil {
return p, err
}
return peer, nil
}

func linuxUserInControlGroup(u *user.User) (bool, error) {
controlGroupGID, err := controlGroupGID()
if err != nil {
return false, err
}
Comment on lines +94 to +98
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The above sounds right to me!

gids, err := u.GroupIds()
if err != nil {
return false, fmt.Errorf("lookup groups for %s: %w", u.Username, err)
}
for _, gid := range gids {
if gid == controlGroupGID {
return true, nil
}
}
return false, nil
}
Comment thread
atavism marked this conversation as resolved.

func getPeerUser(uid uint32, uidStr string) (usr, error) {
u, err := user.LookupId(uidStr)
if err != nil {
return p, fmt.Errorf("lookup user id %v: %w", uid, err)
return usr{}, fmt.Errorf("lookup user id %v: %w", uid, err)
}

if runtime.GOOS == "linux" {
inControlGroup, err := linuxUserInControlGroup(u)
if err != nil {
return usr{}, err
}
return usr{
uid: uidStr,
uname: u.Username,
inControlGroup: inControlGroup,
}, nil
}

return usr{
uid: uidStr,
uname: u.Username,
Expand Down
38 changes: 38 additions & 0 deletions vpn/ipc/control_group_nonwindows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//go:build !android && !ios && !windows

package ipc

import (
"fmt"
"os/user"
"strconv"
)

func controlGroupInfo() (*user.Group, error) {
group, err := user.LookupGroup(controlGroup)
if err != nil {
return nil, fmt.Errorf("lookup %s group: %w", controlGroup, err)
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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)

Copilot uses AI. Check for mistakes.
}
return group, nil
}

func controlGroupGID() (string, error) {
group, err := controlGroupInfo()
if err != nil {
return "", err
}
return group.Gid, nil
}

func controlGroupGIDInt() (int, error) {
gid, err := controlGroupGID()
if err != nil {
return 0, err
}
parsed, err := strconv.Atoi(gid)
if err != nil {
return 0, fmt.Errorf("convert %s gid %s: %w", controlGroup, gid, err)
}
return parsed, nil
}

2 changes: 1 addition & 1 deletion vpn/ipc/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,5 @@ func authPeer(next http.Handler) http.Handler {
}

func peerCanAccess(peer usr) bool {
return peer.isAdmin
return peer.isAdmin || peer.inControlGroup
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
return peer.isAdmin || peer.inControlGroup
return peer.isAdmin || peer.inControlGroup || peer.uid == "0"

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sounds right!

}
22 changes: 20 additions & 2 deletions vpn/ipc/socket.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"strconv"
)

const controlGroup = "lantern"

// use a var so it can be overridden in tests
var _socketPath = "/var/run/lantern/lanternd.sock"

Expand All @@ -25,8 +27,24 @@ func socketPath() string {
func setPermissions() error {
path := socketPath()
if runtime.GOOS == "linux" {
// we'll check if user is sudoer to restrict access
return os.Chmod(socketPath(), 0666)
if _testing || os.Geteuid() != 0 {
if err := os.Chmod(path, 0600); err != nil {
return fmt.Errorf("chmod %s: %w", path, err)
}
return nil
}

gid, err := controlGroupGIDInt()
if err != nil {
return err
}
if err := os.Chown(path, 0, gid); err != nil {
return fmt.Errorf("chown %s: %w", path, err)
}
if err := os.Chmod(path, 0660); err != nil {
return fmt.Errorf("chmod %s: %w", path, err)
}
return nil
}

// chown admin group and let the OS restrict access
Expand Down
7 changes: 4 additions & 3 deletions vpn/ipc/usr.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (
type usrKey struct{}

type usr struct {
uid string
uname string
isAdmin bool
uid string
uname string
isAdmin bool
inControlGroup bool
}

func contextWithUsr(ctx context.Context, u usr) context.Context {
Expand Down