Skip to content

Commit fd096d6

Browse files
dblohm7coadler
authored andcommitted
ipn/ipnauth, util/winutil: add temporary LookupPseudoUser workaround to address os/user.LookupId errors on Windows
I added util/winutil/LookupPseudoUser, which essentially consists of the bits that I am in the process of adding to Go's standard library. We check the provided SID for "S-1-5-x" where 17 <= x <= 20 (which are the known pseudo-users) and then manually populate a os/user.User struct with the correct information. Fixes tailscale#869 Fixes tailscale#2894 Signed-off-by: Aaron Klotz <[email protected]>
1 parent 6f37081 commit fd096d6

File tree

6 files changed

+131
-2
lines changed

6 files changed

+131
-2
lines changed

cmd/derper/depaware.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
183183
net/url from crypto/x509+
184184
os from crypto/rand+
185185
os/exec from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
186+
W os/user from tailscale.com/util/winutil
186187
path from golang.org/x/crypto/acme/autocert+
187188
path/filepath from crypto/x509+
188189
reflect from crypto/x509+

ipn/ipnauth/ipnauth.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import (
1414
"os/user"
1515
"runtime"
1616
"strconv"
17-
"syscall"
1817

1918
"inet.af/peercred"
2019
"tailscale.com/envknob"
2120
"tailscale.com/ipn"
2221
"tailscale.com/net/netstat"
2322
"tailscale.com/safesocket"
2423
"tailscale.com/types/logger"
24+
"tailscale.com/util/clientmetric"
2525
"tailscale.com/util/groupmember"
2626
"tailscale.com/util/pidowner"
2727
"tailscale.com/util/winutil"
@@ -122,18 +122,31 @@ func GetConnIdentity(logf logger.Logf, c net.Conn) (ci *ConnIdentity, err error)
122122
return ci, nil
123123
}
124124

125+
var metricIssue869Workaround = clientmetric.NewCounter("issue_869_workaround")
126+
125127
// LookupUserFromID is a wrapper around os/user.LookupId that works around some
126128
// issues on Windows. On non-Windows platforms it's identical to user.LookupId.
127129
func LookupUserFromID(logf logger.Logf, uid string) (*user.User, error) {
128130
u, err := user.LookupId(uid)
129-
if err != nil && runtime.GOOS == "windows" && errors.Is(err, syscall.Errno(0x534)) {
131+
if err != nil && runtime.GOOS == "windows" {
132+
// See if uid resolves as a pseudo-user. Temporary workaround until
133+
// https://github.com/golang/go/issues/49509 resolves and ships.
134+
if u, err := winutil.LookupPseudoUser(uid); err == nil {
135+
return u, nil
136+
}
137+
138+
// TODO(aaron): With LookupPseudoUser in place, I don't expect us to reach
139+
// this point anymore. Leaving the below workaround in for now to confirm
140+
// that pseudo-user resolution sufficiently handles this problem.
141+
130142
// The below workaround is only applicable when uid represents a
131143
// valid security principal. Omitting this check causes us to succeed
132144
// even when uid represents a deleted user.
133145
if !winutil.IsSIDValidPrincipal(uid) {
134146
return nil, err
135147
}
136148

149+
metricIssue869Workaround.Add(1)
137150
logf("[warning] issue 869: os/user.LookupId failed; ignoring")
138151
// Work around https://github.com/tailscale/tailscale/issues/869 for
139152
// now. We don't strictly need the username. It's just a nice-to-have.

util/winutil/winutil.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
// Package winutil contains misc Windows/Win32 helper functions.
66
package winutil
77

8+
import (
9+
"os/user"
10+
)
11+
812
// RegBase is the registry path inside HKEY_LOCAL_MACHINE where registry settings
913
// are stored. This constant is a non-empty string only when GOOS=windows.
1014
const RegBase = regBase
@@ -62,3 +66,13 @@ func GetRegInteger(name string, defval uint64) uint64 {
6266
func IsSIDValidPrincipal(uid string) bool {
6367
return isSIDValidPrincipal(uid)
6468
}
69+
70+
// LookupPseudoUser attempts to resolve the user specified by uid by checking
71+
// against well-known pseudo-users on Windows. This is a temporary workaround
72+
// until https://github.com/golang/go/issues/49509 is resolved and shipped.
73+
//
74+
// This function will only work on GOOS=windows. Trying to run it on any other
75+
// OS will always return an error.
76+
func LookupPseudoUser(uid string) (*user.User, error) {
77+
return lookupPseudoUser(uid)
78+
}

util/winutil/winutil_notwindows.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
package winutil
88

9+
import (
10+
"fmt"
11+
"os/user"
12+
"runtime"
13+
)
14+
915
const regBase = ``
1016

1117
func getPolicyString(name, defval string) string { return defval }
@@ -17,3 +23,7 @@ func getRegString(name, defval string) string { return defval }
1723
func getRegInteger(name string, defval uint64) uint64 { return defval }
1824

1925
func isSIDValidPrincipal(uid string) bool { return false }
26+
27+
func lookupPseudoUser(uid string) (*user.User, error) {
28+
return nil, fmt.Errorf("unimplemented on %v", runtime.GOOS)
29+
}

util/winutil/winutil_windows.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"log"
1111
"os/exec"
12+
"os/user"
1213
"runtime"
1314
"strings"
1415
"syscall"
@@ -492,3 +493,62 @@ func OpenKeyWait(k registry.Key, path RegistryPath, access uint32) (registry.Key
492493
k = key
493494
}
494495
}
496+
497+
func lookupPseudoUser(uid string) (*user.User, error) {
498+
sid, err := windows.StringToSid(uid)
499+
if err != nil {
500+
return nil, err
501+
}
502+
503+
// We're looking for SIDs "S-1-5-x" where 17 <= x <= 20.
504+
// This is checking for the the "5"
505+
if sid.IdentifierAuthority() != windows.SECURITY_NT_AUTHORITY {
506+
return nil, fmt.Errorf(`SID %q does not use "NT AUTHORITY"`, uid)
507+
}
508+
509+
// This is ensuring that there is only one sub-authority.
510+
// In other words, only one value after the "5".
511+
if sid.SubAuthorityCount() != 1 {
512+
return nil, fmt.Errorf("SID %q should have only one subauthority", uid)
513+
}
514+
515+
// Get that sub-authority value (this is "x" above) and check it.
516+
rid := sid.SubAuthority(0)
517+
if rid < 17 || rid > 20 {
518+
return nil, fmt.Errorf("SID %q does not represent a known pseudo-user", uid)
519+
}
520+
521+
// We've got one of the known pseudo-users. Look up the localized name of the
522+
// account.
523+
username, domain, _, err := sid.LookupAccount("")
524+
if err != nil {
525+
return nil, err
526+
}
527+
528+
// This call is best-effort. If it fails, homeDir will be empty.
529+
homeDir, _ := findHomeDirInRegistry(uid)
530+
531+
result := &user.User{
532+
Uid: uid,
533+
Gid: uid, // Gid == Uid with these accounts.
534+
Username: fmt.Sprintf(`%s\%s`, domain, username),
535+
Name: username,
536+
HomeDir: homeDir,
537+
}
538+
return result, nil
539+
}
540+
541+
// findHomeDirInRegistry finds the user home path based on the uid.
542+
// This is borrowed from Go's std lib.
543+
func findHomeDirInRegistry(uid string) (dir string, err error) {
544+
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\`+uid, registry.QUERY_VALUE)
545+
if err != nil {
546+
return "", err
547+
}
548+
defer k.Close()
549+
dir, _, err = k.GetStringValue("ProfileImagePath")
550+
if err != nil {
551+
return "", err
552+
}
553+
return dir, nil
554+
}

util/winutil/winutil_windows_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package winutil
6+
7+
import (
8+
"testing"
9+
)
10+
11+
const (
12+
localSystemSID = "S-1-5-18"
13+
networkSID = "S-1-5-2"
14+
)
15+
16+
func TestLookupPseudoUser(t *testing.T) {
17+
localSystem, err := LookupPseudoUser(localSystemSID)
18+
if err != nil {
19+
t.Errorf("LookupPseudoUser(%q) error: %v", localSystemSID, err)
20+
}
21+
if localSystem.Gid != localSystemSID {
22+
t.Errorf("incorrect Gid, got %q, want %q", localSystem.Gid, localSystemSID)
23+
}
24+
t.Logf("localSystem: %v", localSystem)
25+
26+
// networkSID is a built-in known group but not a pseudo-user.
27+
_, err = LookupPseudoUser(networkSID)
28+
if err == nil {
29+
t.Errorf("LookupPseudoUser(%q) unexpectedly succeeded", networkSID)
30+
}
31+
}

0 commit comments

Comments
 (0)