diff --git a/ipn/ipnauth/ipnauth_windows.go b/ipn/ipnauth/ipnauth_windows.go index 86cfd7969..c38b6db0f 100644 --- a/ipn/ipnauth/ipnauth_windows.go +++ b/ipn/ipnauth/ipnauth_windows.go @@ -13,6 +13,7 @@ import ( "tailscale.com/ipn" "tailscale.com/safesocket" "tailscale.com/types/logger" + "tailscale.com/util/winutil" ) // GetConnIdentity extracts the identity information from the connection @@ -64,7 +65,28 @@ func (t *token) IsAdministrator() (bool, error) { return false, err } - return t.t.IsMember(baSID) + isMember, err := t.t.IsMember(baSID) + if err != nil { + return false, err + } + if isMember { + return true, nil + } + + isLimited, err := winutil.IsTokenLimited(t.t) + if err != nil || !isLimited { + return false, err + } + + // Try to obtain a linked token, and if present, check it. + // (This should be the elevated token associated with limited UAC accounts.) + linkedToken, err := t.t.GetLinkedToken() + if err != nil { + return false, err + } + defer linkedToken.Close() + + return linkedToken.IsMember(baSID) } func (t *token) IsElevated() bool { diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 755919275..1ff4cc1ab 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -367,8 +367,7 @@ func (s *Server) connCanFetchCerts(ci *ipnauth.ConnIdentity) bool { // connIsLocalAdmin reports whether ci has administrative access to the local // machine, for whatever that means with respect to the current OS. // -// This returns true only on Windows machines when the client user is a -// member of the built-in Administrators group (but not necessarily elevated). +// This returns true only on Windows machines when the client user is elevated. // This is useful because, on Windows, tailscaled itself always runs with // elevated rights: we want to avoid privilege escalation for certain mutative operations. func (s *Server) connIsLocalAdmin(ci *ipnauth.ConnIdentity) bool { @@ -381,12 +380,7 @@ func (s *Server) connIsLocalAdmin(ci *ipnauth.ConnIdentity) bool { } defer tok.Close() - isAdmin, err := tok.IsAdministrator() - if err != nil { - s.logf("ipnauth.WindowsToken.IsAdministrator() error: %v", err) - return false - } - return isAdmin + return tok.IsElevated() } // addActiveHTTPRequest adds c to the server's list of active HTTP requests. diff --git a/util/winutil/winutil_windows.go b/util/winutil/winutil_windows.go index a686e6335..2643314f5 100644 --- a/util/winutil/winutil_windows.go +++ b/util/winutil/winutil_windows.go @@ -367,6 +367,30 @@ func getTokenPrimaryGroupInfo(token windows.Token) (*windows.Tokenprimarygroup, return (*windows.Tokenprimarygroup)(unsafe.Pointer(&buf[0])), nil } +type tokenElevationType int32 + +const ( + tokenElevationTypeDefault tokenElevationType = 1 + tokenElevationTypeFull tokenElevationType = 2 + tokenElevationTypeLimited tokenElevationType = 3 +) + +func getTokenElevationType(token windows.Token) (result tokenElevationType, err error) { + var actualLen uint32 + p := (*byte)(unsafe.Pointer(&result)) + err = windows.GetTokenInformation(token, windows.TokenElevationType, p, uint32(unsafe.Sizeof(result)), &actualLen) + return result, err +} + +// IsTokenLimited returns whether token is a limited UAC token. +func IsTokenLimited(token windows.Token) (bool, error) { + elevationType, err := getTokenElevationType(token) + if err != nil { + return false, err + } + return elevationType == tokenElevationTypeLimited, nil +} + // UserSIDs contains the SIDs for a Windows NT token object's associated user // as well as its primary group. type UserSIDs struct {