winutil_windows.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package winutil
  4. import (
  5. "errors"
  6. "fmt"
  7. "log"
  8. "os/exec"
  9. "os/user"
  10. "runtime"
  11. "strings"
  12. "syscall"
  13. "time"
  14. "unsafe"
  15. "golang.org/x/sys/windows"
  16. "golang.org/x/sys/windows/registry"
  17. )
  18. const (
  19. regBase = `SOFTWARE\Tailscale IPN`
  20. regPolicyBase = `SOFTWARE\Policies\Tailscale`
  21. )
  22. // ErrNoShell is returned when the shell process is not found.
  23. var ErrNoShell = errors.New("no Shell process is present")
  24. // GetDesktopPID searches the PID of the process that's running the
  25. // currently active desktop. Returns ErrNoShell if the shell is not present.
  26. // Usually the PID will be for explorer.exe.
  27. func GetDesktopPID() (uint32, error) {
  28. hwnd := windows.GetShellWindow()
  29. if hwnd == 0 {
  30. return 0, ErrNoShell
  31. }
  32. var pid uint32
  33. windows.GetWindowThreadProcessId(hwnd, &pid)
  34. if pid == 0 {
  35. return 0, fmt.Errorf("invalid PID for HWND %v", hwnd)
  36. }
  37. return pid, nil
  38. }
  39. func getPolicyString(name, defval string) string {
  40. s, err := getRegStringInternal(regPolicyBase, name)
  41. if err != nil {
  42. // Fall back to the legacy path
  43. return getRegString(name, defval)
  44. }
  45. return s
  46. }
  47. func getPolicyInteger(name string, defval uint64) uint64 {
  48. i, err := getRegIntegerInternal(regPolicyBase, name)
  49. if err != nil {
  50. // Fall back to the legacy path
  51. return getRegInteger(name, defval)
  52. }
  53. return i
  54. }
  55. func getRegString(name, defval string) string {
  56. s, err := getRegStringInternal(regBase, name)
  57. if err != nil {
  58. return defval
  59. }
  60. return s
  61. }
  62. func getRegInteger(name string, defval uint64) uint64 {
  63. i, err := getRegIntegerInternal(regBase, name)
  64. if err != nil {
  65. return defval
  66. }
  67. return i
  68. }
  69. func getRegStringInternal(subKey, name string) (string, error) {
  70. key, err := registry.OpenKey(registry.LOCAL_MACHINE, subKey, registry.READ)
  71. if err != nil {
  72. if err != registry.ErrNotExist {
  73. log.Printf("registry.OpenKey(%v): %v", subKey, err)
  74. }
  75. return "", err
  76. }
  77. defer key.Close()
  78. val, _, err := key.GetStringValue(name)
  79. if err != nil {
  80. if err != registry.ErrNotExist {
  81. log.Printf("registry.GetStringValue(%v): %v", name, err)
  82. }
  83. return "", err
  84. }
  85. return val, nil
  86. }
  87. // GetRegStrings looks up a registry value in the local machine path, or returns
  88. // the given default if it can't.
  89. func GetRegStrings(name string, defval []string) []string {
  90. s, err := getRegStringsInternal(regBase, name)
  91. if err != nil {
  92. return defval
  93. }
  94. return s
  95. }
  96. func getRegStringsInternal(subKey, name string) ([]string, error) {
  97. key, err := registry.OpenKey(registry.LOCAL_MACHINE, subKey, registry.READ)
  98. if err != nil {
  99. if err != registry.ErrNotExist {
  100. log.Printf("registry.OpenKey(%v): %v", subKey, err)
  101. }
  102. return nil, err
  103. }
  104. defer key.Close()
  105. val, _, err := key.GetStringsValue(name)
  106. if err != nil {
  107. if err != registry.ErrNotExist {
  108. log.Printf("registry.GetStringValue(%v): %v", name, err)
  109. }
  110. return nil, err
  111. }
  112. return val, nil
  113. }
  114. // SetRegStrings sets a MULTI_SZ value in the in the local machine path
  115. // to the strings specified by values.
  116. func SetRegStrings(name string, values []string) error {
  117. return setRegStringsInternal(regBase, name, values)
  118. }
  119. func setRegStringsInternal(subKey, name string, values []string) error {
  120. key, _, err := registry.CreateKey(registry.LOCAL_MACHINE, subKey, registry.SET_VALUE)
  121. if err != nil {
  122. log.Printf("registry.CreateKey(%v): %v", subKey, err)
  123. }
  124. defer key.Close()
  125. return key.SetStringsValue(name, values)
  126. }
  127. // DeleteRegValue removes a registry value in the local machine path.
  128. func DeleteRegValue(name string) error {
  129. return deleteRegValueInternal(regBase, name)
  130. }
  131. func deleteRegValueInternal(subKey, name string) error {
  132. key, err := registry.OpenKey(registry.LOCAL_MACHINE, subKey, registry.SET_VALUE)
  133. if err == registry.ErrNotExist {
  134. return nil
  135. }
  136. if err != nil {
  137. log.Printf("registry.OpenKey(%v): %v", subKey, err)
  138. return err
  139. }
  140. defer key.Close()
  141. err = key.DeleteValue(name)
  142. if err == registry.ErrNotExist {
  143. err = nil
  144. }
  145. return err
  146. }
  147. func getRegIntegerInternal(subKey, name string) (uint64, error) {
  148. key, err := registry.OpenKey(registry.LOCAL_MACHINE, subKey, registry.READ)
  149. if err != nil {
  150. if err != registry.ErrNotExist {
  151. log.Printf("registry.OpenKey(%v): %v", subKey, err)
  152. }
  153. return 0, err
  154. }
  155. defer key.Close()
  156. val, _, err := key.GetIntegerValue(name)
  157. if err != nil {
  158. if err != registry.ErrNotExist {
  159. log.Printf("registry.GetIntegerValue(%v): %v", name, err)
  160. }
  161. return 0, err
  162. }
  163. return val, nil
  164. }
  165. var (
  166. kernel32 = syscall.NewLazyDLL("kernel32.dll")
  167. procWTSGetActiveConsoleSessionId = kernel32.NewProc("WTSGetActiveConsoleSessionId")
  168. )
  169. // TODO(crawshaw): replace with x/sys/windows... one day.
  170. // https://go-review.googlesource.com/c/sys/+/331909
  171. func WTSGetActiveConsoleSessionId() uint32 {
  172. r1, _, _ := procWTSGetActiveConsoleSessionId.Call()
  173. return uint32(r1)
  174. }
  175. func isSIDValidPrincipal(uid string) bool {
  176. usid, err := syscall.StringToSid(uid)
  177. if err != nil {
  178. return false
  179. }
  180. _, _, accType, err := usid.LookupAccount("")
  181. if err != nil {
  182. return false
  183. }
  184. switch accType {
  185. case syscall.SidTypeUser, syscall.SidTypeGroup, syscall.SidTypeDomain, syscall.SidTypeAlias, syscall.SidTypeWellKnownGroup, syscall.SidTypeComputer:
  186. return true
  187. default:
  188. // Reject deleted users, invalid SIDs, unknown SIDs, mandatory label SIDs, etc.
  189. return false
  190. }
  191. }
  192. // EnableCurrentThreadPrivilege enables the named privilege
  193. // in the current thread access token.
  194. func EnableCurrentThreadPrivilege(name string) error {
  195. var t windows.Token
  196. err := windows.OpenThreadToken(windows.CurrentThread(),
  197. windows.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t)
  198. if err != nil {
  199. return err
  200. }
  201. defer t.Close()
  202. var tp windows.Tokenprivileges
  203. privStr, err := syscall.UTF16PtrFromString(name)
  204. if err != nil {
  205. return err
  206. }
  207. err = windows.LookupPrivilegeValue(nil, privStr, &tp.Privileges[0].Luid)
  208. if err != nil {
  209. return err
  210. }
  211. tp.PrivilegeCount = 1
  212. tp.Privileges[0].Attributes = windows.SE_PRIVILEGE_ENABLED
  213. return windows.AdjustTokenPrivileges(t, false, &tp, 0, nil, nil)
  214. }
  215. // StartProcessAsChild starts exePath process as a child of parentPID.
  216. // StartProcessAsChild copies parentPID's environment variables into
  217. // the new process, along with any optional environment variables in extraEnv.
  218. func StartProcessAsChild(parentPID uint32, exePath string, extraEnv []string) error {
  219. // The rest of this function requires SeDebugPrivilege to be held.
  220. runtime.LockOSThread()
  221. defer runtime.UnlockOSThread()
  222. err := windows.ImpersonateSelf(windows.SecurityImpersonation)
  223. if err != nil {
  224. return err
  225. }
  226. defer windows.RevertToSelf()
  227. // According to https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
  228. //
  229. // ... To open a handle to another process and obtain full access rights,
  230. // you must enable the SeDebugPrivilege privilege. ...
  231. //
  232. // But we only need PROCESS_CREATE_PROCESS. So perhaps SeDebugPrivilege is too much.
  233. //
  234. // https://devblogs.microsoft.com/oldnewthing/20080314-00/?p=23113
  235. //
  236. // TODO: try look for something less than SeDebugPrivilege
  237. err = EnableCurrentThreadPrivilege("SeDebugPrivilege")
  238. if err != nil {
  239. return err
  240. }
  241. ph, err := windows.OpenProcess(
  242. windows.PROCESS_CREATE_PROCESS|windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_DUP_HANDLE,
  243. false, parentPID)
  244. if err != nil {
  245. return err
  246. }
  247. defer windows.CloseHandle(ph)
  248. var pt windows.Token
  249. err = windows.OpenProcessToken(ph, windows.TOKEN_QUERY, &pt)
  250. if err != nil {
  251. return err
  252. }
  253. defer pt.Close()
  254. env, err := pt.Environ(false)
  255. if err != nil {
  256. return err
  257. }
  258. env = append(env, extraEnv...)
  259. sys := &syscall.SysProcAttr{ParentProcess: syscall.Handle(ph)}
  260. cmd := exec.Command(exePath)
  261. cmd.Env = env
  262. cmd.SysProcAttr = sys
  263. return cmd.Start()
  264. }
  265. // StartProcessAsCurrentGUIUser is like StartProcessAsChild, but if finds
  266. // current logged in user desktop process (normally explorer.exe),
  267. // and passes found PID to StartProcessAsChild.
  268. func StartProcessAsCurrentGUIUser(exePath string, extraEnv []string) error {
  269. // as described in https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443
  270. desktop, err := GetDesktopPID()
  271. if err != nil {
  272. return fmt.Errorf("failed to find desktop: %v", err)
  273. }
  274. err = StartProcessAsChild(desktop, exePath, extraEnv)
  275. if err != nil {
  276. return fmt.Errorf("failed to start executable: %v", err)
  277. }
  278. return nil
  279. }
  280. // CreateAppMutex creates a named Windows mutex, returning nil if the mutex
  281. // is created successfully or an error if the mutex already exists or could not
  282. // be created for some other reason.
  283. func CreateAppMutex(name string) (windows.Handle, error) {
  284. return windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(name))
  285. }
  286. func getTokenInfo(token windows.Token, infoClass uint32) ([]byte, error) {
  287. var desiredLen uint32
  288. err := windows.GetTokenInformation(token, infoClass, nil, 0, &desiredLen)
  289. if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER {
  290. return nil, err
  291. }
  292. buf := make([]byte, desiredLen)
  293. actualLen := desiredLen
  294. err = windows.GetTokenInformation(token, infoClass, &buf[0], desiredLen, &actualLen)
  295. return buf, err
  296. }
  297. func getTokenUserInfo(token windows.Token) (*windows.Tokenuser, error) {
  298. buf, err := getTokenInfo(token, windows.TokenUser)
  299. if err != nil {
  300. return nil, err
  301. }
  302. return (*windows.Tokenuser)(unsafe.Pointer(&buf[0])), nil
  303. }
  304. func getTokenPrimaryGroupInfo(token windows.Token) (*windows.Tokenprimarygroup, error) {
  305. buf, err := getTokenInfo(token, windows.TokenPrimaryGroup)
  306. if err != nil {
  307. return nil, err
  308. }
  309. return (*windows.Tokenprimarygroup)(unsafe.Pointer(&buf[0])), nil
  310. }
  311. // UserSIDs contains the SIDs for a Windows NT token object's associated user
  312. // as well as its primary group.
  313. type UserSIDs struct {
  314. User *windows.SID
  315. PrimaryGroup *windows.SID
  316. }
  317. // GetCurrentUserSIDs returns a UserSIDs struct containing SIDs for the
  318. // current process' user and primary group.
  319. func GetCurrentUserSIDs() (*UserSIDs, error) {
  320. token, err := windows.OpenCurrentProcessToken()
  321. if err != nil {
  322. return nil, err
  323. }
  324. defer token.Close()
  325. userInfo, err := getTokenUserInfo(token)
  326. if err != nil {
  327. return nil, err
  328. }
  329. primaryGroup, err := getTokenPrimaryGroupInfo(token)
  330. if err != nil {
  331. return nil, err
  332. }
  333. return &UserSIDs{userInfo.User.Sid, primaryGroup.PrimaryGroup}, nil
  334. }
  335. // IsCurrentProcessElevated returns true when the current process is
  336. // running with an elevated token, implying Administrator access.
  337. func IsCurrentProcessElevated() bool {
  338. token, err := windows.OpenCurrentProcessToken()
  339. if err != nil {
  340. return false
  341. }
  342. defer token.Close()
  343. return token.IsElevated()
  344. }
  345. // keyOpenTimeout is how long we wait for a registry key to appear. For some
  346. // reason, registry keys tied to ephemeral interfaces can take a long while to
  347. // appear after interface creation, and we can end up racing with that.
  348. const keyOpenTimeout = 20 * time.Second
  349. // RegistryPath represents a path inside a root registry.Key.
  350. type RegistryPath string
  351. // RegistryPathPrefix specifies a RegistryPath prefix that must be suffixed with
  352. // another RegistryPath to make a valid RegistryPath.
  353. type RegistryPathPrefix string
  354. // WithSuffix returns a RegistryPath with the given suffix appended.
  355. func (p RegistryPathPrefix) WithSuffix(suf string) RegistryPath {
  356. return RegistryPath(string(p) + suf)
  357. }
  358. const (
  359. IPv4TCPIPBase RegistryPath = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`
  360. IPv6TCPIPBase RegistryPath = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters`
  361. NetBTBase RegistryPath = `SYSTEM\CurrentControlSet\Services\NetBT\Parameters`
  362. IPv4TCPIPInterfacePrefix RegistryPathPrefix = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\`
  363. IPv6TCPIPInterfacePrefix RegistryPathPrefix = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\`
  364. NetBTInterfacePrefix RegistryPathPrefix = `SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces\Tcpip_`
  365. )
  366. // ErrKeyWaitTimeout is returned by OpenKeyWait when calls timeout.
  367. var ErrKeyWaitTimeout = errors.New("timeout waiting for registry key")
  368. // OpenKeyWait opens a registry key, waiting for it to appear if necessary. It
  369. // returns the opened key, or ErrKeyWaitTimeout if the key does not appear
  370. // within 20s. The caller must call Close on the returned key.
  371. func OpenKeyWait(k registry.Key, path RegistryPath, access uint32) (registry.Key, error) {
  372. runtime.LockOSThread()
  373. defer runtime.UnlockOSThread()
  374. deadline := time.Now().Add(keyOpenTimeout)
  375. pathSpl := strings.Split(string(path), "\\")
  376. for i := 0; ; i++ {
  377. keyName := pathSpl[i]
  378. isLast := i+1 == len(pathSpl)
  379. event, err := windows.CreateEvent(nil, 0, 0, nil)
  380. if err != nil {
  381. return 0, fmt.Errorf("windows.CreateEvent: %w", err)
  382. }
  383. defer windows.CloseHandle(event)
  384. var key registry.Key
  385. for {
  386. err = windows.RegNotifyChangeKeyValue(windows.Handle(k), false, windows.REG_NOTIFY_CHANGE_NAME, event, true)
  387. if err != nil {
  388. return 0, fmt.Errorf("windows.RegNotifyChangeKeyValue: %w", err)
  389. }
  390. var accessFlags uint32
  391. if isLast {
  392. accessFlags = access
  393. } else {
  394. accessFlags = registry.NOTIFY
  395. }
  396. key, err = registry.OpenKey(k, keyName, accessFlags)
  397. if err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND {
  398. timeout := time.Until(deadline) / time.Millisecond
  399. if timeout < 0 {
  400. timeout = 0
  401. }
  402. s, err := windows.WaitForSingleObject(event, uint32(timeout))
  403. if err != nil {
  404. return 0, fmt.Errorf("windows.WaitForSingleObject: %w", err)
  405. }
  406. if s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows
  407. return 0, ErrKeyWaitTimeout
  408. }
  409. } else if err != nil {
  410. return 0, fmt.Errorf("registry.OpenKey(%v): %w", path, err)
  411. } else {
  412. if isLast {
  413. return key, nil
  414. }
  415. defer key.Close()
  416. break
  417. }
  418. }
  419. k = key
  420. }
  421. }
  422. func lookupPseudoUser(uid string) (*user.User, error) {
  423. sid, err := windows.StringToSid(uid)
  424. if err != nil {
  425. return nil, err
  426. }
  427. // We're looking for SIDs "S-1-5-x" where 17 <= x <= 20.
  428. // This is checking for the the "5"
  429. if sid.IdentifierAuthority() != windows.SECURITY_NT_AUTHORITY {
  430. return nil, fmt.Errorf(`SID %q does not use "NT AUTHORITY"`, uid)
  431. }
  432. // This is ensuring that there is only one sub-authority.
  433. // In other words, only one value after the "5".
  434. if sid.SubAuthorityCount() != 1 {
  435. return nil, fmt.Errorf("SID %q should have only one subauthority", uid)
  436. }
  437. // Get that sub-authority value (this is "x" above) and check it.
  438. rid := sid.SubAuthority(0)
  439. if rid < 17 || rid > 20 {
  440. return nil, fmt.Errorf("SID %q does not represent a known pseudo-user", uid)
  441. }
  442. // We've got one of the known pseudo-users. Look up the localized name of the
  443. // account.
  444. username, domain, _, err := sid.LookupAccount("")
  445. if err != nil {
  446. return nil, err
  447. }
  448. // This call is best-effort. If it fails, homeDir will be empty.
  449. homeDir, _ := findHomeDirInRegistry(uid)
  450. result := &user.User{
  451. Uid: uid,
  452. Gid: uid, // Gid == Uid with these accounts.
  453. Username: fmt.Sprintf(`%s\%s`, domain, username),
  454. Name: username,
  455. HomeDir: homeDir,
  456. }
  457. return result, nil
  458. }
  459. // findHomeDirInRegistry finds the user home path based on the uid.
  460. // This is borrowed from Go's std lib.
  461. func findHomeDirInRegistry(uid string) (dir string, err error) {
  462. k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\`+uid, registry.QUERY_VALUE)
  463. if err != nil {
  464. return "", err
  465. }
  466. defer k.Close()
  467. dir, _, err = k.GetStringValue("ProfileImagePath")
  468. if err != nil {
  469. return "", err
  470. }
  471. return dir, nil
  472. }