mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
util/winutil: migrate corp's winutil into OSS.
It makes the most sense to have all our utility functions reside in one place. There was nothing in corp that could not reasonably live in OSS. I also updated `StartProcessAsChild` to no longer depend on `futureexec`, thus reducing the amount of code that needed migration. I tested this change with `tswin` and it is working correctly. I have a follow-up PR to remove the corresponding code from corp. The migrated code was mostly written by @alexbrainman. Sourced from corp revision 03e90cfcc4dd7b8bc9b25eb13a26ec3a24ae0ef9 Signed-off-by: Aaron Klotz <aaron@tailscale.com>
This commit is contained in:
parent
39d173e5fc
commit
82cd98609f
@ -5,7 +5,11 @@
|
|||||||
package winutil
|
package winutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
@ -17,16 +21,25 @@
|
|||||||
regPolicyBase = `SOFTWARE\Policies\Tailscale`
|
regPolicyBase = `SOFTWARE\Policies\Tailscale`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrNoShell is returned when the shell process is not found.
|
||||||
|
var ErrNoShell = errors.New("no Shell process is present")
|
||||||
|
|
||||||
// GetDesktopPID searches the PID of the process that's running the
|
// GetDesktopPID searches the PID of the process that's running the
|
||||||
// currently active desktop and whether it was found.
|
// currently active desktop. Returns ErrNoShell if the shell is not present.
|
||||||
// Usually the PID will be for explorer.exe.
|
// Usually the PID will be for explorer.exe.
|
||||||
func GetDesktopPID() (pid uint32, ok bool) {
|
func GetDesktopPID() (uint32, error) {
|
||||||
hwnd := windows.GetShellWindow()
|
hwnd := windows.GetShellWindow()
|
||||||
if hwnd == 0 {
|
if hwnd == 0 {
|
||||||
return 0, false
|
return 0, ErrNoShell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pid uint32
|
||||||
windows.GetWindowThreadProcessId(hwnd, &pid)
|
windows.GetWindowThreadProcessId(hwnd, &pid)
|
||||||
return pid, pid != 0
|
if pid == 0 {
|
||||||
|
return 0, fmt.Errorf("invalid PID for HWND %v", hwnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPolicyString(name, defval string) string {
|
func getPolicyString(name, defval string) string {
|
||||||
@ -130,3 +143,114 @@ func isSIDValidPrincipal(uid string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnableCurrentThreadPrivilege enables the named privilege
|
||||||
|
// in the current thread access token.
|
||||||
|
func EnableCurrentThreadPrivilege(name string) error {
|
||||||
|
var t windows.Token
|
||||||
|
err := windows.OpenThreadToken(windows.CurrentThread(),
|
||||||
|
windows.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer t.Close()
|
||||||
|
|
||||||
|
var tp windows.Tokenprivileges
|
||||||
|
|
||||||
|
privStr, err := syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = windows.LookupPrivilegeValue(nil, privStr, &tp.Privileges[0].Luid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tp.PrivilegeCount = 1
|
||||||
|
tp.Privileges[0].Attributes = windows.SE_PRIVILEGE_ENABLED
|
||||||
|
return windows.AdjustTokenPrivileges(t, false, &tp, 0, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartProcessAsChild starts exePath process as a child of parentPID.
|
||||||
|
// StartProcessAsChild copies parentPID's environment variables into
|
||||||
|
// the new process, along with any optional environment variables in extraEnv.
|
||||||
|
func StartProcessAsChild(parentPID uint32, exePath string, extraEnv []string) error {
|
||||||
|
// The rest of this function requires SeDebugPrivilege to be held.
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
err := windows.ImpersonateSelf(windows.SecurityImpersonation)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer windows.RevertToSelf()
|
||||||
|
|
||||||
|
// According to https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
|
||||||
|
//
|
||||||
|
// ... To open a handle to another process and obtain full access rights,
|
||||||
|
// you must enable the SeDebugPrivilege privilege. ...
|
||||||
|
//
|
||||||
|
// But we only need PROCESS_CREATE_PROCESS. So perhaps SeDebugPrivilege is too much.
|
||||||
|
//
|
||||||
|
// https://devblogs.microsoft.com/oldnewthing/20080314-00/?p=23113
|
||||||
|
//
|
||||||
|
// TODO: try look for something less than SeDebugPrivilege
|
||||||
|
|
||||||
|
err = EnableCurrentThreadPrivilege("SeDebugPrivilege")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ph, err := windows.OpenProcess(
|
||||||
|
windows.PROCESS_CREATE_PROCESS|windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_DUP_HANDLE,
|
||||||
|
false, parentPID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer windows.CloseHandle(ph)
|
||||||
|
|
||||||
|
var pt windows.Token
|
||||||
|
err = windows.OpenProcessToken(ph, windows.TOKEN_QUERY, &pt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer pt.Close()
|
||||||
|
|
||||||
|
env, err := pt.Environ(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
env = append(env, extraEnv...)
|
||||||
|
|
||||||
|
sys := &syscall.SysProcAttr{ParentProcess: syscall.Handle(ph)}
|
||||||
|
|
||||||
|
cmd := exec.Command(exePath)
|
||||||
|
cmd.Env = env
|
||||||
|
cmd.SysProcAttr = sys
|
||||||
|
|
||||||
|
return cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartProcessAsCurrentGUIUser is like StartProcessAsChild, but if finds
|
||||||
|
// current logged in user desktop process (normally explorer.exe),
|
||||||
|
// and passes found PID to StartProcessAsChild.
|
||||||
|
func StartProcessAsCurrentGUIUser(exePath string, extraEnv []string) error {
|
||||||
|
// as described in https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443
|
||||||
|
desktop, err := GetDesktopPID()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find desktop: %v", err)
|
||||||
|
}
|
||||||
|
err = StartProcessAsChild(desktop, exePath, extraEnv)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start executable: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAppMutex creates a named Windows mutex, returning nil if the mutex
|
||||||
|
// is created successfully or an error if the mutex already exists or could not
|
||||||
|
// be created for some other reason.
|
||||||
|
func CreateAppMutex(name string) (windows.Handle, error) {
|
||||||
|
return windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(name))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user