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>
2023-06-26 18:50:45 +01:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
package osdiag
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-08-17 17:40:19 +01:00
|
|
|
"maps"
|
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>
2023-06-26 18:50:45 +01:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"golang.org/x/sys/windows/registry"
|
|
|
|
)
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|