github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/machine/wsl/util_windows.go (about)

     1  package wsl
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"syscall"
    11  	"unicode/utf16"
    12  	"unsafe"
    13  
    14  	"github.com/pkg/errors"
    15  	"github.com/sirupsen/logrus"
    16  	"golang.org/x/sys/windows"
    17  	"golang.org/x/sys/windows/registry"
    18  
    19  	"github.com/containers/storage/pkg/homedir"
    20  )
    21  
    22  //nolint
    23  type SHELLEXECUTEINFO struct {
    24  	cbSize         uint32
    25  	fMask          uint32
    26  	hwnd           syscall.Handle
    27  	lpVerb         uintptr
    28  	lpFile         uintptr
    29  	lpParameters   uintptr
    30  	lpDirectory    uintptr
    31  	nShow          int
    32  	hInstApp       syscall.Handle
    33  	lpIDList       uintptr
    34  	lpClass        uintptr
    35  	hkeyClass      syscall.Handle
    36  	dwHotKey       uint32
    37  	hIconOrMonitor syscall.Handle
    38  	hProcess       syscall.Handle
    39  }
    40  
    41  //nolint
    42  type Luid struct {
    43  	lowPart  uint32
    44  	highPart int32
    45  }
    46  
    47  type LuidAndAttributes struct {
    48  	luid       Luid
    49  	attributes uint32
    50  }
    51  
    52  type TokenPrivileges struct {
    53  	privilegeCount uint32
    54  	privileges     [1]LuidAndAttributes
    55  }
    56  
    57  //nolint // Cleaner to refer to the official OS constant names, and consistent with syscall
    58  const (
    59  	SEE_MASK_NOCLOSEPROCESS         = 0x40
    60  	EWX_FORCEIFHUNG                 = 0x10
    61  	EWX_REBOOT                      = 0x02
    62  	EWX_RESTARTAPPS                 = 0x40
    63  	SHTDN_REASON_MAJOR_APPLICATION  = 0x00040000
    64  	SHTDN_REASON_MINOR_INSTALLATION = 0x00000002
    65  	SHTDN_REASON_FLAG_PLANNED       = 0x80000000
    66  	TOKEN_ADJUST_PRIVILEGES         = 0x0020
    67  	TOKEN_QUERY                     = 0x0008
    68  	SE_PRIVILEGE_ENABLED            = 0x00000002
    69  	SE_ERR_ACCESSDENIED             = 0x05
    70  	WM_QUIT                         = 0x12
    71  )
    72  
    73  func winVersionAtLeast(major uint, minor uint, build uint) bool {
    74  	var out [3]uint32
    75  
    76  	in := []uint32{uint32(major), uint32(minor), uint32(build)}
    77  	out[0], out[1], out[2] = windows.RtlGetNtVersionNumbers()
    78  
    79  	for i, o := range out {
    80  		if in[i] > o {
    81  			return false
    82  		}
    83  		if in[i] < o {
    84  			return true
    85  		}
    86  	}
    87  
    88  	return true
    89  }
    90  
    91  func hasAdminRights() bool {
    92  	var sid *windows.SID
    93  
    94  	// See: https://coolaj86.com/articles/golang-and-windows-and-admins-oh-my/
    95  	if err := windows.AllocateAndInitializeSid(
    96  		&windows.SECURITY_NT_AUTHORITY,
    97  		2,
    98  		windows.SECURITY_BUILTIN_DOMAIN_RID,
    99  		windows.DOMAIN_ALIAS_RID_ADMINS,
   100  		0, 0, 0, 0, 0, 0,
   101  		&sid); err != nil {
   102  		logrus.Warnf("SID allocation error: %s", err)
   103  		return false
   104  	}
   105  	defer windows.FreeSid(sid)
   106  
   107  	//  From MS docs:
   108  	// "If TokenHandle is NULL, CheckTokenMembership uses the impersonation
   109  	//  token of the calling thread. If the thread is not impersonating,
   110  	//  the function duplicates the thread's primary token to create an
   111  	//  impersonation token."
   112  	token := windows.Token(0)
   113  
   114  	member, err := token.IsMember(sid)
   115  	if err != nil {
   116  		logrus.Warnf("Token Membership Error: %s", err)
   117  		return false
   118  	}
   119  
   120  	return member || token.IsElevated()
   121  }
   122  
   123  func relaunchElevatedWait() error {
   124  	e, _ := os.Executable()
   125  	d, _ := os.Getwd()
   126  	exe, _ := syscall.UTF16PtrFromString(e)
   127  	cwd, _ := syscall.UTF16PtrFromString(d)
   128  	arg, _ := syscall.UTF16PtrFromString(buildCommandArgs(true))
   129  	verb, _ := syscall.UTF16PtrFromString("runas")
   130  
   131  	shell32 := syscall.NewLazyDLL("shell32.dll")
   132  
   133  	info := &SHELLEXECUTEINFO{
   134  		fMask:        SEE_MASK_NOCLOSEPROCESS,
   135  		hwnd:         0,
   136  		lpVerb:       uintptr(unsafe.Pointer(verb)),
   137  		lpFile:       uintptr(unsafe.Pointer(exe)),
   138  		lpParameters: uintptr(unsafe.Pointer(arg)),
   139  		lpDirectory:  uintptr(unsafe.Pointer(cwd)),
   140  		nShow:        1,
   141  	}
   142  	info.cbSize = uint32(unsafe.Sizeof(*info))
   143  	procShellExecuteEx := shell32.NewProc("ShellExecuteExW")
   144  	if ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(info))); ret == 0 { // 0 = False
   145  		err := syscall.GetLastError()
   146  		if info.hInstApp == SE_ERR_ACCESSDENIED {
   147  			return wrapMaybe(err, "request to elevate privileges was denied")
   148  		}
   149  		return wrapMaybef(err, "could not launch process, ShellEX Error = %d", info.hInstApp)
   150  	}
   151  
   152  	handle := syscall.Handle(info.hProcess)
   153  	defer syscall.CloseHandle(handle)
   154  
   155  	w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
   156  	switch w {
   157  	case syscall.WAIT_OBJECT_0:
   158  		break
   159  	case syscall.WAIT_FAILED:
   160  		return errors.Wrap(err, "could not wait for process, failed")
   161  	default:
   162  		return errors.Errorf("could not wait for process, unknown error")
   163  	}
   164  	var code uint32
   165  	if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
   166  		return err
   167  	}
   168  	if code != 0 {
   169  		return &ExitCodeError{uint(code)}
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func wrapMaybe(err error, message string) error {
   176  	if err != nil {
   177  		return errors.Wrap(err, message)
   178  	}
   179  
   180  	return errors.New(message)
   181  }
   182  
   183  func wrapMaybef(err error, format string, args ...interface{}) error {
   184  	if err != nil {
   185  		return errors.Wrapf(err, format, args...)
   186  	}
   187  
   188  	return errors.Errorf(format, args...)
   189  }
   190  
   191  func reboot() error {
   192  	const (
   193  		wtLocation   = `Microsoft\WindowsApps\wt.exe`
   194  		wtPrefix     = `%LocalAppData%\Microsoft\WindowsApps\wt -p "Windows PowerShell" `
   195  		localAppData = "LocalAppData"
   196  		pShellLaunch = `powershell -noexit "powershell -EncodedCommand (Get-Content '%s')"`
   197  	)
   198  
   199  	exe, _ := os.Executable()
   200  	relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), buildCommandArgs(false))
   201  	encoded := base64.StdEncoding.EncodeToString(encodeUTF16Bytes(relaunch))
   202  
   203  	dataDir, err := homedir.GetDataHome()
   204  	if err != nil {
   205  		return errors.Wrap(err, "could not determine data directory")
   206  	}
   207  	if err := os.MkdirAll(dataDir, 0755); err != nil {
   208  		return errors.Wrap(err, "could not create data directory")
   209  	}
   210  	commFile := filepath.Join(dataDir, "podman-relaunch.dat")
   211  	if err := ioutil.WriteFile(commFile, []byte(encoded), 0600); err != nil {
   212  		return errors.Wrap(err, "could not serialize command state")
   213  	}
   214  
   215  	command := fmt.Sprintf(pShellLaunch, commFile)
   216  	if _, err := os.Lstat(filepath.Join(os.Getenv(localAppData), wtLocation)); err == nil {
   217  		wtCommand := wtPrefix + command
   218  		// RunOnce is limited to 260 chars (supposedly no longer in Builds >= 19489)
   219  		// For now fallbacak in cases of long usernames (>89 chars)
   220  		if len(wtCommand) < 260 {
   221  			command = wtCommand
   222  		}
   223  	}
   224  
   225  	if err := addRunOnceRegistryEntry(command); err != nil {
   226  		return err
   227  	}
   228  
   229  	if err := obtainShutdownPrivilege(); err != nil {
   230  		return err
   231  	}
   232  
   233  	message := "To continue the process of enabling WSL, the system needs to reboot. " +
   234  		"Alternatively, you can cancel and reboot manually\n\n" +
   235  		"After rebooting, please wait a minute or two for podman machine to relaunch and continue installing."
   236  
   237  	if MessageBox(message, "Podman Machine", false) != 1 {
   238  		fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
   239  		os.Exit(ErrorSuccessRebootRequired)
   240  		return nil
   241  	}
   242  
   243  	user32 := syscall.NewLazyDLL("user32")
   244  	procExit := user32.NewProc("ExitWindowsEx")
   245  	if ret, _, err := procExit.Call(EWX_REBOOT|EWX_RESTARTAPPS|EWX_FORCEIFHUNG,
   246  		SHTDN_REASON_MAJOR_APPLICATION|SHTDN_REASON_MINOR_INSTALLATION|SHTDN_REASON_FLAG_PLANNED); ret != 1 {
   247  		return errors.Wrap(err, "reboot failed")
   248  	}
   249  
   250  	return nil
   251  }
   252  
   253  func obtainShutdownPrivilege() error {
   254  	const SeShutdownName = "SeShutdownPrivilege"
   255  
   256  	advapi32 := syscall.NewLazyDLL("advapi32")
   257  	OpenProcessToken := advapi32.NewProc("OpenProcessToken")
   258  	LookupPrivilegeValue := advapi32.NewProc("LookupPrivilegeValueW")
   259  	AdjustTokenPrivileges := advapi32.NewProc("AdjustTokenPrivileges")
   260  
   261  	proc, _ := syscall.GetCurrentProcess()
   262  
   263  	var hToken uintptr
   264  	if ret, _, err := OpenProcessToken.Call(uintptr(proc), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, uintptr(unsafe.Pointer(&hToken))); ret != 1 {
   265  		return errors.Wrap(err, "error opening process token")
   266  	}
   267  
   268  	var privs TokenPrivileges
   269  	if ret, _, err := LookupPrivilegeValue.Call(uintptr(0), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(SeShutdownName))), uintptr(unsafe.Pointer(&(privs.privileges[0].luid)))); ret != 1 {
   270  		return errors.Wrap(err, "error looking up shutdown privilege")
   271  	}
   272  
   273  	privs.privilegeCount = 1
   274  	privs.privileges[0].attributes = SE_PRIVILEGE_ENABLED
   275  
   276  	if ret, _, err := AdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&privs)), 0, uintptr(0), 0); ret != 1 {
   277  		return errors.Wrap(err, "error enabling shutdown privilege on token")
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  func getProcessState(pid int) (active bool, exitCode int) {
   284  	const da = syscall.STANDARD_RIGHTS_READ | syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
   285  	handle, err := syscall.OpenProcess(da, false, uint32(pid))
   286  	if err != nil {
   287  		return false, int(syscall.ERROR_PROC_NOT_FOUND)
   288  	}
   289  
   290  	var code uint32
   291  	syscall.GetExitCodeProcess(handle, &code)
   292  	return code == 259, int(code)
   293  }
   294  
   295  func addRunOnceRegistryEntry(command string) error {
   296  	k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE)
   297  	if err != nil {
   298  		return errors.Wrap(err, "could not open RunOnce registry entry")
   299  	}
   300  
   301  	defer k.Close()
   302  
   303  	if err := k.SetExpandStringValue("podman-machine", command); err != nil {
   304  		return errors.Wrap(err, "could not open RunOnce registry entry")
   305  	}
   306  
   307  	return nil
   308  }
   309  
   310  func encodeUTF16Bytes(s string) []byte {
   311  	u16 := utf16.Encode([]rune(s))
   312  	u16le := make([]byte, len(u16)*2)
   313  	for i := 0; i < len(u16); i++ {
   314  		u16le[i<<1] = byte(u16[i])
   315  		u16le[(i<<1)+1] = byte(u16[i] >> 8)
   316  	}
   317  	return u16le
   318  }
   319  
   320  func MessageBox(caption, title string, fail bool) int {
   321  	var format int
   322  	if fail {
   323  		format = 0x10
   324  	} else {
   325  		format = 0x41
   326  	}
   327  
   328  	user32 := syscall.NewLazyDLL("user32.dll")
   329  	captionPtr, _ := syscall.UTF16PtrFromString(caption)
   330  	titlePtr, _ := syscall.UTF16PtrFromString(title)
   331  	ret, _, _ := user32.NewProc("MessageBoxW").Call(
   332  		uintptr(0),
   333  		uintptr(unsafe.Pointer(captionPtr)),
   334  		uintptr(unsafe.Pointer(titlePtr)),
   335  		uintptr(format))
   336  
   337  	return int(ret)
   338  }
   339  
   340  func buildCommandArgs(elevate bool) string {
   341  	var args []string
   342  	for _, arg := range os.Args[1:] {
   343  		if arg != "--reexec" {
   344  			args = append(args, syscall.EscapeArg(arg))
   345  			if elevate && arg == "init" {
   346  				args = append(args, "--reexec")
   347  			}
   348  		}
   349  	}
   350  	return strings.Join(args, " ")
   351  }
   352  
   353  func sendQuit(tid uint32) {
   354  	user32 := syscall.NewLazyDLL("user32.dll")
   355  	postMessage := user32.NewProc("PostThreadMessageW")
   356  	postMessage.Call(uintptr(tid), WM_QUIT, 0, 0)
   357  }