1//go:build linux
2
3package localadmin
4
5import (
6 "context"
7 "errors"
8 "fmt"
9 "net"
10 "os"
11 "path/filepath"
12 "time"
13
14 "golang.org/x/sys/unix"
15)
16
17// ErrPeerUIDMismatch marks a Unix socket whose listening process does not run
18// as the current effective user.
19var ErrPeerUIDMismatch = errors.New("local admin peer uid mismatch")
20
21// DialContext connects to path and authenticates the listening process before
22// returning the connection to an HTTP transport.
23func DialContext(ctx context.Context, path string) (net.Conn, error) {
24 euid := os.Geteuid()
25 if euid < 0 {
26 return nil, fmt.Errorf("authenticate local admin peer: invalid effective uid %d", euid)
27 }
28 return dialContextForUID(ctx, path, uint32(euid)) // #nosec G115 -- euid is non-negative and Linux uid_t is uint32.
29}
30
31func dialContextForUID(ctx context.Context, path string, expectedUID uint32) (net.Conn, error) {
32 if err := ValidateDir(filepath.Dir(path)); err != nil {
33 return nil, err
34 }
35 if err := ValidateSocket(path); err != nil {
36 return nil, err
37 }
38
39 dialer := &net.Dialer{Timeout: 10 * time.Second, KeepAlive: 30 * time.Second}
40 conn, err := dialer.DialContext(ctx, "unix", path)
41 if err != nil {
42 return nil, err
43 }
44 unixConn, ok := conn.(*net.UnixConn)
45 if !ok {
46 _ = conn.Close()
47 return nil, fmt.Errorf("authenticate local admin peer: unexpected connection type %T", conn)
48 }
49
50 uid, err := peerUID(unixConn)
51 if err != nil {
52 _ = conn.Close()
53 return nil, fmt.Errorf("authenticate local admin peer: %w", err)
54 }
55 if uid != expectedUID {
56 _ = conn.Close()
57 return nil, fmt.Errorf("%w: got %d, want %d", ErrPeerUIDMismatch, uid, expectedUID)
58 }
59 return conn, nil
60}
61
62func peerUID(conn *net.UnixConn) (uint32, error) {
63 raw, err := conn.SyscallConn()
64 if err != nil {
65 return 0, err
66 }
67 var (
68 cred *unix.Ucred
69 sockErr error
70 )
71 if err := raw.Control(func(fd uintptr) {
72 cred, sockErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
73 }); err != nil {
74 return 0, err
75 }
76 if sockErr != nil {
77 return 0, sockErr
78 }
79 return cred.Uid, nil
80}