0990478d9c
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / fuzz (push) Has been cancelled
tailscale.com/cmd/vet / vet (push) Has been cancelled
update-flakehub / flakehub-publish (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (benchmarks) (push) Has been cancelled
CI / Windows (1/2) (push) Has been cancelled
CI / Windows (2/2) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (macOS) (push) Has been cancelled
CI / staticcheck (Linux) (push) Has been cancelled
CI / staticcheck (Windows) (push) Has been cancelled
CI / staticcheck (Portable (1/4)) (push) Has been cancelled
CI / staticcheck (Portable (2/4)) (push) Has been cancelled
CI / staticcheck (Portable (3/4)) (push) Has been cancelled
CI / staticcheck (Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
205 lines
6.5 KiB
Go
205 lines
6.5 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package safesocket
|
|
|
|
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go pipe_windows.go
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/tailscale/go-winio"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
func connect(ctx context.Context, path string) (net.Conn, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
|
defer cancel()
|
|
// We use the identification impersonation level so that tailscaled may
|
|
// obtain information about our token for access control purposes.
|
|
return winio.DialPipeAccessImpLevel(ctx, path, windows.GENERIC_READ|windows.GENERIC_WRITE, winio.PipeImpLevelIdentification)
|
|
}
|
|
|
|
// windowsSDDL is the Security Descriptor set on the namedpipe.
|
|
// It provides read/write access to interactive users (IU) and the local
|
|
// system (SY). Using IU instead of BU (Built-in Users) restricts pipe
|
|
// access to interactively logged-in sessions only, preventing service
|
|
// accounts and network/batch logons from reaching the daemon IPC.
|
|
// It is a var for testing, do not change this value.
|
|
var windowsSDDL = "O:BAG:BAD:PAI(A;OICI;GWGR;;;IU)(A;OICI;GWGR;;;SY)"
|
|
|
|
func listen(path string) (net.Listener, error) {
|
|
lc, err := winio.ListenPipe(
|
|
path,
|
|
&winio.PipeConfig{
|
|
SecurityDescriptor: windowsSDDL,
|
|
InputBufferSize: 256 * 1024,
|
|
OutputBufferSize: 256 * 1024,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("namedpipe.Listen: %w", err)
|
|
}
|
|
return &winIOPipeListener{Listener: lc}, nil
|
|
}
|
|
|
|
// WindowsClientConn is an implementation of net.Conn that permits retrieval of
|
|
// the Windows access token associated with the connection's client. The
|
|
// embedded net.Conn must be a go-winio PipeConn.
|
|
type WindowsClientConn struct {
|
|
winioPipeConn
|
|
tokenOnce sync.Once
|
|
token windows.Token // or zero, if we couldn't obtain the client's token
|
|
tokenErr error
|
|
}
|
|
|
|
// winioPipeConn is a subset of the interface implemented by the go-winio's
|
|
// unexported *win32pipe type, as returned by go-winio's ListenPipe
|
|
// net.Listener's Accept method. This type is used in places where we really are
|
|
// assuming that specific unexported type and its Fd method.
|
|
type winioPipeConn interface {
|
|
net.Conn
|
|
// Fd returns the Windows handle associated with the connection.
|
|
Fd() uintptr
|
|
}
|
|
|
|
func resolvePipeHandle(pc winioPipeConn) windows.Handle {
|
|
return windows.Handle(pc.Fd())
|
|
}
|
|
|
|
func (conn *WindowsClientConn) handle() windows.Handle {
|
|
return resolvePipeHandle(conn.winioPipeConn)
|
|
}
|
|
|
|
// ClientPID returns the pid of conn's client, or else an error.
|
|
func (conn *WindowsClientConn) ClientPID() (int, error) {
|
|
var pid uint32
|
|
if err := getNamedPipeClientProcessId(conn.handle(), &pid); err != nil {
|
|
return -1, fmt.Errorf("GetNamedPipeClientProcessId: %w", err)
|
|
}
|
|
return int(pid), nil
|
|
}
|
|
|
|
// CheckToken returns an error if the client user's access token could not be retrieved,
|
|
// for example when the client opens the pipe with an anonymous impersonation level.
|
|
//
|
|
// Deprecated: use [WindowsClientConn.Token] instead.
|
|
func (conn *WindowsClientConn) CheckToken() error {
|
|
_, err := conn.getToken()
|
|
return err
|
|
}
|
|
|
|
// getToken returns the Windows access token of the client user,
|
|
// or an error if the token could not be retrieved, for example
|
|
// when the client opens the pipe with an anonymous impersonation level.
|
|
//
|
|
// The connection retains ownership of the returned token handle;
|
|
// the caller must not close it.
|
|
//
|
|
// TODO(nickkhyl): Remove this, along with [WindowsClientConn.CheckToken],
|
|
// once [ipnauth.ConnIdentity] is removed in favor of [ipnauth.Actor].
|
|
func (conn *WindowsClientConn) getToken() (windows.Token, error) {
|
|
conn.tokenOnce.Do(func() {
|
|
conn.token, conn.tokenErr = clientUserAccessToken(conn.winioPipeConn)
|
|
})
|
|
return conn.token, conn.tokenErr
|
|
}
|
|
|
|
// Token returns the Windows access token of the client user,
|
|
// or an error if the token could not be retrieved, for example
|
|
// when the client opens the pipe with an anonymous impersonation level.
|
|
//
|
|
// The caller is responsible for closing the returned token handle.
|
|
func (conn *WindowsClientConn) Token() (windows.Token, error) {
|
|
token, err := conn.getToken()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var dupToken windows.Handle
|
|
if err := windows.DuplicateHandle(
|
|
windows.CurrentProcess(),
|
|
windows.Handle(token),
|
|
windows.CurrentProcess(),
|
|
&dupToken,
|
|
0,
|
|
false,
|
|
windows.DUPLICATE_SAME_ACCESS,
|
|
); err != nil {
|
|
return 0, err
|
|
}
|
|
return windows.Token(dupToken), nil
|
|
}
|
|
|
|
func (conn *WindowsClientConn) Close() error {
|
|
// Either wait for any pending [WindowsClientConn.Token] calls to complete,
|
|
// or ensure that the token will never be opened.
|
|
conn.tokenOnce.Do(func() {
|
|
conn.tokenErr = net.ErrClosed
|
|
})
|
|
if conn.token != 0 {
|
|
conn.token.Close()
|
|
conn.token = 0
|
|
}
|
|
return conn.winioPipeConn.Close()
|
|
}
|
|
|
|
// winIOPipeListener is a net.Listener that wraps a go-winio PipeListener and
|
|
// returns net.Conn values of type *WindowsClientConn with the associated
|
|
// windows.Token.
|
|
type winIOPipeListener struct {
|
|
net.Listener // must be from winio.ListenPipe
|
|
}
|
|
|
|
func (lw *winIOPipeListener) Accept() (net.Conn, error) {
|
|
conn, err := lw.Listener.Accept()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pipeConn, ok := conn.(winioPipeConn)
|
|
if !ok {
|
|
conn.Close()
|
|
return nil, fmt.Errorf("unexpected type %T from winio.ListenPipe listener (itself a %T)", conn, lw.Listener)
|
|
}
|
|
return &WindowsClientConn{winioPipeConn: pipeConn}, nil
|
|
}
|
|
|
|
func clientUserAccessToken(pc winioPipeConn) (windows.Token, error) {
|
|
h := resolvePipeHandle(pc)
|
|
if h == 0 {
|
|
return 0, fmt.Errorf("clientUserAccessToken failed to get handle from pipeConn %T", pc)
|
|
}
|
|
|
|
// Impersonation touches thread-local state, so we need to lock until the
|
|
// client access token has been extracted.
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
|
|
if err := impersonateNamedPipeClient(h); err != nil {
|
|
return 0, err
|
|
}
|
|
defer func() {
|
|
// Revert the current thread's impersonation.
|
|
if err := windows.RevertToSelf(); err != nil {
|
|
panic(fmt.Errorf("could not revert impersonation: %w", err))
|
|
}
|
|
}()
|
|
|
|
// Extract the client's access token from the thread-local state.
|
|
var token windows.Token
|
|
if err := windows.OpenThreadToken(windows.CurrentThread(), windows.TOKEN_DUPLICATE|windows.TOKEN_QUERY, true, &token); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
//sys getNamedPipeClientProcessId(h windows.Handle, clientPid *uint32) (err error) [int32(failretval)==0] = kernel32.GetNamedPipeClientProcessId
|
|
//sys impersonateNamedPipeClient(h windows.Handle) (err error) [int32(failretval)==0] = advapi32.ImpersonateNamedPipeClient
|