mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-18 20:51:45 +00:00
util/osdiag: add logging for winsock layered service providers to Windows bugreports
The Layered Service Provider (LSP) is a deprecated (but still supported) mechanism for inserting user-mode DLLs into a filter chain between the Winsock API surface (ie, ws2_32.dll) and the internal user-mode interface to the networking stack. While their use is becoming more rare due to the aforementioned deprecation, it is still possible for third-party software to install their DLLs into this filter chain and interfere with Winsock API calls. Knowing whether this is happening is useful for troubleshooting. Fixes https://github.com/tailscale/tailscale/issues/8142 Signed-off-by: Aaron Klotz <aaron@tailscale.com>
This commit is contained in:
parent
411e3364a9
commit
c17a817769
@ -7,3 +7,6 @@ package osdiag
|
|||||||
//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go
|
//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go
|
||||||
|
|
||||||
//sys regEnumValue(key registry.Key, index uint32, valueName *uint16, valueNameLen *uint32, reserved *uint32, valueType *uint32, pData *byte, cbData *uint32) (ret error) [failretval!=0] = advapi32.RegEnumValueW
|
//sys regEnumValue(key registry.Key, index uint32, valueName *uint16, valueNameLen *uint32, reserved *uint32, valueType *uint32, pData *byte, cbData *uint32) (ret error) [failretval!=0] = advapi32.RegEnumValueW
|
||||||
|
//sys wscEnumProtocols(iProtocols *int32, protocolBuffer *wsaProtocolInfo, bufLen *uint32, errno *int32) (ret int32) = ws2_32.WSCEnumProtocols
|
||||||
|
//sys wscGetProviderInfo(providerId *windows.GUID, infoType _WSC_PROVIDER_INFO_TYPE, info unsafe.Pointer, infoSize *uintptr, flags uint32, errno *int32) (ret int32) = ws2_32.WSCGetProviderInfo
|
||||||
|
//sys wscGetProviderPath(providerId *windows.GUID, providerDllPath *uint16, providerDllPathLen *int32, errno *int32) (ret int32) = ws2_32.WSCGetProviderPath
|
||||||
|
@ -22,6 +22,10 @@ import (
|
|||||||
"tailscale.com/util/winutil/authenticode"
|
"tailscale.com/util/winutil/authenticode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errUnexpectedResult = errors.New("API call returned an unexpected value")
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxBinaryValueLen = 128 // we'll truncate any binary values longer than this
|
maxBinaryValueLen = 128 // we'll truncate any binary values longer than this
|
||||||
maxRegValueNameLen = 16384 // maximum length supported by Windows + 1
|
maxRegValueNameLen = 16384 // maximum length supported by Windows + 1
|
||||||
@ -38,8 +42,9 @@ func logSupportInfo(logf logger.Logf, reason LogSupportInfoReason) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
supportInfoKeyModules = "modules"
|
supportInfoKeyModules = "modules"
|
||||||
supportInfoKeyRegistry = "registry"
|
supportInfoKeyRegistry = "registry"
|
||||||
|
supportInfoKeyWinsockLSP = "winsockLSP"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getSupportInfo(w io.Writer, reason LogSupportInfoReason) error {
|
func getSupportInfo(w io.Writer, reason LogSupportInfoReason) error {
|
||||||
@ -59,6 +64,13 @@ func getSupportInfo(w io.Writer, reason LogSupportInfoReason) error {
|
|||||||
} else {
|
} else {
|
||||||
output[supportInfoKeyModules] = err
|
output[supportInfoKeyModules] = err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lspInfo, err := getWinsockLSPInfo()
|
||||||
|
if err == nil {
|
||||||
|
output[supportInfoKeyWinsockLSP] = lspInfo
|
||||||
|
} else {
|
||||||
|
output[supportInfoKeyWinsockLSP] = err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
@ -228,8 +240,8 @@ func (mi *moduleInfo) setVersionInfo() {
|
|||||||
|
|
||||||
var errAssertingType = errors.New("asserting DataDirectory type")
|
var errAssertingType = errors.New("asserting DataDirectory type")
|
||||||
|
|
||||||
func (mi *moduleInfo) setDebugInfo(base uintptr, size uint32) {
|
func (mi *moduleInfo) setDebugInfo() {
|
||||||
pem, err := pe.NewPEFromBaseAddressAndSize(base, size)
|
pem, err := pe.NewPEFromBaseAddressAndSize(mi.BaseAddress, mi.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mi.DebugInfoErr = err
|
mi.DebugInfoErr = err
|
||||||
return
|
return
|
||||||
@ -320,7 +332,7 @@ func getModuleInfo() (map[string]moduleInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
entry.setVersionInfo()
|
entry.setVersionInfo()
|
||||||
entry.setDebugInfo(base, size)
|
entry.setDebugInfo()
|
||||||
entry.setAuthenticodeInfo()
|
entry.setAuthenticodeInfo()
|
||||||
|
|
||||||
result[name] = entry
|
result[name] = entry
|
||||||
@ -328,3 +340,145 @@ func getModuleInfo() (map[string]moduleInfo, error) {
|
|||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type _WSC_PROVIDER_INFO_TYPE int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
providerInfoLspCategories _WSC_PROVIDER_INFO_TYPE = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_SOCKET_ERROR = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Note that wsaProtocolInfo needs to be identical to windows.WSAProtocolInfo;
|
||||||
|
// the purpose of this type is to have the ability to use it as a reciever in
|
||||||
|
// the path and categoryFlags funcs defined below.
|
||||||
|
type wsaProtocolInfo windows.WSAProtocolInfo
|
||||||
|
|
||||||
|
func (pi *wsaProtocolInfo) path() (string, error) {
|
||||||
|
var errno int32
|
||||||
|
var buf [windows.MAX_PATH]uint16
|
||||||
|
bufCount := int32(len(buf))
|
||||||
|
ret := wscGetProviderPath(&pi.ProviderId, &buf[0], &bufCount, &errno)
|
||||||
|
if ret == _SOCKET_ERROR {
|
||||||
|
return "", windows.Errno(errno)
|
||||||
|
}
|
||||||
|
if ret != 0 {
|
||||||
|
return "", errUnexpectedResult
|
||||||
|
}
|
||||||
|
|
||||||
|
return windows.UTF16ToString(buf[:bufCount]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pi *wsaProtocolInfo) categoryFlags() (uint32, error) {
|
||||||
|
var errno int32
|
||||||
|
var result uint32
|
||||||
|
bufLen := uintptr(unsafe.Sizeof(result))
|
||||||
|
ret := wscGetProviderInfo(&pi.ProviderId, providerInfoLspCategories, unsafe.Pointer(&result), &bufLen, 0, &errno)
|
||||||
|
if ret == _SOCKET_ERROR {
|
||||||
|
return 0, windows.Errno(errno)
|
||||||
|
}
|
||||||
|
if ret != 0 {
|
||||||
|
return 0, errUnexpectedResult
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type wsaProtocolInfoOutput struct {
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
AddressFamily int32 `json:"addressFamily"`
|
||||||
|
SocketType int32 `json:"socketType"`
|
||||||
|
Protocol int32 `json:"protocol"`
|
||||||
|
ServiceFlags1 string `json:"serviceFlags1"`
|
||||||
|
ProviderFlags string `json:"providerFlags"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
PathErr error `json:"pathErr,omitempty"`
|
||||||
|
Category string `json:"category,omitempty"`
|
||||||
|
CategoryErr error `json:"categoryErr,omitempty"`
|
||||||
|
BaseProviderID string `json:"baseProviderID,omitempty"`
|
||||||
|
LayerProviderID string `json:"layerProviderID,omitempty"`
|
||||||
|
Chain []uint32 `json:"chain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWinsockLSPInfo() (map[uint32]wsaProtocolInfoOutput, error) {
|
||||||
|
protocols, err := enumWinsockProtocols()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[uint32]wsaProtocolInfoOutput, len(protocols))
|
||||||
|
for _, p := range protocols {
|
||||||
|
v := wsaProtocolInfoOutput{
|
||||||
|
Description: windows.UTF16ToString(p.ProtocolName[:]),
|
||||||
|
Version: p.Version,
|
||||||
|
AddressFamily: p.AddressFamily,
|
||||||
|
SocketType: p.SocketType,
|
||||||
|
Protocol: p.Protocol,
|
||||||
|
ServiceFlags1: fmt.Sprintf("0x%08X", p.ServiceFlags1), // Serializing as hex string to make the flags easier to decode by human inspection
|
||||||
|
ProviderFlags: fmt.Sprintf("0x%08X", p.ProviderFlags),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.ProtocolChain.ChainLen {
|
||||||
|
case windows.BASE_PROTOCOL:
|
||||||
|
v.BaseProviderID = p.ProviderId.String()
|
||||||
|
case windows.LAYERED_PROTOCOL:
|
||||||
|
v.LayerProviderID = p.ProviderId.String()
|
||||||
|
default:
|
||||||
|
v.Chain = p.ProtocolChain.ChainEntries[:p.ProtocolChain.ChainLen]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queries that are only valid for base and layered protocols (not chains)
|
||||||
|
if v.Chain == nil {
|
||||||
|
path, err := p.path()
|
||||||
|
if err == nil {
|
||||||
|
v.Path = strings.ToLower(path)
|
||||||
|
} else {
|
||||||
|
v.PathErr = err
|
||||||
|
}
|
||||||
|
|
||||||
|
category, err := p.categoryFlags()
|
||||||
|
if err == nil {
|
||||||
|
v.Category = fmt.Sprintf("0x%08X", category)
|
||||||
|
} else if !errors.Is(err, windows.WSAEINVALIDPROVIDER) {
|
||||||
|
// WSAEINVALIDPROVIDER == "no category info found", so we only log
|
||||||
|
// errors other than that one.
|
||||||
|
v.CategoryErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chains reference other providers using catalog entry IDs, so we use that
|
||||||
|
// value as the key in our map.
|
||||||
|
result[p.CatalogEntryId] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func enumWinsockProtocols() ([]wsaProtocolInfo, error) {
|
||||||
|
// Get the required size
|
||||||
|
var errno int32
|
||||||
|
var bytesReqd uint32
|
||||||
|
ret := wscEnumProtocols(nil, nil, &bytesReqd, &errno)
|
||||||
|
if ret != _SOCKET_ERROR {
|
||||||
|
return nil, errUnexpectedResult
|
||||||
|
}
|
||||||
|
if e := windows.Errno(errno); e != windows.WSAENOBUFS {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate
|
||||||
|
szEntry := uint32(unsafe.Sizeof(wsaProtocolInfo{}))
|
||||||
|
buf := make([]wsaProtocolInfo, bytesReqd/szEntry)
|
||||||
|
|
||||||
|
// Now do the query for real
|
||||||
|
bufLen := uint32(len(buf)) * szEntry
|
||||||
|
ret = wscEnumProtocols(nil, &buf[0], &bufLen, &errno)
|
||||||
|
if ret == _SOCKET_ERROR {
|
||||||
|
return nil, windows.Errno(errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
@ -40,8 +40,12 @@ func errnoErr(e syscall.Errno) error {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||||
|
|
||||||
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
|
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
|
||||||
|
procWSCEnumProtocols = modws2_32.NewProc("WSCEnumProtocols")
|
||||||
|
procWSCGetProviderInfo = modws2_32.NewProc("WSCGetProviderInfo")
|
||||||
|
procWSCGetProviderPath = modws2_32.NewProc("WSCGetProviderPath")
|
||||||
)
|
)
|
||||||
|
|
||||||
func regEnumValue(key registry.Key, index uint32, valueName *uint16, valueNameLen *uint32, reserved *uint32, valueType *uint32, pData *byte, cbData *uint32) (ret error) {
|
func regEnumValue(key registry.Key, index uint32, valueName *uint16, valueNameLen *uint32, reserved *uint32, valueType *uint32, pData *byte, cbData *uint32) (ret error) {
|
||||||
@ -51,3 +55,21 @@ func regEnumValue(key registry.Key, index uint32, valueName *uint16, valueNameLe
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wscEnumProtocols(iProtocols *int32, protocolBuffer *wsaProtocolInfo, bufLen *uint32, errno *int32) (ret int32) {
|
||||||
|
r0, _, _ := syscall.Syscall6(procWSCEnumProtocols.Addr(), 4, uintptr(unsafe.Pointer(iProtocols)), uintptr(unsafe.Pointer(protocolBuffer)), uintptr(unsafe.Pointer(bufLen)), uintptr(unsafe.Pointer(errno)), 0, 0)
|
||||||
|
ret = int32(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func wscGetProviderInfo(providerId *windows.GUID, infoType _WSC_PROVIDER_INFO_TYPE, info unsafe.Pointer, infoSize *uintptr, flags uint32, errno *int32) (ret int32) {
|
||||||
|
r0, _, _ := syscall.Syscall6(procWSCGetProviderInfo.Addr(), 6, uintptr(unsafe.Pointer(providerId)), uintptr(infoType), uintptr(info), uintptr(unsafe.Pointer(infoSize)), uintptr(flags), uintptr(unsafe.Pointer(errno)))
|
||||||
|
ret = int32(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func wscGetProviderPath(providerId *windows.GUID, providerDllPath *uint16, providerDllPathLen *int32, errno *int32) (ret int32) {
|
||||||
|
r0, _, _ := syscall.Syscall6(procWSCGetProviderPath.Addr(), 4, uintptr(unsafe.Pointer(providerId)), uintptr(unsafe.Pointer(providerDllPath)), uintptr(unsafe.Pointer(providerDllPathLen)), uintptr(unsafe.Pointer(errno)), 0, 0)
|
||||||
|
ret = int32(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user