Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
42 changes: 40 additions & 2 deletions vpn/ipc/conn_nonwindows.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func listen() (net.Listener, error) {
Listener: listener,
path: path,
}
// ensure listener is closed
// Close the listener when the socket wrapper is garbage-collected.
runtime.AddCleanup(socket, func(ll *net.UnixListener) {
ll.Close()
}, listener)
Expand Down 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
13 changes: 13 additions & 0 deletions vpn/ipc/control_group_mobile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build android || ios

package ipc

import "fmt"

func controlGroupGID() (string, error) {
return "", fmt.Errorf("control group lookup is unsupported on %s", controlGroup)
}

func controlGroupGIDInt() (int, error) {
return 0, fmt.Errorf("control group gid conversion is unsupported on %s", controlGroup)
}
42 changes: 42 additions & 0 deletions vpn/ipc/control_group_nonwindows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//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. Create the %s group on this system and add the users that should access IPC, or configure a different control group if supported",
controlGroup,
err,
controlGroup,
)
}
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 || peer.uid == "0"
}
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 @@ -23,9 +23,10 @@ func init() {
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
Loading