golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/elevate/doas.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package elevate
     7  
     8  import (
     9  	"errors"
    10  	"os"
    11  	"runtime"
    12  	"strings"
    13  	"unsafe"
    14  
    15  	"golang.org/x/sys/windows"
    16  	"golang.org/x/sys/windows/svc/mgr"
    17  )
    18  
    19  func setAllEnv(env []string) {
    20  	windows.Clearenv()
    21  	for _, e := range env {
    22  		k, v, ok := strings.Cut(e, "=")
    23  		if !ok {
    24  			continue
    25  		}
    26  		windows.Setenv(k, v)
    27  	}
    28  }
    29  
    30  func DoAsSystem(f func() error) error {
    31  	runtime.LockOSThread()
    32  	defer func() {
    33  		windows.RevertToSelf()
    34  		runtime.UnlockOSThread()
    35  	}()
    36  	privileges := windows.Tokenprivileges{
    37  		PrivilegeCount: 1,
    38  		Privileges: [1]windows.LUIDAndAttributes{
    39  			{
    40  				Attributes: windows.SE_PRIVILEGE_ENABLED,
    41  			},
    42  		},
    43  	}
    44  	err := windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr("SeDebugPrivilege"), &privileges.Privileges[0].Luid)
    45  	if err != nil {
    46  		return err
    47  	}
    48  	err = windows.ImpersonateSelf(windows.SecurityImpersonation)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	var threadToken windows.Token
    53  	err = windows.OpenThreadToken(windows.CurrentThread(), windows.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &threadToken)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	tokenUser, err := threadToken.GetTokenUser()
    58  	if err == nil && tokenUser.User.Sid.IsWellKnown(windows.WinLocalSystemSid) {
    59  		threadToken.Close()
    60  		return f()
    61  	}
    62  	err = windows.AdjustTokenPrivileges(threadToken, false, &privileges, uint32(unsafe.Sizeof(privileges)), nil, nil)
    63  	threadToken.Close()
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	processes, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	processEntry := windows.ProcessEntry32{Size: uint32(unsafe.Sizeof(windows.ProcessEntry32{}))}
    73  	var impersonationError error
    74  	for err = windows.Process32First(processes, &processEntry); err == nil; err = windows.Process32Next(processes, &processEntry) {
    75  		if strings.ToLower(windows.UTF16ToString(processEntry.ExeFile[:])) != "winlogon.exe" {
    76  			continue
    77  		}
    78  		winlogonProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, processEntry.ProcessID)
    79  		if err != nil {
    80  			impersonationError = err
    81  			continue
    82  		}
    83  		var winlogonToken windows.Token
    84  		err = windows.OpenProcessToken(winlogonProcess, windows.TOKEN_QUERY|windows.TOKEN_IMPERSONATE|windows.TOKEN_DUPLICATE, &winlogonToken)
    85  		windows.CloseHandle(winlogonProcess)
    86  		if err != nil {
    87  			continue
    88  		}
    89  		tokenUser, err := winlogonToken.GetTokenUser()
    90  		if err != nil || !tokenUser.User.Sid.IsWellKnown(windows.WinLocalSystemSid) {
    91  			winlogonToken.Close()
    92  			continue
    93  		}
    94  		windows.CloseHandle(processes)
    95  
    96  		var duplicatedToken windows.Token
    97  		err = windows.DuplicateTokenEx(winlogonToken, 0, nil, windows.SecurityImpersonation, windows.TokenImpersonation, &duplicatedToken)
    98  		windows.CloseHandle(winlogonProcess)
    99  		if err != nil {
   100  			return err
   101  		}
   102  		newEnv, err := duplicatedToken.Environ(false)
   103  		if err != nil {
   104  			duplicatedToken.Close()
   105  			return err
   106  		}
   107  		currentEnv := os.Environ()
   108  		err = windows.SetThreadToken(nil, duplicatedToken)
   109  		duplicatedToken.Close()
   110  		if err != nil {
   111  			return err
   112  		}
   113  		setAllEnv(newEnv)
   114  		err = f()
   115  		setAllEnv(currentEnv)
   116  		return err
   117  	}
   118  	windows.CloseHandle(processes)
   119  	if impersonationError != nil {
   120  		return impersonationError
   121  	}
   122  	return errors.New("unable to find winlogon.exe process")
   123  }
   124  
   125  func DoAsService(serviceName string, f func() error) error {
   126  	scm, err := mgr.Connect()
   127  	if err != nil {
   128  		return err
   129  	}
   130  	service, err := scm.OpenService(serviceName)
   131  	scm.Disconnect()
   132  	if err != nil {
   133  		return err
   134  	}
   135  	status, err := service.Query()
   136  	service.Close()
   137  	if err != nil {
   138  		return err
   139  	}
   140  	if status.ProcessId == 0 {
   141  		return errors.New("service is not running")
   142  	}
   143  	return DoAsSystem(func() error {
   144  		serviceProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, status.ProcessId)
   145  		if err != nil {
   146  			return err
   147  		}
   148  		var serviceToken windows.Token
   149  		err = windows.OpenProcessToken(serviceProcess, windows.TOKEN_IMPERSONATE|windows.TOKEN_DUPLICATE, &serviceToken)
   150  		windows.CloseHandle(serviceProcess)
   151  		if err != nil {
   152  			return err
   153  		}
   154  		var duplicatedToken windows.Token
   155  		err = windows.DuplicateTokenEx(serviceToken, 0, nil, windows.SecurityImpersonation, windows.TokenImpersonation, &duplicatedToken)
   156  		serviceToken.Close()
   157  		if err != nil {
   158  			return err
   159  		}
   160  		newEnv, err := duplicatedToken.Environ(false)
   161  		if err != nil {
   162  			duplicatedToken.Close()
   163  			return err
   164  		}
   165  		currentEnv := os.Environ()
   166  		err = windows.SetThreadToken(nil, duplicatedToken)
   167  		duplicatedToken.Close()
   168  		if err != nil {
   169  			return err
   170  		}
   171  		setAllEnv(newEnv)
   172  		err = f()
   173  		setAllEnv(currentEnv)
   174  		return err
   175  	})
   176  }