mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-16 18:08:40 +00:00
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:
parent
301e59f398
commit
37925b3e7a
@ -77,9 +77,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm
|
L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
||||||
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
|
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
|
||||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com
|
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com+
|
||||||
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled
|
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled
|
||||||
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
|
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
|
||||||
|
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/osdiag+
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
|
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
|
||||||
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
|
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
|
||||||
@ -332,6 +333,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/util/mak from tailscale.com/control/controlclient+
|
tailscale.com/util/mak from tailscale.com/control/controlclient+
|
||||||
tailscale.com/util/multierr from tailscale.com/control/controlclient+
|
tailscale.com/util/multierr from tailscale.com/control/controlclient+
|
||||||
tailscale.com/util/must from tailscale.com/logpolicy
|
tailscale.com/util/must from tailscale.com/logpolicy
|
||||||
|
💣 tailscale.com/util/osdiag from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/util/osshare from tailscale.com/ipn/ipnlocal+
|
tailscale.com/util/osshare from tailscale.com/ipn/ipnlocal+
|
||||||
W tailscale.com/util/pidowner from tailscale.com/ipn/ipnauth
|
W tailscale.com/util/pidowner from tailscale.com/ipn/ipnauth
|
||||||
tailscale.com/util/racebuild from tailscale.com/logpolicy
|
tailscale.com/util/racebuild from tailscale.com/logpolicy
|
||||||
@ -343,6 +345,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/util/systemd from tailscale.com/control/controlclient+
|
tailscale.com/util/systemd from tailscale.com/control/controlclient+
|
||||||
tailscale.com/util/uniq from tailscale.com/wgengine/magicsock+
|
tailscale.com/util/uniq from tailscale.com/wgengine/magicsock+
|
||||||
💣 tailscale.com/util/winutil from tailscale.com/control/controlclient+
|
💣 tailscale.com/util/winutil from tailscale.com/control/controlclient+
|
||||||
|
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/util/osdiag
|
||||||
W tailscale.com/util/winutil/policy from tailscale.com/ipn/ipnlocal
|
W tailscale.com/util/winutil/policy from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/version from tailscale.com/derp+
|
tailscale.com/version from tailscale.com/derp+
|
||||||
tailscale.com/version/distro from tailscale.com/hostinfo+
|
tailscale.com/version/distro from tailscale.com/hostinfo+
|
||||||
@ -409,6 +412,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
bytes from bufio+
|
bytes from bufio+
|
||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from golang.org/x/net/http2+
|
compress/gzip from golang.org/x/net/http2+
|
||||||
|
W compress/zlib from debug/pe
|
||||||
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
context from crypto/tls+
|
context from crypto/tls+
|
||||||
@ -433,6 +437,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
crypto/tls from github.com/tcnksm/go-httpstat+
|
crypto/tls from github.com/tcnksm/go-httpstat+
|
||||||
crypto/x509 from crypto/tls+
|
crypto/x509 from crypto/tls+
|
||||||
crypto/x509/pkix from crypto/x509+
|
crypto/x509/pkix from crypto/x509+
|
||||||
|
W debug/dwarf from debug/pe
|
||||||
|
W debug/pe from github.com/dblohm7/wingoes/pe
|
||||||
embed from tailscale.com+
|
embed from tailscale.com+
|
||||||
encoding from encoding/json+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
@ -448,7 +454,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
flag from net/http/httptest+
|
flag from net/http/httptest+
|
||||||
fmt from compress/flate+
|
fmt from compress/flate+
|
||||||
hash from crypto+
|
hash from crypto+
|
||||||
hash/adler32 from tailscale.com/ipn/ipnlocal
|
hash/adler32 from tailscale.com/ipn/ipnlocal+
|
||||||
hash/crc32 from compress/gzip+
|
hash/crc32 from compress/gzip+
|
||||||
hash/fnv from tailscale.com/wgengine/magicsock+
|
hash/fnv from tailscale.com/wgengine/magicsock+
|
||||||
hash/maphash from go4.org/mem
|
hash/maphash from go4.org/mem
|
||||||
|
@ -50,6 +50,7 @@ import (
|
|||||||
"tailscale.com/tsd"
|
"tailscale.com/tsd"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/types/logid"
|
"tailscale.com/types/logid"
|
||||||
|
"tailscale.com/util/osdiag"
|
||||||
"tailscale.com/util/winutil"
|
"tailscale.com/util/winutil"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
"tailscale.com/wf"
|
"tailscale.com/wf"
|
||||||
@ -127,7 +128,7 @@ var syslogf logger.Logf = logger.Discard
|
|||||||
// Windows started.
|
// Windows started.
|
||||||
func runWindowsService(pol *logpolicy.Policy) error {
|
func runWindowsService(pol *logpolicy.Policy) error {
|
||||||
go func() {
|
go func() {
|
||||||
winutil.LogSupportInfo(log.Printf)
|
osdiag.LogSupportInfo(logger.WithPrefix(log.Printf, "Support Info: "), osdiag.LogSupportInfoReasonStartup)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if winutil.GetPolicyInteger("LogSCMInteractions", 0) != 0 {
|
if winutil.GetPolicyInteger("LogSCMInteractions", 0) != 0 {
|
||||||
|
2
go.mod
2
go.mod
@ -18,7 +18,7 @@ require (
|
|||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/dave/jennifer v1.6.1
|
github.com/dave/jennifer v1.6.1
|
||||||
github.com/dblohm7/wingoes v0.0.0-20230801195049-ed8077baf0cd
|
github.com/dblohm7/wingoes v0.0.0-20230803162905-5c6286bb8c6e
|
||||||
github.com/dsnet/try v0.0.3
|
github.com/dsnet/try v0.0.3
|
||||||
github.com/evanw/esbuild v0.14.53
|
github.com/evanw/esbuild v0.14.53
|
||||||
github.com/frankban/quicktest v1.14.5
|
github.com/frankban/quicktest v1.14.5
|
||||||
|
4
go.sum
4
go.sum
@ -240,8 +240,8 @@ github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dblohm7/wingoes v0.0.0-20230801195049-ed8077baf0cd h1:zYVpYS5d3Uf04vVCJuzqpOCwQQIzJibtOx8ivt7zt2Q=
|
github.com/dblohm7/wingoes v0.0.0-20230803162905-5c6286bb8c6e h1:tTRuQNnXKO6Ffu62nk9bnnPx/m+IyNMdFFfzsETyRO8=
|
||||||
github.com/dblohm7/wingoes v0.0.0-20230801195049-ed8077baf0cd/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs=
|
github.com/dblohm7/wingoes v0.0.0-20230803162905-5c6286bb8c6e/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs=
|
||||||
github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU=
|
github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU=
|
||||||
github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c=
|
github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c=
|
||||||
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
|
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
|
||||||
|
@ -48,6 +48,7 @@ import (
|
|||||||
"tailscale.com/util/clientmetric"
|
"tailscale.com/util/clientmetric"
|
||||||
"tailscale.com/util/httpm"
|
"tailscale.com/util/httpm"
|
||||||
"tailscale.com/util/mak"
|
"tailscale.com/util/mak"
|
||||||
|
"tailscale.com/util/osdiag"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -350,6 +351,9 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
|||||||
// logs for them.
|
// logs for them.
|
||||||
envknob.LogCurrent(logger.WithPrefix(h.logf, "user bugreport: "))
|
envknob.LogCurrent(logger.WithPrefix(h.logf, "user bugreport: "))
|
||||||
|
|
||||||
|
// OS-specific details
|
||||||
|
osdiag.LogSupportInfo(logger.WithPrefix(h.logf, "user bugreport OS: "), osdiag.LogSupportInfoReasonBugReport)
|
||||||
|
|
||||||
if defBool(r.URL.Query().Get("diagnose"), false) {
|
if defBool(r.URL.Query().Get("diagnose"), false) {
|
||||||
h.b.Doctor(r.Context(), logger.WithPrefix(h.logf, "diag: "))
|
h.b.Doctor(r.Context(), logger.WithPrefix(h.logf, "diag: "))
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ import (
|
|||||||
_ "tailscale.com/types/logid"
|
_ "tailscale.com/types/logid"
|
||||||
_ "tailscale.com/util/clientmetric"
|
_ "tailscale.com/util/clientmetric"
|
||||||
_ "tailscale.com/util/multierr"
|
_ "tailscale.com/util/multierr"
|
||||||
|
_ "tailscale.com/util/osdiag"
|
||||||
_ "tailscale.com/util/osshare"
|
_ "tailscale.com/util/osshare"
|
||||||
_ "tailscale.com/util/winutil"
|
_ "tailscale.com/util/winutil"
|
||||||
_ "tailscale.com/version"
|
_ "tailscale.com/version"
|
||||||
|
9
util/osdiag/mksyscall.go
Normal file
9
util/osdiag/mksyscall.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package osdiag
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.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
|
23
util/osdiag/osdiag.go
Normal file
23
util/osdiag/osdiag.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package osdiag provides loggers for OS-specific diagnostic information.
|
||||||
|
package osdiag
|
||||||
|
|
||||||
|
import "tailscale.com/types/logger"
|
||||||
|
|
||||||
|
// LogSupportInfoReason is an enumeration indicating the reason for logging
|
||||||
|
// support info.
|
||||||
|
type LogSupportInfoReason int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogSupportInfoReasonStartup LogSupportInfoReason = iota + 1 // tailscaled is starting up.
|
||||||
|
LogSupportInfoReasonBugReport // a bugreport is in the process of being gathered.
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogSupportInfo obtains OS-specific diagnostic information useful for
|
||||||
|
// troubleshooting and support, and writes it to logf. The reason argument is
|
||||||
|
// useful for governing the verbosity of this function's output.
|
||||||
|
func LogSupportInfo(logf logger.Logf, reason LogSupportInfoReason) {
|
||||||
|
logSupportInfo(logf, reason)
|
||||||
|
}
|
11
util/osdiag/osdiag_notwindows.go
Normal file
11
util/osdiag/osdiag_notwindows.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package osdiag
|
||||||
|
|
||||||
|
import "tailscale.com/types/logger"
|
||||||
|
|
||||||
|
func logSupportInfo(logger.Logf, LogSupportInfoReason) {
|
||||||
|
}
|
330
util/osdiag/osdiag_windows.go
Normal file
330
util/osdiag/osdiag_windows.go
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package osdiag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/dblohm7/wingoes/pe"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
"tailscale.com/types/logger"
|
||||||
|
"tailscale.com/util/winutil"
|
||||||
|
"tailscale.com/util/winutil/authenticode"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
func logSupportInfo(logf logger.Logf, reason LogSupportInfoReason) {
|
||||||
|
var b strings.Builder
|
||||||
|
if err := getSupportInfo(&b, reason); err != nil {
|
||||||
|
logf("error encoding support info: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logf("%s", b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
supportInfoKeyModules = "modules"
|
||||||
|
supportInfoKeyRegistry = "registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getSupportInfo(w io.Writer, reason LogSupportInfoReason) error {
|
||||||
|
output := make(map[string]any)
|
||||||
|
|
||||||
|
regInfo, err := getRegistrySupportInfo(registry.LOCAL_MACHINE, []string{`SOFTWARE\Policies\Tailscale`, winutil.RegBase})
|
||||||
|
if err == nil {
|
||||||
|
output[supportInfoKeyRegistry] = regInfo
|
||||||
|
} else {
|
||||||
|
output[supportInfoKeyRegistry] = err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reason == LogSupportInfoReasonBugReport {
|
||||||
|
modInfo, err := getModuleInfo()
|
||||||
|
if err == nil {
|
||||||
|
output[supportInfoKeyModules] = modInfo
|
||||||
|
} else {
|
||||||
|
output[supportInfoKeyModules] = 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
|
||||||
|
}
|
||||||
|
|
||||||
|
type moduleInfo struct {
|
||||||
|
path string `json:"-"` // internal use only
|
||||||
|
BaseAddress uintptr `json:"baseAddress"`
|
||||||
|
Size uint32 `json:"size"`
|
||||||
|
DebugInfo map[string]string `json:"debugInfo,omitempty"` // map for JSON marshaling purposes
|
||||||
|
DebugInfoErr error `json:"debugInfoErr,omitempty"`
|
||||||
|
Signature map[string]string `json:"signature,omitempty"` // map for JSON marshaling purposes
|
||||||
|
SignatureErr error `json:"signatureErr,omitempty"`
|
||||||
|
VersionInfo map[string]string `json:"versionInfo,omitempty"` // map for JSON marshaling purposes
|
||||||
|
VersionErr error `json:"versionErr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi *moduleInfo) setVersionInfo() {
|
||||||
|
vi, err := pe.NewVersionInfo(mi.path)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, pe.ErrNotPresent) {
|
||||||
|
mi.VersionErr = err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info := map[string]string{
|
||||||
|
"": vi.VersionNumber().String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
ci, err := vi.Field("CompanyName")
|
||||||
|
if err == nil {
|
||||||
|
info["companyName"] = ci
|
||||||
|
}
|
||||||
|
|
||||||
|
mi.VersionInfo = info
|
||||||
|
}
|
||||||
|
|
||||||
|
var errAssertingType = errors.New("asserting DataDirectory type")
|
||||||
|
|
||||||
|
func (mi *moduleInfo) setDebugInfo(base uintptr, size uint32) {
|
||||||
|
pem, err := pe.NewPEFromBaseAddressAndSize(base, size)
|
||||||
|
if err != nil {
|
||||||
|
mi.DebugInfoErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer pem.Close()
|
||||||
|
|
||||||
|
debugDirAny, err := pem.DataDirectoryEntry(pe.IMAGE_DIRECTORY_ENTRY_DEBUG)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, pe.ErrNotPresent) {
|
||||||
|
mi.DebugInfoErr = err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
debugDir, ok := debugDirAny.([]pe.IMAGE_DEBUG_DIRECTORY)
|
||||||
|
if !ok {
|
||||||
|
mi.DebugInfoErr = errAssertingType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dde := range debugDir {
|
||||||
|
if dde.Type != pe.IMAGE_DEBUG_TYPE_CODEVIEW {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cv, err := pem.ExtractCodeViewInfo(dde)
|
||||||
|
if err == nil {
|
||||||
|
mi.DebugInfo = map[string]string{
|
||||||
|
"id": cv.String(),
|
||||||
|
"pdb": strings.ToLower(filepath.Base(cv.PDBPath)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mi.DebugInfoErr = err
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi *moduleInfo) setAuthenticodeInfo() {
|
||||||
|
certSubject, provenance, err := authenticode.QueryCertSubject(mi.path)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, authenticode.ErrSigNotFound) {
|
||||||
|
mi.SignatureErr = err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sigInfo := map[string]string{
|
||||||
|
"subject": certSubject,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch provenance {
|
||||||
|
case authenticode.SigProvEmbedded:
|
||||||
|
sigInfo["provenance"] = "embedded"
|
||||||
|
case authenticode.SigProvCatalog:
|
||||||
|
sigInfo["provenance"] = "catalog"
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
mi.Signature = sigInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModuleInfo() (map[string]moduleInfo, error) {
|
||||||
|
// Take a snapshot of all modules currently loaded into the current process
|
||||||
|
snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer windows.CloseHandle(snap)
|
||||||
|
|
||||||
|
result := make(map[string]moduleInfo)
|
||||||
|
me := windows.ModuleEntry32{
|
||||||
|
Size: uint32(unsafe.Sizeof(windows.ModuleEntry32{})),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now walk the list
|
||||||
|
for merr := windows.Module32First(snap, &me); merr == nil; merr = windows.Module32Next(snap, &me) {
|
||||||
|
name := strings.ToLower(windows.UTF16ToString(me.Module[:]))
|
||||||
|
path := windows.UTF16ToString(me.ExePath[:])
|
||||||
|
base := me.ModBaseAddr
|
||||||
|
size := me.ModBaseSize
|
||||||
|
|
||||||
|
entry := moduleInfo{
|
||||||
|
path: path,
|
||||||
|
BaseAddress: base,
|
||||||
|
Size: size,
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.setVersionInfo()
|
||||||
|
entry.setDebugInfo(base, size)
|
||||||
|
entry.setAuthenticodeInfo()
|
||||||
|
|
||||||
|
result[name] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
128
util/osdiag/osdiag_windows_test.go
Normal file
128
util/osdiag/osdiag_windows_test.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package osdiag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
}
|
53
util/osdiag/zsyscall_windows.go
Normal file
53
util/osdiag/zsyscall_windows.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Code generated by 'go generate'; DO NOT EDIT.
|
||||||
|
|
||||||
|
package osdiag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
// TODO: add more here, after collecting data on the common
|
||||||
|
// error values see on Windows. (perhaps when running
|
||||||
|
// all.bat?)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
|
||||||
|
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -260,6 +260,9 @@ func extractCertBlob(hfile windows.Handle) ([]byte, error) {
|
|||||||
|
|
||||||
certsAny, err := pef.DataDirectoryEntry(pe.IMAGE_DIRECTORY_ENTRY_SECURITY)
|
certsAny, err := pef.DataDirectoryEntry(pe.IMAGE_DIRECTORY_ENTRY_SECURITY)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, pe.ErrNotPresent) {
|
||||||
|
err = ErrSigNotFound
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,4 +7,3 @@ package winutil
|
|||||||
//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 queryServiceConfig2(hService windows.Handle, infoLevel uint32, buf *byte, bufLen uint32, bytesNeeded *uint32) (err error) [failretval==0] = advapi32.QueryServiceConfig2W
|
//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
|
|
||||||
|
@ -4,11 +4,8 @@
|
|||||||
package winutil
|
package winutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -16,12 +13,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf16"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
"golang.org/x/sys/windows/registry"
|
"golang.org/x/sys/windows/registry"
|
||||||
"tailscale.com/types/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -556,166 +551,3 @@ func findHomeDirInRegistry(uid string) (dir string, err error) {
|
|||||||
}
|
}
|
||||||
return dir, nil
|
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
|
|
||||||
}
|
|
||||||
|
@ -4,13 +4,7 @@
|
|||||||
package winutil
|
package winutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
"golang.org/x/sys/windows/registry"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -34,117 +28,3 @@ func TestLookupPseudoUser(t *testing.T) {
|
|||||||
t.Errorf("LookupPseudoUser(%q) unexpectedly succeeded", networkSID)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
"golang.org/x/sys/windows/registry"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
var _ unsafe.Pointer
|
||||||
@ -42,7 +41,6 @@ var (
|
|||||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
|
||||||
procQueryServiceConfig2W = modadvapi32.NewProc("QueryServiceConfig2W")
|
procQueryServiceConfig2W = modadvapi32.NewProc("QueryServiceConfig2W")
|
||||||
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func queryServiceConfig2(hService windows.Handle, infoLevel uint32, buf *byte, bufLen uint32, bytesNeeded *uint32) (err error) {
|
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
|
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
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user