diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 00531b8ac..ee2f31986 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -201,6 +201,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/derp/derphttp from tailscale.com/net/netcheck+ tailscale.com/disco from tailscale.com/derp+ tailscale.com/doctor from tailscale.com/ipn/ipnlocal + 💣 tailscale.com/doctor/permissions from tailscale.com/ipn/ipnlocal tailscale.com/doctor/routetable from tailscale.com/ipn/ipnlocal tailscale.com/envknob from tailscale.com/control/controlclient+ tailscale.com/health from tailscale.com/control/controlclient+ @@ -346,7 +347,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/crypto/poly1305 from github.com/tailscale/golang-x-crypto/ssh+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+ - golang.org/x/exp/constraints from golang.org/x/exp/slices + golang.org/x/exp/constraints from golang.org/x/exp/slices+ golang.org/x/exp/maps from tailscale.com/wgengine golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal+ golang.org/x/net/bpf from github.com/mdlayher/genetlink+ diff --git a/doctor/permissions/permissions.go b/doctor/permissions/permissions.go new file mode 100644 index 000000000..f9c0de950 --- /dev/null +++ b/doctor/permissions/permissions.go @@ -0,0 +1,56 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +// Package permissions provides a doctor.Check that prints the process +// permissions for the running process. +package permissions + +import ( + "context" + "fmt" + "os/user" + "strings" + + "golang.org/x/exp/constraints" + "tailscale.com/types/logger" +) + +// Check implements the doctor.Check interface. +type Check struct{} + +func (Check) Name() string { + return "permissions" +} + +func (Check) Run(_ context.Context, logf logger.Logf) error { + return permissionsImpl(logf) +} + +func formatUserID[T constraints.Integer](id T) string { + idStr := fmt.Sprint(id) + if uu, err := user.LookupId(idStr); err != nil { + return idStr + "()" + } else { + return fmt.Sprintf("%s(%q)", idStr, uu.Username) + } +} + +func formatGroupID[T constraints.Integer](id T) string { + idStr := fmt.Sprint(id) + if g, err := user.LookupGroupId(idStr); err != nil { + return idStr + "()" + } else { + return fmt.Sprintf("%s(%q)", idStr, g.Name) + } +} + +func formatGroups[T constraints.Integer](groups []T) string { + var buf strings.Builder + for i, group := range groups { + if i > 0 { + buf.WriteByte(',') + } + buf.WriteString(formatGroupID(group)) + } + return buf.String() +} diff --git a/doctor/permissions/permissions_bsd.go b/doctor/permissions/permissions_bsd.go new file mode 100644 index 000000000..8b034cfff --- /dev/null +++ b/doctor/permissions/permissions_bsd.go @@ -0,0 +1,23 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build darwin || freebsd || openbsd + +package permissions + +import ( + "golang.org/x/sys/unix" + "tailscale.com/types/logger" +) + +func permissionsImpl(logf logger.Logf) error { + groups, _ := unix.Getgroups() + logf("uid=%s euid=%s gid=%s egid=%s groups=%s", + formatUserID(unix.Getuid()), + formatUserID(unix.Geteuid()), + formatGroupID(unix.Getgid()), + formatGroupID(unix.Getegid()), + formatGroups(groups), + ) + return nil +} diff --git a/doctor/permissions/permissions_linux.go b/doctor/permissions/permissions_linux.go new file mode 100644 index 000000000..12bb393d5 --- /dev/null +++ b/doctor/permissions/permissions_linux.go @@ -0,0 +1,62 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux + +package permissions + +import ( + "fmt" + "strings" + "unsafe" + + "golang.org/x/sys/unix" + "tailscale.com/types/logger" +) + +func permissionsImpl(logf logger.Logf) error { + // NOTE: getresuid and getresgid never fail unless passed an + // invalid address. + var ruid, euid, suid uint64 + unix.Syscall(unix.SYS_GETRESUID, + uintptr(unsafe.Pointer(&ruid)), + uintptr(unsafe.Pointer(&euid)), + uintptr(unsafe.Pointer(&suid)), + ) + + var rgid, egid, sgid uint64 + unix.Syscall(unix.SYS_GETRESGID, + uintptr(unsafe.Pointer(&rgid)), + uintptr(unsafe.Pointer(&egid)), + uintptr(unsafe.Pointer(&sgid)), + ) + + groups, _ := unix.Getgroups() + + var buf strings.Builder + fmt.Fprintf(&buf, "ruid=%s euid=%s suid=%s rgid=%s egid=%s sgid=%s groups=%s", + formatUserID(ruid), formatUserID(euid), formatUserID(suid), + formatGroupID(rgid), formatGroupID(egid), formatGroupID(sgid), + formatGroups(groups), + ) + + // Get process capabilities + var ( + capHeader = unix.CapUserHeader{ + Version: unix.LINUX_CAPABILITY_VERSION_3, + Pid: 0, // 0 means 'ourselves' + } + capData unix.CapUserData + ) + + if err := unix.Capget(&capHeader, &capData); err != nil { + fmt.Fprintf(&buf, " caperr=%v", err) + } else { + fmt.Fprintf(&buf, " cap_effective=%08x cap_permitted=%08x cap_inheritable=%08x", + capData.Effective, capData.Permitted, capData.Inheritable, + ) + } + + logf("%s", buf.String()) + return nil +} diff --git a/doctor/permissions/permissions_other.go b/doctor/permissions/permissions_other.go new file mode 100644 index 000000000..7e6912b49 --- /dev/null +++ b/doctor/permissions/permissions_other.go @@ -0,0 +1,17 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !(linux || darwin || freebsd || openbsd) + +package permissions + +import ( + "runtime" + + "tailscale.com/types/logger" +) + +func permissionsImpl(logf logger.Logf) error { + logf("unsupported on %s/%s", runtime.GOOS, runtime.GOARCH) + return nil +} diff --git a/doctor/permissions/permissions_test.go b/doctor/permissions/permissions_test.go new file mode 100644 index 000000000..941d406ef --- /dev/null +++ b/doctor/permissions/permissions_test.go @@ -0,0 +1,12 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package permissions + +import "testing" + +func TestPermissionsImpl(t *testing.T) { + if err := permissionsImpl(t.Logf); err != nil { + t.Error(err) + } +} diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 4c5efd9b7..5be259af2 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -35,6 +35,7 @@ import ( "tailscale.com/client/tailscale/apitype" "tailscale.com/control/controlclient" "tailscale.com/doctor" + "tailscale.com/doctor/permissions" "tailscale.com/doctor/routetable" "tailscale.com/envknob" "tailscale.com/health" @@ -4698,7 +4699,10 @@ func (b *LocalBackend) Doctor(ctx context.Context, logf logger.Logf) { logf = logger.SlowLoggerWithClock(ctx, logf, 20*time.Millisecond, 60, time.Now) var checks []doctor.Check - checks = append(checks, routetable.Check{}) + checks = append(checks, + permissions.Check{}, + routetable.Check{}, + ) // Print a log message if any of the global DNS resolvers are Tailscale // IPs; this can interfere with our ability to connect to the Tailscale