internal/lsp/lsprpc: don't connect to sockets owned by different users

When running gopls as a forwarder it attempts to forward the LSP to a
remote daemon. On posix systems, by default this uses a unix domain
socket at a predictable filesystem location.

As an extra precaution, attempt to verify that the remote socket is in
fact owned by the current user.

Also, change the default TCP listen address used on windows to bind to
localhost.

Updates golang/go#34111

Change-Id: Ib24886d290089a773851c5439586c3ddc9eb797d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/222246
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/lsp/lsprpc/autostart_default.go b/internal/lsp/lsprpc/autostart_default.go
index a2ccaeb..a68cef5 100644
--- a/internal/lsp/lsprpc/autostart_default.go
+++ b/internal/lsp/lsprpc/autostart_default.go
@@ -10,8 +10,9 @@
 )
 
 var (
-	startRemote        = startRemoteDefault
-	autoNetworkAddress = autoNetworkAddressDefault
+	startRemote           = startRemoteDefault
+	autoNetworkAddress    = autoNetworkAddressDefault
+	verifyRemoteOwnership = verifyRemoteOwnershipDefault
 )
 
 func startRemoteDefault(goplsPath string, args ...string) error {
@@ -29,5 +30,9 @@
 	if id != "" {
 		panic("identified remotes are not supported on windows")
 	}
-	return "tcp", ":37374"
+	return "tcp", "localhost:37374"
+}
+
+func verifyRemoteOwnershipDefault(network, address string) (bool, error) {
+	return true, nil
 }
diff --git a/internal/lsp/lsprpc/autostart_posix.go b/internal/lsp/lsprpc/autostart_posix.go
index 9961f8e..5978f33 100644
--- a/internal/lsp/lsprpc/autostart_posix.go
+++ b/internal/lsp/lsprpc/autostart_posix.go
@@ -2,23 +2,27 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build darwin dragonfly freebsd linux netbsd solaris openbsd
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
 
 package lsprpc
 
 import (
 	"crypto/sha1"
+	"errors"
 	"fmt"
 	"log"
 	"os"
 	"os/exec"
+	"os/user"
 	"path/filepath"
+	"strconv"
 	"syscall"
 )
 
 func init() {
 	startRemote = startRemotePosix
 	autoNetworkAddress = autoNetworkAddressPosix
+	verifyRemoteOwnership = verifyRemoteOwnershipPosix
 }
 
 func startRemotePosix(goplsPath string, args ...string) error {
@@ -64,3 +68,29 @@
 	}
 	return "unix", filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s-daemon.%s%s", basename, shortHash, user, idComponent))
 }
+
+func verifyRemoteOwnershipPosix(network, address string) (bool, error) {
+	if network != "unix" {
+		return true, nil
+	}
+	fi, err := os.Stat(address)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return true, nil
+		}
+		return false, fmt.Errorf("checking socket owner: %v", err)
+	}
+	stat, ok := fi.Sys().(*syscall.Stat_t)
+	if !ok {
+		return false, errors.New("fi.Sys() is not a Stat_t")
+	}
+	user, err := user.Current()
+	if err != nil {
+		return false, fmt.Errorf("checking current user: %v", err)
+	}
+	uid, err := strconv.ParseUint(user.Uid, 10, 32)
+	if err != nil {
+		return false, fmt.Errorf("parsing current UID: %v", err)
+	}
+	return stat.Uid == uint32(uid), nil
+}
diff --git a/internal/lsp/lsprpc/lsprpc.go b/internal/lsp/lsprpc/lsprpc.go
index 65351cc..e26497e 100644
--- a/internal/lsp/lsprpc/lsprpc.go
+++ b/internal/lsp/lsprpc/lsprpc.go
@@ -268,6 +268,18 @@
 		// So we need to resolve a real network and address here.
 		network, address = autoNetworkAddress(f.goplsPath, f.addr)
 	}
+	// Attempt to verify that we own the remote. This is imperfect, but if we can
+	// determine that the remote is owned by a different user, we should fail.
+	ok, err := verifyRemoteOwnership(network, address)
+	if err != nil {
+		// If the ownership check itself failed, we fail open but log an error to
+		// the user.
+		log.Error(ctx, "unable to check daemon socket owner, failing open: %v", err)
+	} else if !ok {
+		// We succesfully checked that the socket is not owned by us, we fail
+		// closed.
+		return nil, fmt.Errorf("socket %q is owned by a different user", address)
+	}
 	// Try dialing our remote once, in case it is already running.
 	netConn, err = net.DialTimeout(network, address, f.dialTimeout)
 	if err == nil {