Skip to content
Snippets Groups Projects
Commit fe392d0d authored by qmuntal's avatar qmuntal Committed by Quim Muntal
Browse files

os/user: support calling Current on impersonated threads

The syscall.OpenCurrentProcessToken call in user.Current fails
when called from an impersonated thread, as the process token is
normally in that case.

This change ensures that the current thread is not impersonated
when calling OpenCurrentProcessToken, and then restores the
impersonation state, if any.

Fixes #68647

Change-Id: I3197535dd8355d21029a42f7aa3936d8fb021202
Reviewed-on: https://go-review.googlesource.com/c/go/+/602415


Reviewed-by: default avatarDavid Chase <drchase@google.com>
Reviewed-by: default avatarMichael Knyszek <mknyszek@google.com>
Reviewed-by: default avatarAlex Brainman <alex.brainman@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
parent db0b6a85
No related branches found
No related tags found
No related merge requests found
...@@ -18,6 +18,8 @@ const ( ...@@ -18,6 +18,8 @@ const (
//sys ImpersonateSelf(impersonationlevel uint32) (err error) = advapi32.ImpersonateSelf //sys ImpersonateSelf(impersonationlevel uint32) (err error) = advapi32.ImpersonateSelf
//sys RevertToSelf() (err error) = advapi32.RevertToSelf //sys RevertToSelf() (err error) = advapi32.RevertToSelf
//sys ImpersonateLoggedOnUser(token syscall.Token) (err error) = advapi32.ImpersonateLoggedOnUser
//sys LogonUser(username *uint16, domain *uint16, password *uint16, logonType uint32, logonProvider uint32, token *syscall.Token) (err error) = advapi32.LogonUserW
const ( const (
TOKEN_ADJUST_PRIVILEGES = 0x0020 TOKEN_ADJUST_PRIVILEGES = 0x0020
...@@ -93,6 +95,26 @@ type LocalGroupUserInfo0 struct { ...@@ -93,6 +95,26 @@ type LocalGroupUserInfo0 struct {
Name *uint16 Name *uint16
} }
const (
NERR_UserNotFound syscall.Errno = 2221
NERR_UserExists syscall.Errno = 2224
)
const (
USER_PRIV_USER = 1
)
type UserInfo1 struct {
Name *uint16
Password *uint16
PasswordAge uint32
Priv uint32
HomeDir *uint16
Comment *uint16
Flags uint32
ScriptPath *uint16
}
type UserInfo4 struct { type UserInfo4 struct {
Name *uint16 Name *uint16
Password *uint16 Password *uint16
...@@ -125,6 +147,8 @@ type UserInfo4 struct { ...@@ -125,6 +147,8 @@ type UserInfo4 struct {
PasswordExpired uint32 PasswordExpired uint32
} }
//sys NetUserAdd(serverName *uint16, level uint32, buf *byte, parmErr *uint32) (neterr error) = netapi32.NetUserAdd
//sys NetUserDel(serverName *uint16, userName *uint16) (neterr error) = netapi32.NetUserDel
//sys NetUserGetLocalGroups(serverName *uint16, userName *uint16, level uint32, flags uint32, buf **byte, prefMaxLen uint32, entriesRead *uint32, totalEntries *uint32) (neterr error) = netapi32.NetUserGetLocalGroups //sys NetUserGetLocalGroups(serverName *uint16, userName *uint16, level uint32, flags uint32, buf **byte, prefMaxLen uint32, entriesRead *uint32, totalEntries *uint32) (neterr error) = netapi32.NetUserGetLocalGroups
// GetSystemDirectory retrieves the path to current location of the system // GetSystemDirectory retrieves the path to current location of the system
......
...@@ -39,6 +39,7 @@ const ( ...@@ -39,6 +39,7 @@ const (
ERROR_CALL_NOT_IMPLEMENTED syscall.Errno = 120 ERROR_CALL_NOT_IMPLEMENTED syscall.Errno = 120
ERROR_INVALID_NAME syscall.Errno = 123 ERROR_INVALID_NAME syscall.Errno = 123
ERROR_LOCK_FAILED syscall.Errno = 167 ERROR_LOCK_FAILED syscall.Errno = 167
ERROR_NO_TOKEN syscall.Errno = 1008
ERROR_NO_UNICODE_TRANSLATION syscall.Errno = 1113 ERROR_NO_UNICODE_TRANSLATION syscall.Errno = 1113
) )
......
...@@ -49,7 +49,9 @@ var ( ...@@ -49,7 +49,9 @@ var (
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx") procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
procImpersonateLoggedOnUser = modadvapi32.NewProc("ImpersonateLoggedOnUser")
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf") procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
procLogonUserW = modadvapi32.NewProc("LogonUserW")
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
procOpenSCManagerW = modadvapi32.NewProc("OpenSCManagerW") procOpenSCManagerW = modadvapi32.NewProc("OpenSCManagerW")
procOpenServiceW = modadvapi32.NewProc("OpenServiceW") procOpenServiceW = modadvapi32.NewProc("OpenServiceW")
...@@ -82,6 +84,8 @@ var ( ...@@ -82,6 +84,8 @@ var (
procVirtualQuery = modkernel32.NewProc("VirtualQuery") procVirtualQuery = modkernel32.NewProc("VirtualQuery")
procNetShareAdd = modnetapi32.NewProc("NetShareAdd") procNetShareAdd = modnetapi32.NewProc("NetShareAdd")
procNetShareDel = modnetapi32.NewProc("NetShareDel") procNetShareDel = modnetapi32.NewProc("NetShareDel")
procNetUserAdd = modnetapi32.NewProc("NetUserAdd")
procNetUserDel = modnetapi32.NewProc("NetUserDel")
procNetUserGetLocalGroups = modnetapi32.NewProc("NetUserGetLocalGroups") procNetUserGetLocalGroups = modnetapi32.NewProc("NetUserGetLocalGroups")
procRtlGetVersion = modntdll.NewProc("RtlGetVersion") procRtlGetVersion = modntdll.NewProc("RtlGetVersion")
procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
...@@ -113,6 +117,14 @@ func DuplicateTokenEx(hExistingToken syscall.Token, dwDesiredAccess uint32, lpTo ...@@ -113,6 +117,14 @@ func DuplicateTokenEx(hExistingToken syscall.Token, dwDesiredAccess uint32, lpTo
return return
} }
func ImpersonateLoggedOnUser(token syscall.Token) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateLoggedOnUser.Addr(), 1, uintptr(token), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ImpersonateSelf(impersonationlevel uint32) (err error) { func ImpersonateSelf(impersonationlevel uint32) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(impersonationlevel), 0, 0) r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(impersonationlevel), 0, 0)
if r1 == 0 { if r1 == 0 {
...@@ -121,6 +133,14 @@ func ImpersonateSelf(impersonationlevel uint32) (err error) { ...@@ -121,6 +133,14 @@ func ImpersonateSelf(impersonationlevel uint32) (err error) {
return return
} }
func LogonUser(username *uint16, domain *uint16, password *uint16, logonType uint32, logonProvider uint32, token *syscall.Token) (err error) {
r1, _, e1 := syscall.Syscall6(procLogonUserW.Addr(), 6, uintptr(unsafe.Pointer(username)), uintptr(unsafe.Pointer(domain)), uintptr(unsafe.Pointer(password)), uintptr(logonType), uintptr(logonProvider), uintptr(unsafe.Pointer(token)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func LookupPrivilegeValue(systemname *uint16, name *uint16, luid *LUID) (err error) { func LookupPrivilegeValue(systemname *uint16, name *uint16, luid *LUID) (err error) {
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemname)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid))) r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemname)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
if r1 == 0 { if r1 == 0 {
...@@ -385,6 +405,22 @@ func NetShareDel(serverName *uint16, netName *uint16, reserved uint32) (neterr e ...@@ -385,6 +405,22 @@ func NetShareDel(serverName *uint16, netName *uint16, reserved uint32) (neterr e
return return
} }
func NetUserAdd(serverName *uint16, level uint32, buf *byte, parmErr *uint32) (neterr error) {
r0, _, _ := syscall.Syscall6(procNetUserAdd.Addr(), 4, uintptr(unsafe.Pointer(serverName)), uintptr(level), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(parmErr)), 0, 0)
if r0 != 0 {
neterr = syscall.Errno(r0)
}
return
}
func NetUserDel(serverName *uint16, userName *uint16) (neterr error) {
r0, _, _ := syscall.Syscall(procNetUserDel.Addr(), 2, uintptr(unsafe.Pointer(serverName)), uintptr(unsafe.Pointer(userName)), 0)
if r0 != 0 {
neterr = syscall.Errno(r0)
}
return
}
func NetUserGetLocalGroups(serverName *uint16, userName *uint16, level uint32, flags uint32, buf **byte, prefMaxLen uint32, entriesRead *uint32, totalEntries *uint32) (neterr error) { func NetUserGetLocalGroups(serverName *uint16, userName *uint16, level uint32, flags uint32, buf **byte, prefMaxLen uint32, entriesRead *uint32, totalEntries *uint32) (neterr error) {
r0, _, _ := syscall.Syscall9(procNetUserGetLocalGroups.Addr(), 8, uintptr(unsafe.Pointer(serverName)), uintptr(unsafe.Pointer(userName)), uintptr(level), uintptr(flags), uintptr(unsafe.Pointer(buf)), uintptr(prefMaxLen), uintptr(unsafe.Pointer(entriesRead)), uintptr(unsafe.Pointer(totalEntries)), 0) r0, _, _ := syscall.Syscall9(procNetUserGetLocalGroups.Addr(), 8, uintptr(unsafe.Pointer(serverName)), uintptr(unsafe.Pointer(userName)), uintptr(level), uintptr(flags), uintptr(unsafe.Pointer(buf)), uintptr(prefMaxLen), uintptr(unsafe.Pointer(entriesRead)), uintptr(unsafe.Pointer(totalEntries)), 0)
if r0 != 0 { if r0 != 0 {
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
package user package user
import ( import (
"errors"
"fmt" "fmt"
"internal/syscall/windows" "internal/syscall/windows"
"internal/syscall/windows/registry" "internal/syscall/windows/registry"
"runtime"
"syscall" "syscall"
"unsafe" "unsafe"
) )
...@@ -200,36 +202,91 @@ var ( ...@@ -200,36 +202,91 @@ var (
) )
func current() (*User, error) { func current() (*User, error) {
t, e := syscall.OpenCurrentProcessToken() // Use runAsProcessOwner to ensure that we can access the process token
if e != nil { // when calling syscall.OpenCurrentProcessToken if the current thread
return nil, e // is impersonating a different user. See https://go.dev/issue/68647.
} var usr *User
defer t.Close() err := runAsProcessOwner(func() error {
u, e := t.GetTokenUser() t, e := syscall.OpenCurrentProcessToken()
if e != nil { if e != nil {
return nil, e return e
} }
pg, e := t.GetTokenPrimaryGroup() defer t.Close()
if e != nil { u, e := t.GetTokenUser()
return nil, e if e != nil {
} return e
uid, e := u.User.Sid.String() }
if e != nil { pg, e := t.GetTokenPrimaryGroup()
return nil, e if e != nil {
} return e
gid, e := pg.PrimaryGroup.String() }
if e != nil { uid, e := u.User.Sid.String()
return nil, e if e != nil {
} return e
dir, e := t.GetUserProfileDirectory() }
if e != nil { gid, e := pg.PrimaryGroup.String()
return nil, e if e != nil {
return e
}
dir, e := t.GetUserProfileDirectory()
if e != nil {
return e
}
username, domain, e := lookupUsernameAndDomain(u.User.Sid)
if e != nil {
return e
}
usr, e = newUser(uid, gid, dir, username, domain)
return e
})
return usr, err
}
// runAsProcessOwner runs f in the context of the current process owner,
// that is, removing any impersonation that may be in effect before calling f,
// and restoring the impersonation afterwards.
func runAsProcessOwner(f func() error) error {
var impersonationRollbackErr error
runtime.LockOSThread()
defer func() {
// If impersonation failed, the thread is running with the wrong token,
// so it's better to terminate it.
// This is achieved by not calling runtime.UnlockOSThread.
if impersonationRollbackErr != nil {
println("os/user: failed to revert to previous token:", impersonationRollbackErr.Error())
runtime.Goexit()
} else {
runtime.UnlockOSThread()
}
}()
prevToken, isProcessToken, err := getCurrentToken()
if err != nil {
return fmt.Errorf("os/user: failed to get current token: %w", err)
} }
username, domain, e := lookupUsernameAndDomain(u.User.Sid) defer prevToken.Close()
if e != nil { if !isProcessToken {
return nil, e if err = windows.RevertToSelf(); err != nil {
return fmt.Errorf("os/user: failed to revert to self: %w", err)
}
defer func() {
impersonationRollbackErr = windows.ImpersonateLoggedOnUser(prevToken)
}()
} }
return newUser(uid, gid, dir, username, domain) return f()
}
// getCurrentToken returns the current thread token, or
// the process token if the thread doesn't have a token.
func getCurrentToken() (t syscall.Token, isProcessToken bool, err error) {
thread, _ := windows.GetCurrentThread()
// Need TOKEN_DUPLICATE and TOKEN_IMPERSONATE to use the token in ImpersonateLoggedOnUser.
err = windows.OpenThreadToken(thread, syscall.TOKEN_QUERY|syscall.TOKEN_DUPLICATE|syscall.TOKEN_IMPERSONATE, true, &t)
if errors.Is(err, windows.ERROR_NO_TOKEN) {
// Not impersonating, use the process token.
isProcessToken = true
t, err = syscall.OpenCurrentProcessToken()
}
return t, isProcessToken, err
} }
// lookupUserPrimaryGroup obtains the primary group SID for a user using this method: // lookupUserPrimaryGroup obtains the primary group SID for a user using this method:
......
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package user
import (
"crypto/rand"
"encoding/base64"
"errors"
"internal/syscall/windows"
"runtime"
"strconv"
"syscall"
"testing"
"unsafe"
)
// windowsTestAcount creates a test user and returns a token for that user.
// If the user already exists, it will be deleted and recreated.
// The caller is responsible for closing the token.
func windowsTestAcount(t *testing.T) syscall.Token {
var password [33]byte
rand.Read(password[:])
// Add special chars to ensure it satisfies password requirements.
pwd := base64.StdEncoding.EncodeToString(password[:]) + "_-As@!%*(1)4#2"
name, err := syscall.UTF16PtrFromString("GoStdTestUser01")
if err != nil {
t.Fatal(err)
}
pwd16, err := syscall.UTF16PtrFromString(pwd)
if err != nil {
t.Fatal(err)
}
userInfo := windows.UserInfo1{
Name: name,
Password: pwd16,
Priv: windows.USER_PRIV_USER,
}
// Create user.
err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
t.Skip("skipping test; don't have permission to create user")
}
if errors.Is(err, windows.NERR_UserExists) {
// User already exists, delete and recreate.
if err = windows.NetUserDel(nil, name); err != nil {
t.Fatal(err)
}
if err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil); err != nil {
t.Fatal(err)
}
} else if err != nil {
t.Fatal(err)
}
domain, err := syscall.UTF16PtrFromString(".")
if err != nil {
t.Fatal(err)
}
const LOGON32_PROVIDER_DEFAULT = 0
const LOGON32_LOGON_INTERACTIVE = 2
var token syscall.Token
if err = windows.LogonUser(name, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
token.Close()
if err = windows.NetUserDel(nil, name); err != nil {
if !errors.Is(err, windows.NERR_UserNotFound) {
t.Fatal(err)
}
}
})
return token
}
func TestImpersonatedSelf(t *testing.T) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
want, err := current()
if err != nil {
t.Fatal(err)
}
levels := []uint32{
windows.SecurityAnonymous,
windows.SecurityIdentification,
windows.SecurityImpersonation,
windows.SecurityDelegation,
}
for _, level := range levels {
t.Run(strconv.Itoa(int(level)), func(t *testing.T) {
if err = windows.ImpersonateSelf(level); err != nil {
t.Fatal(err)
}
defer windows.RevertToSelf()
got, err := current()
if level == windows.SecurityAnonymous {
// We can't get the process token when using an anonymous token,
// so we expect an error here.
if err == nil {
t.Fatal("expected error")
}
return
}
if err != nil {
t.Fatal(err)
}
compare(t, want, got)
})
}
}
func TestImpersonated(t *testing.T) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
want, err := current()
if err != nil {
t.Fatal(err)
}
// Create a test user and log in as that user.
token := windowsTestAcount(t)
// Impersonate the test user.
if err = windows.ImpersonateLoggedOnUser(token); err != nil {
t.Fatal(err)
}
defer func() {
err = windows.RevertToSelf()
if err != nil {
// If we can't revert to self, we can't continue testing.
panic(err)
}
}()
got, err := current()
if err != nil {
t.Fatal(err)
}
compare(t, want, got)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment