mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +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 @@
|
||||
//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 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 @@
|
||||
"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
|
||||
@ -40,6 +44,7 @@ func logSupportInfo(logf logger.Logf, reason LogSupportInfoReason) {
|
||||
const (
|
||||
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
|
||||
}
|
||||
|
@ -40,8 +40,12 @@ func errnoErr(e syscall.Errno) error {
|
||||
|
||||
var (
|
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||
|
||||
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) {
|
||||
@ -51,3 +55,21 @@ func regEnumValue(key registry.Key, index uint32, valueName *uint16, valueNameLe
|
||||
}
|
||||
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…
Reference in New Issue
Block a user