mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-08 05:36:38 +02:00
578 lines
15 KiB
Go
578 lines
15 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package winutil
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// 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 {
|
|
return StartProcessWithAttributes(exePath, ProcessAttributeEnvExtra{Slice: extraEnv}, ProcessAttributeParentProcessID(parentPID))
|
|
}
|
|
|
|
func StartProcessWithAttributes(exePath string, attrs ...any) (err error) {
|
|
var desktop string
|
|
var parentPID uint32
|
|
var mitigationBits uint64
|
|
var inheritableHandleList ProcessAttributeExplicitInheritableHandleList
|
|
var useStdHandles bool
|
|
var useToken windows.Token
|
|
var wd string
|
|
var procSA *windows.SecurityAttributes
|
|
var threadSA *windows.SecurityAttributes
|
|
var envExtra ProcessAttributeEnvExtra
|
|
var args []string
|
|
creationFlags := uint32(windows.CREATE_UNICODE_ENVIRONMENT | windows.EXTENDED_STARTUPINFO_PRESENT)
|
|
|
|
for _, attr := range attrs {
|
|
switch v := attr.(type) {
|
|
case ProcessAttributeExplicitInheritableHandleList:
|
|
inheritableHandleList, useStdHandles, err = v.filtered()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case *ProcessAttributeExplicitInheritableHandleList:
|
|
inheritableHandleList, useStdHandles, err = v.filtered()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case ProcessAttributeParentProcessID:
|
|
parentPID = uint32(v)
|
|
case *ProcessAttributeParentProcessID:
|
|
parentPID = uint32(*v)
|
|
case ProcessMitigationPolicies:
|
|
mitigationBits = v.asMitigationBits()
|
|
case *ProcessMitigationPolicies:
|
|
mitigationBits = v.asMitigationBits()
|
|
case windows.Token:
|
|
useToken = v
|
|
case ProcessAttributeGUIBindInfo:
|
|
desktop = v.String()
|
|
case *ProcessAttributeGUIBindInfo:
|
|
desktop = v.String()
|
|
case ProcessAttributeWorkingDirectory:
|
|
wd = v.String()
|
|
case *ProcessAttributeWorkingDirectory:
|
|
wd = v.String()
|
|
case ProcessAttributeSecurity:
|
|
procSA = v.Process
|
|
threadSA = v.Thread
|
|
case *ProcessAttributeSecurity:
|
|
procSA = v.Process
|
|
threadSA = v.Thread
|
|
case ProcessAttributeEnvExtra:
|
|
envExtra = v
|
|
case *ProcessAttributeEnvExtra:
|
|
envExtra = *v
|
|
case ProcessAttributeArgs:
|
|
args = []string(v)
|
|
case *ProcessAttributeArgs:
|
|
args = []string(*v)
|
|
case ProcessAttributeFlags:
|
|
creationFlags |= v.creationFlags()
|
|
case *ProcessAttributeFlags:
|
|
creationFlags |= v.creationFlags()
|
|
default:
|
|
return os.ErrInvalid
|
|
}
|
|
}
|
|
|
|
var attrCount uint32
|
|
if len(inheritableHandleList.Handles) > 0 {
|
|
attrCount++
|
|
}
|
|
if parentPID != 0 {
|
|
attrCount++
|
|
}
|
|
if mitigationBits != 0 {
|
|
attrCount++
|
|
}
|
|
|
|
var ph windows.Handle
|
|
var env []string
|
|
if parentPID == 0 {
|
|
env = os.Environ()
|
|
} else {
|
|
// 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
|
|
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
|
|
err := windows.ImpersonateSelf(windows.SecurityImpersonation)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer windows.RevertToSelf()
|
|
|
|
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
|
|
if err := windows.OpenProcessToken(ph, windows.TOKEN_QUERY, &pt); err != nil {
|
|
return err
|
|
}
|
|
defer pt.Close()
|
|
|
|
env, err = pt.Environ(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
env16 := envExtra.envBlock(env)
|
|
|
|
var inheritHandles bool
|
|
var attrList *windows.ProcThreadAttributeList
|
|
if attrCount > 0 {
|
|
attrListContainer, err := windows.NewProcThreadAttributeList(attrCount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer attrListContainer.Delete()
|
|
|
|
if ph != 0 {
|
|
attrListContainer.Update(windows.PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, unsafe.Pointer(&ph), unsafe.Sizeof(ph))
|
|
}
|
|
|
|
if hll := uintptr(len(inheritableHandleList.Handles)); hll > 0 {
|
|
attrListContainer.Update(windows.PROC_THREAD_ATTRIBUTE_HANDLE_LIST, unsafe.Pointer(&inheritableHandleList.Handles[0]), hll*unsafe.Sizeof(windows.Handle(0)))
|
|
inheritHandles = true
|
|
}
|
|
|
|
if mitigationBits != 0 {
|
|
attrListContainer.Update(windows.PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, unsafe.Pointer(&mitigationBits), unsafe.Sizeof(mitigationBits))
|
|
}
|
|
|
|
attrList = attrListContainer.List()
|
|
}
|
|
|
|
var desktop16 *uint16
|
|
if desktop != "" {
|
|
desktop16, err = windows.UTF16PtrFromString(desktop)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var startupInfoFlags uint32
|
|
if useStdHandles {
|
|
startupInfoFlags |= windows.STARTF_USESTDHANDLES
|
|
}
|
|
|
|
siex := windows.StartupInfoEx{
|
|
StartupInfo: windows.StartupInfo{
|
|
Cb: uint32(unsafe.Sizeof(windows.StartupInfoEx{})),
|
|
Desktop: desktop16,
|
|
Flags: startupInfoFlags,
|
|
StdInput: inheritableHandleList.Stdin,
|
|
StdOutput: inheritableHandleList.Stdout,
|
|
StdErr: inheritableHandleList.Stderr,
|
|
},
|
|
ProcThreadAttributeList: attrList,
|
|
}
|
|
|
|
var wd16 *uint16
|
|
if wd != "" {
|
|
wd16, err = windows.UTF16PtrFromString(wd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
exePath16, err := windows.UTF16PtrFromString(exePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmdLine, err := makeCmdLine(exePath, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var pi windows.ProcessInformation
|
|
if useToken == 0 {
|
|
err = windows.CreateProcess(exePath16, cmdLine, procSA, threadSA, inheritHandles, creationFlags, env16, wd16, &siex.StartupInfo, &pi)
|
|
} else {
|
|
err = windows.CreateProcessAsUser(useToken, exePath16, cmdLine, procSA, threadSA, inheritHandles, creationFlags, env16, wd16, &siex.StartupInfo, &pi)
|
|
}
|
|
|
|
runtime.KeepAlive(siex)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer windows.CloseHandle(pi.Thread)
|
|
defer windows.CloseHandle(pi.Process)
|
|
|
|
return err
|
|
}
|
|
|
|
func makeCmdLine(exePath string, args []string) (*uint16, error) {
|
|
var buf strings.Builder
|
|
|
|
buf.WriteString(windows.EscapeArg(exePath))
|
|
|
|
for _, arg := range args {
|
|
if buf.Len() > 0 {
|
|
buf.WriteByte(' ')
|
|
}
|
|
buf.WriteString(windows.EscapeArg(arg))
|
|
}
|
|
|
|
return windows.UTF16PtrFromString(buf.String())
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
type ProcessAttributeEnvExtra struct {
|
|
Map map[string]string
|
|
Slice []string
|
|
}
|
|
|
|
func (ee *ProcessAttributeEnvExtra) envBlock(env []string) *uint16 {
|
|
var buf bytes.Buffer
|
|
|
|
for _, s := range [][]string{env, ee.Slice} {
|
|
for _, v := range s {
|
|
buf.WriteString(v)
|
|
buf.WriteByte(0)
|
|
}
|
|
}
|
|
|
|
for k, v := range ee.Map {
|
|
buf.WriteString(k)
|
|
buf.WriteByte('=')
|
|
buf.WriteString(v)
|
|
buf.WriteByte(0)
|
|
}
|
|
|
|
if buf.Len() == 0 {
|
|
// So that we end with a double-null in the empty env case (unlikely)
|
|
buf.WriteByte(0)
|
|
}
|
|
|
|
buf.WriteByte(0)
|
|
|
|
return &utf16.Encode([]rune(string(buf.Bytes())))[0]
|
|
}
|
|
|
|
type ProcessAttributeFlags struct {
|
|
BreakawayFromJob bool
|
|
CreateNewConsole bool
|
|
CreateNewProcessGroup bool
|
|
Detached bool
|
|
InheritParentAffinity bool
|
|
NoConsoleWindow bool
|
|
}
|
|
|
|
func (paf *ProcessAttributeFlags) creationFlags() (result uint32) {
|
|
if paf.BreakawayFromJob {
|
|
result |= windows.CREATE_BREAKAWAY_FROM_JOB
|
|
}
|
|
if paf.CreateNewConsole {
|
|
result |= windows.CREATE_NEW_CONSOLE
|
|
}
|
|
if paf.CreateNewProcessGroup {
|
|
result |= windows.CREATE_NEW_PROCESS_GROUP
|
|
}
|
|
if paf.Detached {
|
|
result |= windows.DETACHED_PROCESS
|
|
}
|
|
if paf.InheritParentAffinity {
|
|
result |= windows.INHERIT_PARENT_AFFINITY
|
|
}
|
|
if paf.NoConsoleWindow {
|
|
result |= windows.CREATE_NO_WINDOW
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
type ProcessAttributeArgs []string
|
|
|
|
type ProcessAttributeSecurity struct {
|
|
Process *windows.SecurityAttributes
|
|
Thread *windows.SecurityAttributes
|
|
}
|
|
|
|
type ProcessAttributeWorkingDirectory string
|
|
|
|
func (wd *ProcessAttributeWorkingDirectory) String() string {
|
|
return string(*wd)
|
|
}
|
|
|
|
type ProcessAttributeGUIBindInfo struct {
|
|
WindowStation string
|
|
Desktop string
|
|
}
|
|
|
|
func (gbi *ProcessAttributeGUIBindInfo) String() string {
|
|
winsta := gbi.WindowStation
|
|
if winsta == "" {
|
|
winsta = "Winsta0"
|
|
}
|
|
|
|
desktop := gbi.Desktop
|
|
if desktop == "" {
|
|
desktop = "default"
|
|
}
|
|
|
|
var buf strings.Builder
|
|
buf.WriteString(winsta)
|
|
buf.WriteByte('\\')
|
|
buf.WriteString(desktop)
|
|
return buf.String()
|
|
}
|
|
|
|
type ProcessAttributeParentProcessID uint32
|
|
|
|
type ProcessAttributeExplicitInheritableHandleList struct {
|
|
Stdin windows.Handle
|
|
Stdout windows.Handle
|
|
Stderr windows.Handle
|
|
Handles []windows.Handle
|
|
}
|
|
|
|
func (eihl *ProcessAttributeExplicitInheritableHandleList) filtered() (result ProcessAttributeExplicitInheritableHandleList, containsStd bool, err error) {
|
|
result = ProcessAttributeExplicitInheritableHandleList{
|
|
Stdin: eihl.Stdin,
|
|
Stdout: eihl.Stdout,
|
|
Stderr: eihl.Stderr,
|
|
Handles: make([]windows.Handle, 0, len(eihl.Handles)+3),
|
|
}
|
|
|
|
handles := make([]windows.Handle, 0, len(eihl.Handles)+3)
|
|
|
|
if result.Stdin == 0 {
|
|
result.Stdin = windows.Stdin
|
|
}
|
|
handles = append(handles, result.Stdin)
|
|
|
|
if result.Stdout == 0 {
|
|
result.Stdout = windows.Stdout
|
|
}
|
|
handles = append(handles, result.Stdout)
|
|
|
|
if result.Stderr == 0 {
|
|
result.Stderr = windows.Stderr
|
|
}
|
|
handles = append(handles, result.Stderr)
|
|
|
|
handles = append(handles, eihl.Handles...)
|
|
|
|
for i, h := range handles {
|
|
fileType, err := windows.GetFileType(h)
|
|
if err != nil {
|
|
return result, false, err
|
|
}
|
|
if fileType != windows.FILE_TYPE_DISK && fileType != windows.FILE_TYPE_PIPE {
|
|
continue
|
|
}
|
|
|
|
if err := windows.SetHandleInformation(h, windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT); err != nil {
|
|
return result, false, err
|
|
}
|
|
|
|
result.Handles = append(result.Handles, h)
|
|
if i < 3 {
|
|
// Standard handle
|
|
containsStd = true
|
|
}
|
|
}
|
|
|
|
return result, containsStd, nil
|
|
}
|
|
|
|
type _PROCESS_MITIGATION_POLICY int32
|
|
|
|
const (
|
|
processDEPPolicy _PROCESS_MITIGATION_POLICY = 0
|
|
processASLRPolicy _PROCESS_MITIGATION_POLICY = 1
|
|
processDynamicCodePolicy _PROCESS_MITIGATION_POLICY = 2
|
|
processStrictHandleCheckPolicy _PROCESS_MITIGATION_POLICY = 3
|
|
processSystemCallDisablePolicy _PROCESS_MITIGATION_POLICY = 4
|
|
processMitigationOptionsMask _PROCESS_MITIGATION_POLICY = 5
|
|
processExtensionPointDisablePolicy _PROCESS_MITIGATION_POLICY = 6
|
|
processControlFlowGuardPolicy _PROCESS_MITIGATION_POLICY = 7
|
|
processSignaturePolicy _PROCESS_MITIGATION_POLICY = 8
|
|
processFontDisablePolicy _PROCESS_MITIGATION_POLICY = 9
|
|
processImageLoadPolicy _PROCESS_MITIGATION_POLICY = 10
|
|
processSystemCallFilterPolicy _PROCESS_MITIGATION_POLICY = 11
|
|
processPayloadRestrictionPolicy _PROCESS_MITIGATION_POLICY = 12
|
|
processChildProcessPolicy _PROCESS_MITIGATION_POLICY = 13
|
|
processSideChannelIsolationPolicy _PROCESS_MITIGATION_POLICY = 14
|
|
processUserShadowStackPolicy _PROCESS_MITIGATION_POLICY = 15
|
|
processRedirectionTrustPolicy _PROCESS_MITIGATION_POLICY = 16
|
|
processUserPointerAuthPolicy _PROCESS_MITIGATION_POLICY = 17
|
|
processSEHOPPolicy _PROCESS_MITIGATION_POLICY = 18
|
|
)
|
|
|
|
type processMitigationPolicyFlags struct {
|
|
Flags uint32
|
|
}
|
|
|
|
const (
|
|
_NoRemoteImages = 1
|
|
_NoLowMandatoryLabelImages = (1 << 1)
|
|
_PreferSystem32Images = (1 << 2)
|
|
_MicrosoftSignedOnly = 1
|
|
_DisableExtensionPoints = 1
|
|
_ProhibitDynamicCode = 1
|
|
)
|
|
|
|
type ProcessMitigationPolicies struct {
|
|
DisableExtensionPoints bool
|
|
PreferSystem32Images bool
|
|
ProhibitDynamicCode bool
|
|
ProhibitLowMandatoryLabelImages bool
|
|
ProhibitNonMicrosoftSignedDLLs bool
|
|
ProhibitRemoteImages bool
|
|
}
|
|
|
|
func CurrentProcessMitigationPolicies() (result ProcessMitigationPolicies, _ error) {
|
|
var flags processMitigationPolicyFlags
|
|
cp := windows.CurrentProcess()
|
|
|
|
if err := getProcessMitigationPolicy(cp, processExtensionPointDisablePolicy, unsafe.Pointer(&flags), unsafe.Sizeof(flags)); err != nil {
|
|
return result, err
|
|
}
|
|
result.DisableExtensionPoints = flags.Flags&_DisableExtensionPoints != 0
|
|
|
|
if err := getProcessMitigationPolicy(cp, processSystemCallDisablePolicy, unsafe.Pointer(&flags), unsafe.Sizeof(flags)); err != nil {
|
|
return result, err
|
|
}
|
|
result.ProhibitNonMicrosoftSignedDLLs = flags.Flags&_MicrosoftSignedOnly != 0
|
|
|
|
if err := getProcessMitigationPolicy(cp, processDynamicCodePolicy, unsafe.Pointer(&flags), unsafe.Sizeof(flags)); err != nil {
|
|
return result, err
|
|
}
|
|
result.ProhibitDynamicCode = flags.Flags&_ProhibitDynamicCode != 0
|
|
|
|
if err := getProcessMitigationPolicy(cp, processImageLoadPolicy, unsafe.Pointer(&flags), unsafe.Sizeof(flags)); err != nil {
|
|
return result, err
|
|
}
|
|
result.ProhibitRemoteImages = flags.Flags&_NoRemoteImages != 0
|
|
result.ProhibitLowMandatoryLabelImages = flags.Flags&_NoLowMandatoryLabelImages != 0
|
|
result.PreferSystem32Images = flags.Flags&_PreferSystem32Images != 0
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (pmp *ProcessMitigationPolicies) SetOnCurrentProcess() error {
|
|
if pmp.DisableExtensionPoints {
|
|
v := processMitigationPolicyFlags{
|
|
Flags: _DisableExtensionPoints,
|
|
}
|
|
if err := setProcessMitigationPolicy(processExtensionPointDisablePolicy, unsafe.Pointer(&v), unsafe.Sizeof(v)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if pmp.ProhibitNonMicrosoftSignedDLLs {
|
|
v := processMitigationPolicyFlags{
|
|
Flags: _MicrosoftSignedOnly,
|
|
}
|
|
if err := setProcessMitigationPolicy(processSystemCallDisablePolicy, unsafe.Pointer(&v), unsafe.Sizeof(v)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if pmp.ProhibitDynamicCode {
|
|
v := processMitigationPolicyFlags{
|
|
Flags: _ProhibitDynamicCode,
|
|
}
|
|
if err := setProcessMitigationPolicy(processDynamicCodePolicy, unsafe.Pointer(&v), unsafe.Sizeof(v)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var imageLoadFlags uint32
|
|
if pmp.PreferSystem32Images {
|
|
imageLoadFlags |= _PreferSystem32Images
|
|
}
|
|
if pmp.ProhibitLowMandatoryLabelImages {
|
|
imageLoadFlags |= _NoLowMandatoryLabelImages
|
|
}
|
|
if pmp.ProhibitRemoteImages {
|
|
imageLoadFlags |= _NoRemoteImages
|
|
}
|
|
|
|
if imageLoadFlags != 0 {
|
|
v := processMitigationPolicyFlags{
|
|
Flags: imageLoadFlags,
|
|
}
|
|
if err := setProcessMitigationPolicy(processImageLoadPolicy, unsafe.Pointer(&v), unsafe.Sizeof(v)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (pmp *ProcessMitigationPolicies) asMitigationBits() (result uint64) {
|
|
if pmp.DisableExtensionPoints {
|
|
result |= (1 << 32)
|
|
}
|
|
if pmp.PreferSystem32Images {
|
|
result |= (1 << 60)
|
|
}
|
|
if pmp.ProhibitDynamicCode {
|
|
result |= (1 << 36)
|
|
}
|
|
if pmp.ProhibitLowMandatoryLabelImages {
|
|
result |= (1 << 56)
|
|
}
|
|
if pmp.ProhibitNonMicrosoftSignedDLLs {
|
|
result |= (1 << 44)
|
|
}
|
|
if pmp.ProhibitRemoteImages {
|
|
result |= (1 << 52)
|
|
}
|
|
return result
|
|
}
|