delve/service/rpccommon/sameuser_linux.go
Alessandro Arzilli 81a86086dd
cmd/dlv: Fix same-user check and add flag to disable it (#1839)
* service: also search IPv6 connections when checking user

When checking if the user is allowed to connect to this Delve instance
also search IPv6 connections even though the local address is IPv4.

Fixes #1835

* cmd: add flag to disable same-user check

Fixes #1835
2020-02-11 09:06:29 -08:00

116 lines
3.3 KiB
Go

//+build linux
package rpccommon
import (
"bytes"
"encoding/binary"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"strings"
)
// for testing
var (
uid = os.Getuid()
readFile = ioutil.ReadFile
)
type errConnectionNotFound struct {
filename string
}
func (e *errConnectionNotFound) Error() string {
return fmt.Sprintf("connection not found in %s", e.filename)
}
func sameUserForHexLocalAddr(filename, hexaddr string) (bool, error) {
b, err := readFile(filename)
if err != nil {
return false, err
}
for _, line := range strings.Split(strings.TrimSpace(string(b)), "\n") {
// The format contains whitespace padding (%4d, %5u), so we use
// fmt.Sscanf instead of splitting on whitespace.
var (
sl int
localAddr, remoteAddr string
state int
queue, timer string
retransmit int
remoteUID uint
)
// Note that we must use %d where the kernel format uses %5u:
// %u is not understood by the fmt package (%U is something else),
// %5d cuts off longer uids (e.g. 149098 on gLinux).
n, err := fmt.Sscanf(line, "%4d: %s %s %02X %s %s %08X %d",
&sl, &localAddr, &remoteAddr, &state, &queue, &timer, &retransmit, &remoteUID)
if n != 8 || err != nil {
continue // invalid line (e.g. header line)
}
if localAddr != hexaddr {
continue
}
return uid == int(remoteUID), nil
}
return false, &errConnectionNotFound{filename}
}
func sameUserForRemoteAddr4(remoteAddr *net.TCPAddr) (bool, error) {
// For details about the format, see the kernel side implementation:
// https://elixir.bootlin.com/linux/v5.2.2/source/net/ipv4/tcp_ipv4.c#L2375
b := remoteAddr.IP.To4()
hexaddr := fmt.Sprintf("%02X%02X%02X%02X:%04X", b[3], b[2], b[1], b[0], remoteAddr.Port)
r, err := sameUserForHexLocalAddr("/proc/net/tcp", hexaddr)
if _, isNotFound := err.(*errConnectionNotFound); isNotFound {
// See Issue #1835
r, err2 := sameUserForHexLocalAddr("/proc/net/tcp6", "0000000000000000FFFF0000"+hexaddr)
if err2 == nil {
return r, nil
}
}
return r, err
}
func sameUserForRemoteAddr6(remoteAddr *net.TCPAddr) (bool, error) {
a16 := remoteAddr.IP.To16()
// For details about the format, see the kernel side implementation:
// https://elixir.bootlin.com/linux/v5.2.2/source/net/ipv6/tcp_ipv6.c#L1792
words := make([]uint32, 4)
if err := binary.Read(bytes.NewReader(a16), binary.LittleEndian, words); err != nil {
return false, err
}
hexaddr := fmt.Sprintf("%08X%08X%08X%08X:%04X", words[0], words[1], words[2], words[3], remoteAddr.Port)
return sameUserForHexLocalAddr("/proc/net/tcp6", hexaddr)
}
func sameUserForRemoteAddr(remoteAddr *net.TCPAddr) (bool, error) {
if remoteAddr.IP.To4() == nil {
return sameUserForRemoteAddr6(remoteAddr)
}
return sameUserForRemoteAddr4(remoteAddr)
}
func canAccept(listenAddr, remoteAddr net.Addr) bool {
laddr, ok := listenAddr.(*net.TCPAddr)
if !ok || !laddr.IP.IsLoopback() {
return true
}
addr, ok := remoteAddr.(*net.TCPAddr)
if !ok {
panic(fmt.Sprintf("BUG: conn.RemoteAddr is %T, want *net.TCPAddr", remoteAddr))
}
same, err := sameUserForRemoteAddr(addr)
if err != nil {
log.Printf("cannot check remote address: %v", err)
}
if !same {
log.Printf("closing connection from different user (%v): connections to localhost are only accepted from the same UNIX user for security reasons", addr)
return false
}
return true
}