From 7d8feb2784b6c8278fd19c808de33ac20bda19e5 Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Mon, 22 Nov 2021 11:45:19 -0700 Subject: [PATCH] hostinfo: change Windows implementation to directly query version information using API and registry We replace the cmd.exe invocation with RtlGetNtVersionNumbers for the first three fields. On Windows 10+, we query for the fourth field which is available via the registry. The fourth field is not really documented anywhere; Firefox has been querying it successfully since Windows 10 was released, so we can be pretty confident in its longevity at this point. Fixes https://github.com/tailscale/tailscale/issues/1478 Signed-off-by: Aaron Klotz --- cmd/tailscale/depaware.txt | 2 +- hostinfo/hostinfo_windows.go | 45 +++++++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index bbf06da4d..d1fcf8a20 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -103,7 +103,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+ LD golang.org/x/sys/unix from tailscale.com/net/netns+ W golang.org/x/sys/windows from golang.org/x/sys/windows/registry+ - W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg + W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ golang.org/x/text/secure/bidirule from golang.org/x/net/idna golang.org/x/text/transform from golang.org/x/text/secure/bidirule+ golang.org/x/text/unicode/bidi from golang.org/x/net/idna+ diff --git a/hostinfo/hostinfo_windows.go b/hostinfo/hostinfo_windows.go index 618b79e69..2df088f4b 100644 --- a/hostinfo/hostinfo_windows.go +++ b/hostinfo/hostinfo_windows.go @@ -5,10 +5,11 @@ package hostinfo import ( - "os/exec" - "strings" + "fmt" "sync/atomic" - "syscall" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" ) func init() { @@ -21,19 +22,37 @@ func osVersionWindows() string { if s, ok := winVerCache.Load().(string); ok { return s } - cmd := exec.Command("cmd", "/c", "ver") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - out, _ := cmd.Output() // "\nMicrosoft Windows [Version 10.0.19041.388]\n\n" - s := strings.TrimSpace(string(out)) - s = strings.TrimPrefix(s, "Microsoft Windows [") - s = strings.TrimSuffix(s, "]") - - // "Version 10.x.y.z", with "Version" localized. Keep only stuff after the space. - if sp := strings.Index(s, " "); sp != -1 { - s = s[sp+1:] + major, minor, build := windows.RtlGetNtVersionNumbers() + s := fmt.Sprintf("%d.%d.%d", major, minor, build) + // Windows 11 still uses 10 as its major number internally + if major == 10 { + if ubr, err := getUBR(); err == nil { + s += fmt.Sprintf(".%d", ubr) + } } if s != "" { winVerCache.Store(s) } return s // "10.0.19041.388", ideally } + +// getUBR obtains a fourth version field, the "Update Build Revision", +// from the registry. This field is only available beginning with Windows 10. +func getUBR() (uint32, error) { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, + `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE|registry.WOW64_64KEY) + if err != nil { + return 0, err + } + defer key.Close() + + val, valType, err := key.GetIntegerValue("UBR") + if err != nil { + return 0, err + } + if valType != registry.DWORD { + return 0, registry.ErrUnexpectedType + } + + return uint32(val), nil +}