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

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package updater
     7  
     8  import (
     9  	"crypto/rand"
    10  	"encoding/hex"
    11  	"errors"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"runtime"
    16  	"syscall"
    17  	"unsafe"
    18  
    19  	"golang.org/x/sys/windows"
    20  )
    21  
    22  type tempFile struct {
    23  	*os.File
    24  	originalHandle windows.Handle
    25  }
    26  
    27  func (t *tempFile) ExclusivePath() string {
    28  	if t.originalHandle != 0 {
    29  		t.Close() // TODO: sort of a toctou, but msi requires unshared file
    30  		t.originalHandle = 0
    31  	}
    32  	return t.Name()
    33  }
    34  
    35  func (t *tempFile) Delete() error {
    36  	if t.originalHandle == 0 {
    37  		name16, err := windows.UTF16PtrFromString(t.Name())
    38  		if err != nil {
    39  			return err
    40  		}
    41  		return windows.DeleteFile(name16) // TODO: how does this deal with reparse points?
    42  	}
    43  	disposition := byte(1)
    44  	err := windows.SetFileInformationByHandle(t.originalHandle, windows.FileDispositionInfo, &disposition, 1)
    45  	t.originalHandle = 0
    46  	t.Close()
    47  	return err
    48  }
    49  
    50  func runMsi(msi *tempFile, userToken uintptr) error {
    51  	system32, err := windows.GetSystemDirectory()
    52  	if err != nil {
    53  		return err
    54  	}
    55  	devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0)
    56  	if err != nil {
    57  		return err
    58  	}
    59  	defer devNull.Close()
    60  	msiPath := msi.ExclusivePath()
    61  	attr := &os.ProcAttr{
    62  		Sys: &syscall.SysProcAttr{
    63  			Token: syscall.Token(userToken),
    64  		},
    65  		Files: []*os.File{devNull, devNull, devNull},
    66  		Dir:   filepath.Dir(msiPath),
    67  	}
    68  	msiexec := filepath.Join(system32, "msiexec.exe")
    69  	proc, err := os.StartProcess(msiexec, []string{msiexec, "/qb!-", "/i", filepath.Base(msiPath)}, attr)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	state, err := proc.Wait()
    74  	if err != nil {
    75  		return err
    76  	}
    77  	if !state.Success() {
    78  		return &exec.ExitError{ProcessState: state}
    79  	}
    80  	return nil
    81  }
    82  
    83  func msiTempFile() (*tempFile, error) {
    84  	var randBytes [32]byte
    85  	n, err := rand.Read(randBytes[:])
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	if n != int(len(randBytes)) {
    90  		return nil, errors.New("Unable to generate random bytes")
    91  	}
    92  	sd, err := windows.SecurityDescriptorFromString("O:SYD:PAI(A;;FA;;;SY)(A;;FR;;;BA)")
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	sa := &windows.SecurityAttributes{
    97  		Length:             uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
    98  		SecurityDescriptor: sd,
    99  	}
   100  	windir, err := windows.GetWindowsDirectory()
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	name := filepath.Join(windir, "Temp", hex.EncodeToString(randBytes[:]))
   105  	name16 := windows.StringToUTF16Ptr(name)
   106  	fileHandle, err := windows.CreateFile(name16, windows.GENERIC_WRITE|windows.DELETE, 0, sa, windows.CREATE_NEW, windows.FILE_ATTRIBUTE_TEMPORARY, 0)
   107  	runtime.KeepAlive(sd)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	windows.MoveFileEx(name16, nil, windows.MOVEFILE_DELAY_UNTIL_REBOOT)
   112  	return &tempFile{
   113  		File:           os.NewFile(uintptr(fileHandle), name),
   114  		originalHandle: fileHandle,
   115  	}, nil
   116  }