go.mod, cmd/tailscaled, ipn/localapi, util/osdiag, util/winutil, util/winutil/authenticode: add Windows module list to OS-specific logs that are written upon bugreport

* We update wingoes to pick up new version information functionality
  (See pe/version.go in the https://github.com/dblohm7/wingoes repo);
* We move the existing LogSupportInfo code (including necessary syscall
  stubs) out of util/winutil into a new package, util/osdiag, and implement
  the public LogSupportInfo function may be implemented for other platforms
  as needed;
* We add a new reason argument to LogSupportInfo and wire that into
  localapi's bugreport implementation;
* We add module information to the Windows implementation of LogSupportInfo
  when reason indicates a bugreport. We enumerate all loaded modules in our
  process, and for each one we gather debug, authenticode signature, and
  version information.

Fixes #7802

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
This commit is contained in:
Aaron Klotz
2023-06-26 11:50:45 -06:00
parent 301e59f398
commit 37925b3e7a
17 changed files with 575 additions and 305 deletions
@@ -260,6 +260,9 @@ func extractCertBlob(hfile windows.Handle) ([]byte, error) {
certsAny, err := pef.DataDirectoryEntry(pe.IMAGE_DIRECTORY_ENTRY_SECURITY)
if err != nil {
if errors.Is(err, pe.ErrNotPresent) {
err = ErrSigNotFound
}
return nil, err
}
-1
View File
@@ -7,4 +7,3 @@ package winutil
//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go
//sys queryServiceConfig2(hService windows.Handle, infoLevel uint32, buf *byte, bufLen uint32, bytesNeeded *uint32) (err error) [failretval==0] = advapi32.QueryServiceConfig2W
//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
-168
View File
@@ -4,11 +4,8 @@
package winutil
import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os/exec"
"os/user"
@@ -16,12 +13,10 @@ import (
"strings"
"syscall"
"time"
"unicode/utf16"
"unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
"tailscale.com/types/logger"
)
const (
@@ -556,166 +551,3 @@ func findHomeDirInRegistry(uid string) (dir string, err error) {
}
return dir, nil
}
const (
maxBinaryValueLen = 128 // we'll truncate any binary values longer than this
maxRegValueNameLen = 16384 // maximum length supported by Windows + 1
initialValueBufLen = 80 // large enough to contain a stringified GUID encoded as UTF-16
)
const (
supportInfoKeyRegistry = "Registry"
)
// LogSupportInfo obtains information useful for troubleshooting and support,
// and writes it to the log as a JSON-encoded object.
func LogSupportInfo(logf logger.Logf) {
var b strings.Builder
if err := getSupportInfo(&b); err != nil {
log.Printf("error encoding support info: %v", err)
return
}
logf("Support Info: %s", b.String())
}
func getSupportInfo(w io.Writer) error {
output := make(map[string]any)
regInfo, err := getRegistrySupportInfo(registry.LOCAL_MACHINE, []string{regPolicyBase, regBase})
if err == nil {
output[supportInfoKeyRegistry] = regInfo
} else {
output[supportInfoKeyRegistry] = err
}
enc := json.NewEncoder(w)
return enc.Encode(output)
}
type getRegistrySupportInfoBufs struct {
nameBuf []uint16
valueBuf []byte
}
func getRegistrySupportInfo(root registry.Key, subKeys []string) (map[string]any, error) {
bufs := getRegistrySupportInfoBufs{
nameBuf: make([]uint16, maxRegValueNameLen),
valueBuf: make([]byte, initialValueBufLen),
}
output := make(map[string]any)
for _, subKey := range subKeys {
if err := getRegSubKey(root, subKey, 5, &bufs, output); err != nil && !errors.Is(err, registry.ErrNotExist) {
return nil, fmt.Errorf("getRegistrySupportInfo: %w", err)
}
}
return output, nil
}
func keyString(key registry.Key, subKey string) string {
var keyStr string
switch key {
case registry.CLASSES_ROOT:
keyStr = `HKCR\`
case registry.CURRENT_USER:
keyStr = `HKCU\`
case registry.LOCAL_MACHINE:
keyStr = `HKLM\`
case registry.USERS:
keyStr = `HKU\`
case registry.CURRENT_CONFIG:
keyStr = `HKCC\`
case registry.PERFORMANCE_DATA:
keyStr = `HKPD\`
default:
}
return keyStr + subKey
}
func getRegSubKey(key registry.Key, subKey string, recursionLimit int, bufs *getRegistrySupportInfoBufs, output map[string]any) error {
keyStr := keyString(key, subKey)
k, err := registry.OpenKey(key, subKey, registry.READ)
if err != nil {
return fmt.Errorf("opening %q: %w", keyStr, err)
}
defer k.Close()
kv := make(map[string]any)
index := uint32(0)
loopValues:
for {
nbuf := bufs.nameBuf
nameLen := uint32(len(nbuf))
valueType := uint32(0)
vbuf := bufs.valueBuf
valueLen := uint32(len(vbuf))
err := regEnumValue(k, index, &nbuf[0], &nameLen, nil, &valueType, &vbuf[0], &valueLen)
switch err {
case windows.ERROR_NO_MORE_ITEMS:
break loopValues
case windows.ERROR_MORE_DATA:
bufs.valueBuf = make([]byte, valueLen)
continue
case nil:
default:
return fmt.Errorf("regEnumValue: %w", err)
}
var value any
switch valueType {
case registry.SZ, registry.EXPAND_SZ:
value = windows.UTF16PtrToString((*uint16)(unsafe.Pointer(&vbuf[0])))
case registry.BINARY:
if valueLen > maxBinaryValueLen {
valueLen = maxBinaryValueLen
}
value = append([]byte{}, vbuf[:valueLen]...)
case registry.DWORD:
value = binary.LittleEndian.Uint32(vbuf[:4])
case registry.MULTI_SZ:
// Adapted from x/sys/windows/registry/(Key).GetStringsValue
p := (*[1 << 29]uint16)(unsafe.Pointer(&vbuf[0]))[: valueLen/2 : valueLen/2]
var strs []string
if len(p) > 0 {
if p[len(p)-1] == 0 {
p = p[:len(p)-1]
}
strs = make([]string, 0, 5)
from := 0
for i, c := range p {
if c == 0 {
strs = append(strs, string(utf16.Decode(p[from:i])))
from = i + 1
}
}
}
value = strs
case registry.QWORD:
value = binary.LittleEndian.Uint64(vbuf[:8])
default:
value = fmt.Sprintf("<unsupported value type %d>", valueType)
}
kv[windows.UTF16PtrToString(&nbuf[0])] = value
index++
}
if recursionLimit > 0 {
if sks, err := k.ReadSubKeyNames(0); err == nil {
for _, sk := range sks {
if err := getRegSubKey(k, sk, recursionLimit-1, bufs, kv); err != nil {
return err
}
}
}
}
output[keyStr] = kv
return nil
}
-120
View File
@@ -4,13 +4,7 @@
package winutil
import (
"errors"
"fmt"
"strings"
"testing"
"golang.org/x/exp/maps"
"golang.org/x/sys/windows/registry"
)
const (
@@ -34,117 +28,3 @@ func TestLookupPseudoUser(t *testing.T) {
t.Errorf("LookupPseudoUser(%q) unexpectedly succeeded", networkSID)
}
}
func makeLongBinaryValue() []byte {
buf := make([]byte, maxBinaryValueLen*2)
for i, _ := range buf {
buf[i] = byte(i % 0xFF)
}
return buf
}
var testData = map[string]any{
"": "I am the default",
"StringEmpty": "",
"StringShort": "Hello",
"StringLong": strings.Repeat("7", initialValueBufLen+1),
"MultiStringEmpty": []string{},
"MultiStringSingle": []string{"Foo"},
"MultiStringSingleEmpty": []string{""},
"MultiString": []string{"Foo", "Bar", "Baz"},
"MultiStringWithEmptyBeginning": []string{"", "Foo", "Bar"},
"MultiStringWithEmptyMiddle": []string{"Foo", "", "Bar"},
"MultiStringWithEmptyEnd": []string{"Foo", "Bar", ""},
"DWord": uint32(0x12345678),
"QWord": uint64(0x123456789abcdef0),
"BinaryEmpty": []byte{},
"BinaryShort": []byte{0x01, 0x02, 0x03, 0x04},
"BinaryLong": makeLongBinaryValue(),
}
const (
keyNameTest = `SOFTWARE\Tailscale Test`
subKeyNameTest = "SubKey"
)
func setValues(t *testing.T, k registry.Key) {
for vk, v := range testData {
var err error
switch tv := v.(type) {
case string:
err = k.SetStringValue(vk, tv)
case []string:
err = k.SetStringsValue(vk, tv)
case uint32:
err = k.SetDWordValue(vk, tv)
case uint64:
err = k.SetQWordValue(vk, tv)
case []byte:
err = k.SetBinaryValue(vk, tv)
default:
t.Fatalf("Unknown type")
}
if err != nil {
t.Fatalf("Error setting %q: %v", vk, err)
}
}
}
func TestRegistrySupportInfo(t *testing.T) {
// Make sure the key doesn't exist yet
k, err := registry.OpenKey(registry.CURRENT_USER, keyNameTest, registry.READ)
switch {
case err == nil:
k.Close()
t.Fatalf("Test key already exists")
case !errors.Is(err, registry.ErrNotExist):
t.Fatal(err)
}
func() {
k, _, err := registry.CreateKey(registry.CURRENT_USER, keyNameTest, registry.WRITE)
if err != nil {
t.Fatalf("Error creating test key: %v", err)
}
defer k.Close()
setValues(t, k)
sk, _, err := registry.CreateKey(k, subKeyNameTest, registry.WRITE)
if err != nil {
t.Fatalf("Error creating test subkey: %v", err)
}
defer sk.Close()
setValues(t, sk)
}()
t.Cleanup(func() {
registry.DeleteKey(registry.CURRENT_USER, keyNameTest+"\\"+subKeyNameTest)
registry.DeleteKey(registry.CURRENT_USER, keyNameTest)
})
wantValuesData := maps.Clone(testData)
wantValuesData["BinaryLong"] = (wantValuesData["BinaryLong"].([]byte))[:maxBinaryValueLen]
wantKeyData := make(map[string]any)
maps.Copy(wantKeyData, wantValuesData)
wantSubKeyData := make(map[string]any)
maps.Copy(wantSubKeyData, wantValuesData)
wantKeyData[subKeyNameTest] = wantSubKeyData
wantData := map[string]any{
"HKCU\\" + keyNameTest: wantKeyData,
}
gotData, err := getRegistrySupportInfo(registry.CURRENT_USER, []string{keyNameTest})
if err != nil {
t.Errorf("getRegistrySupportInfo error: %v", err)
}
want, got := fmt.Sprintf("%#v", wantData), fmt.Sprintf("%#v", gotData)
if want != got {
t.Errorf("Compare error: want\n%s,\ngot %s", want, got)
}
}
-10
View File
@@ -7,7 +7,6 @@ import (
"unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
var _ unsafe.Pointer
@@ -42,7 +41,6 @@ var (
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procQueryServiceConfig2W = modadvapi32.NewProc("QueryServiceConfig2W")
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
)
func queryServiceConfig2(hService windows.Handle, infoLevel uint32, buf *byte, bufLen uint32, bytesNeeded *uint32) (err error) {
@@ -52,11 +50,3 @@ func queryServiceConfig2(hService windows.Handle, infoLevel uint32, buf *byte, b
}
return
}
func regEnumValue(key registry.Key, index uint32, valueName *uint16, valueNameLen *uint32, reserved *uint32, valueType *uint32, pData *byte, cbData *uint32) (ret error) {
r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(valueName)), uintptr(unsafe.Pointer(valueNameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valueType)), uintptr(unsafe.Pointer(pData)), uintptr(unsafe.Pointer(cbData)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}