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 <aaron@tailscale.com>
This commit is contained in:
Aaron Klotz 2021-11-22 11:45:19 -07:00
parent 1a629a4715
commit 7d8feb2784
2 changed files with 33 additions and 14 deletions

View File

@ -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+ golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from tailscale.com/net/netns+ 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 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/secure/bidirule from golang.org/x/net/idna
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+ golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+ golang.org/x/text/unicode/bidi from golang.org/x/net/idna+

View File

@ -5,10 +5,11 @@
package hostinfo package hostinfo
import ( import (
"os/exec" "fmt"
"strings"
"sync/atomic" "sync/atomic"
"syscall"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
) )
func init() { func init() {
@ -21,19 +22,37 @@ func osVersionWindows() string {
if s, ok := winVerCache.Load().(string); ok { if s, ok := winVerCache.Load().(string); ok {
return s return s
} }
cmd := exec.Command("cmd", "/c", "ver") major, minor, build := windows.RtlGetNtVersionNumbers()
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} s := fmt.Sprintf("%d.%d.%d", major, minor, build)
out, _ := cmd.Output() // "\nMicrosoft Windows [Version 10.0.19041.388]\n\n" // Windows 11 still uses 10 as its major number internally
s := strings.TrimSpace(string(out)) if major == 10 {
s = strings.TrimPrefix(s, "Microsoft Windows [") if ubr, err := getUBR(); err == nil {
s = strings.TrimSuffix(s, "]") s += fmt.Sprintf(".%d", ubr)
}
// "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:]
} }
if s != "" { if s != "" {
winVerCache.Store(s) winVerCache.Store(s)
} }
return s // "10.0.19041.388", ideally 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
}