Skip to content

Commit fe392d0

Browse files
committed
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: David Chase <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Alex Brainman <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent db0b6a8 commit fe392d0

File tree

5 files changed

+291
-28
lines changed

5 files changed

+291
-28
lines changed

src/internal/syscall/windows/security_windows.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const (
1818

1919
//sys ImpersonateSelf(impersonationlevel uint32) (err error) = advapi32.ImpersonateSelf
2020
//sys RevertToSelf() (err error) = advapi32.RevertToSelf
21+
//sys ImpersonateLoggedOnUser(token syscall.Token) (err error) = advapi32.ImpersonateLoggedOnUser
22+
//sys LogonUser(username *uint16, domain *uint16, password *uint16, logonType uint32, logonProvider uint32, token *syscall.Token) (err error) = advapi32.LogonUserW
2123

2224
const (
2325
TOKEN_ADJUST_PRIVILEGES = 0x0020
@@ -93,6 +95,26 @@ type LocalGroupUserInfo0 struct {
9395
Name *uint16
9496
}
9597

98+
const (
99+
NERR_UserNotFound syscall.Errno = 2221
100+
NERR_UserExists syscall.Errno = 2224
101+
)
102+
103+
const (
104+
USER_PRIV_USER = 1
105+
)
106+
107+
type UserInfo1 struct {
108+
Name *uint16
109+
Password *uint16
110+
PasswordAge uint32
111+
Priv uint32
112+
HomeDir *uint16
113+
Comment *uint16
114+
Flags uint32
115+
ScriptPath *uint16
116+
}
117+
96118
type UserInfo4 struct {
97119
Name *uint16
98120
Password *uint16
@@ -125,6 +147,8 @@ type UserInfo4 struct {
125147
PasswordExpired uint32
126148
}
127149

150+
//sys NetUserAdd(serverName *uint16, level uint32, buf *byte, parmErr *uint32) (neterr error) = netapi32.NetUserAdd
151+
//sys NetUserDel(serverName *uint16, userName *uint16) (neterr error) = netapi32.NetUserDel
128152
//sys NetUserGetLocalGroups(serverName *uint16, userName *uint16, level uint32, flags uint32, buf **byte, prefMaxLen uint32, entriesRead *uint32, totalEntries *uint32) (neterr error) = netapi32.NetUserGetLocalGroups
129153

130154
// GetSystemDirectory retrieves the path to current location of the system

src/internal/syscall/windows/syscall_windows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const (
3939
ERROR_CALL_NOT_IMPLEMENTED syscall.Errno = 120
4040
ERROR_INVALID_NAME syscall.Errno = 123
4141
ERROR_LOCK_FAILED syscall.Errno = 167
42+
ERROR_NO_TOKEN syscall.Errno = 1008
4243
ERROR_NO_UNICODE_TRANSLATION syscall.Errno = 1113
4344
)
4445

src/internal/syscall/windows/zsyscall_windows.go

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/os/user/lookup_windows.go

Lines changed: 85 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
package user
66

77
import (
8+
"errors"
89
"fmt"
910
"internal/syscall/windows"
1011
"internal/syscall/windows/registry"
12+
"runtime"
1113
"syscall"
1214
"unsafe"
1315
)
@@ -200,36 +202,91 @@ var (
200202
)
201203

202204
func current() (*User, error) {
203-
t, e := syscall.OpenCurrentProcessToken()
204-
if e != nil {
205-
return nil, e
206-
}
207-
defer t.Close()
208-
u, e := t.GetTokenUser()
209-
if e != nil {
210-
return nil, e
211-
}
212-
pg, e := t.GetTokenPrimaryGroup()
213-
if e != nil {
214-
return nil, e
215-
}
216-
uid, e := u.User.Sid.String()
217-
if e != nil {
218-
return nil, e
219-
}
220-
gid, e := pg.PrimaryGroup.String()
221-
if e != nil {
222-
return nil, e
223-
}
224-
dir, e := t.GetUserProfileDirectory()
225-
if e != nil {
226-
return nil, e
205+
// Use runAsProcessOwner to ensure that we can access the process token
206+
// when calling syscall.OpenCurrentProcessToken if the current thread
207+
// is impersonating a different user. See https://go.dev/issue/68647.
208+
var usr *User
209+
err := runAsProcessOwner(func() error {
210+
t, e := syscall.OpenCurrentProcessToken()
211+
if e != nil {
212+
return e
213+
}
214+
defer t.Close()
215+
u, e := t.GetTokenUser()
216+
if e != nil {
217+
return e
218+
}
219+
pg, e := t.GetTokenPrimaryGroup()
220+
if e != nil {
221+
return e
222+
}
223+
uid, e := u.User.Sid.String()
224+
if e != nil {
225+
return e
226+
}
227+
gid, e := pg.PrimaryGroup.String()
228+
if e != nil {
229+
return e
230+
}
231+
dir, e := t.GetUserProfileDirectory()
232+
if e != nil {
233+
return e
234+
}
235+
username, domain, e := lookupUsernameAndDomain(u.User.Sid)
236+
if e != nil {
237+
return e
238+
}
239+
usr, e = newUser(uid, gid, dir, username, domain)
240+
return e
241+
})
242+
return usr, err
243+
}
244+
245+
// runAsProcessOwner runs f in the context of the current process owner,
246+
// that is, removing any impersonation that may be in effect before calling f,
247+
// and restoring the impersonation afterwards.
248+
func runAsProcessOwner(f func() error) error {
249+
var impersonationRollbackErr error
250+
runtime.LockOSThread()
251+
defer func() {
252+
// If impersonation failed, the thread is running with the wrong token,
253+
// so it's better to terminate it.
254+
// This is achieved by not calling runtime.UnlockOSThread.
255+
if impersonationRollbackErr != nil {
256+
println("os/user: failed to revert to previous token:", impersonationRollbackErr.Error())
257+
runtime.Goexit()
258+
} else {
259+
runtime.UnlockOSThread()
260+
}
261+
}()
262+
prevToken, isProcessToken, err := getCurrentToken()
263+
if err != nil {
264+
return fmt.Errorf("os/user: failed to get current token: %w", err)
227265
}
228-
username, domain, e := lookupUsernameAndDomain(u.User.Sid)
229-
if e != nil {
230-
return nil, e
266+
defer prevToken.Close()
267+
if !isProcessToken {
268+
if err = windows.RevertToSelf(); err != nil {
269+
return fmt.Errorf("os/user: failed to revert to self: %w", err)
270+
}
271+
defer func() {
272+
impersonationRollbackErr = windows.ImpersonateLoggedOnUser(prevToken)
273+
}()
231274
}
232-
return newUser(uid, gid, dir, username, domain)
275+
return f()
276+
}
277+
278+
// getCurrentToken returns the current thread token, or
279+
// the process token if the thread doesn't have a token.
280+
func getCurrentToken() (t syscall.Token, isProcessToken bool, err error) {
281+
thread, _ := windows.GetCurrentThread()
282+
// Need TOKEN_DUPLICATE and TOKEN_IMPERSONATE to use the token in ImpersonateLoggedOnUser.
283+
err = windows.OpenThreadToken(thread, syscall.TOKEN_QUERY|syscall.TOKEN_DUPLICATE|syscall.TOKEN_IMPERSONATE, true, &t)
284+
if errors.Is(err, windows.ERROR_NO_TOKEN) {
285+
// Not impersonating, use the process token.
286+
isProcessToken = true
287+
t, err = syscall.OpenCurrentProcessToken()
288+
}
289+
return t, isProcessToken, err
233290
}
234291

235292
// lookupUserPrimaryGroup obtains the primary group SID for a user using this method:

0 commit comments

Comments
 (0)