|
5 | 5 | package user
|
6 | 6 |
|
7 | 7 | import (
|
| 8 | + "errors" |
8 | 9 | "fmt"
|
9 | 10 | "internal/syscall/windows"
|
10 | 11 | "internal/syscall/windows/registry"
|
| 12 | + "runtime" |
11 | 13 | "syscall"
|
12 | 14 | "unsafe"
|
13 | 15 | )
|
@@ -200,36 +202,91 @@ var (
|
200 | 202 | )
|
201 | 203 |
|
202 | 204 | 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) |
227 | 265 | }
|
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 | + }() |
231 | 274 | }
|
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 |
233 | 290 | }
|
234 | 291 |
|
235 | 292 | // lookupUserPrimaryGroup obtains the primary group SID for a user using this method:
|
|
0 commit comments