tailscale/safesocket/pipe_windows.go

167 lines
4.9 KiB
Go
Raw Permalink Normal View History

// Copyright (c) Tailscale Inc & AUTHORS
// 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"
"syscall"
"time"
"github.com/tailscale/go-winio"
"golang.org/x/sys/windows"
)
func connect(s *ConnectionStrategy) (net.Conn, error) {
dl := time.Now().Add(20 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), dl)
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, s.path, windows.GENERIC_READ|windows.GENERIC_WRITE, winio.PipeImpLevelIdentification)
}
func setFlags(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET,
syscall.SO_REUSEADDR, 1)
})
}
// windowsSDDL is the Security Descriptor set on the namedpipe.
// It provides read/write access to all users and the local system.
// It is a var for testing, do not change this value.
var windowsSDDL = "O:BAG:BAD:PAI(A;OICI;GWGR;;;BU)(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
token windows.Token
}
// 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
}
// Token returns the Windows access token of the client user.
func (conn *WindowsClientConn) Token() windows.Token {
return conn.token
}
func (conn *WindowsClientConn) Close() error {
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)
}
token, err := clientUserAccessToken(pipeConn)
if err != nil {
conn.Close()
return nil, err
}
return &WindowsClientConn{
winioPipeConn: pipeConn,
token: token,
}, 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