mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-23 03:17:43 +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:
@@ -22,6 +22,10 @@ import (
|
||||
"tailscale.com/util/winutil/authenticode"
|
||||
)
|
||||
|
||||
var (
|
||||
errUnexpectedResult = errors.New("API call returned an unexpected value")
|
||||
)
|
||||
|
||||
const (
|
||||
maxBinaryValueLen = 128 // we'll truncate any binary values longer than this
|
||||
maxRegValueNameLen = 16384 // maximum length supported by Windows + 1
|
||||
@@ -38,8 +42,9 @@ func logSupportInfo(logf logger.Logf, reason LogSupportInfoReason) {
|
||||
}
|
||||
|
||||
const (
|
||||
supportInfoKeyModules = "modules"
|
||||
supportInfoKeyRegistry = "registry"
|
||||
supportInfoKeyModules = "modules"
|
||||
supportInfoKeyRegistry = "registry"
|
||||
supportInfoKeyWinsockLSP = "winsockLSP"
|
||||
)
|
||||
|
||||
func getSupportInfo(w io.Writer, reason LogSupportInfoReason) error {
|
||||
@@ -59,6 +64,13 @@ func getSupportInfo(w io.Writer, reason LogSupportInfoReason) error {
|
||||
} else {
|
||||
output[supportInfoKeyModules] = err
|
||||
}
|
||||
|
||||
lspInfo, err := getWinsockLSPInfo()
|
||||
if err == nil {
|
||||
output[supportInfoKeyWinsockLSP] = lspInfo
|
||||
} else {
|
||||
output[supportInfoKeyWinsockLSP] = err
|
||||
}
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
@@ -228,8 +240,8 @@ func (mi *moduleInfo) setVersionInfo() {
|
||||
|
||||
var errAssertingType = errors.New("asserting DataDirectory type")
|
||||
|
||||
func (mi *moduleInfo) setDebugInfo(base uintptr, size uint32) {
|
||||
pem, err := pe.NewPEFromBaseAddressAndSize(base, size)
|
||||
func (mi *moduleInfo) setDebugInfo() {
|
||||
pem, err := pe.NewPEFromBaseAddressAndSize(mi.BaseAddress, mi.Size)
|
||||
if err != nil {
|
||||
mi.DebugInfoErr = err
|
||||
return
|
||||
@@ -320,7 +332,7 @@ func getModuleInfo() (map[string]moduleInfo, error) {
|
||||
}
|
||||
|
||||
entry.setVersionInfo()
|
||||
entry.setDebugInfo(base, size)
|
||||
entry.setDebugInfo()
|
||||
entry.setAuthenticodeInfo()
|
||||
|
||||
result[name] = entry
|
||||
@@ -328,3 +340,145 @@ func getModuleInfo() (map[string]moduleInfo, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
Reference in New Issue
Block a user