aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/updater/msirunner_windows.go
blob: 4ae18c0181fa145b64fa30f502a6589373e44267 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/* SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
 */

package updater

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"syscall"
	"unsafe"

	"golang.org/x/sys/windows"
)

type tempFile struct {
	*os.File
	originalHandle windows.Handle
}

func (t *tempFile) ExclusivePath() string {
	if t.originalHandle != 0 {
		t.Close() // TODO: sort of a toctou, but msi requires unshared file
		t.originalHandle = 0
	}
	return t.Name()
}


func (t *tempFile) Delete() error {
	if t.originalHandle == 0 {
		name16, err := windows.UTF16PtrFromString(t.Name())
		if err != nil {
			return err
		}
		return windows.DeleteFile(name16) //TODO: how does this deal with reparse points?
	}
	disposition := uint32(1)
	err := windows.SetFileInformationByHandle(t.originalHandle, windows.FileDispositionInfo, (*byte)(unsafe.Pointer(&disposition)), uint32(unsafe.Sizeof(disposition)))
	t.originalHandle = 0
	t.Close()
	return err
}

func runMsi(msi *tempFile, userToken uintptr) error {
	system32, err := windows.GetSystemDirectory()
	if err != nil {
		return err
	}
	devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0)
	if err != nil {
		return err
	}
	defer devNull.Close()
	msiPath := msi.ExclusivePath()
	attr := &os.ProcAttr{
		Sys: &syscall.SysProcAttr{
			Token: syscall.Token(userToken),
		},
		Files: []*os.File{devNull, devNull, devNull},
		Dir:   filepath.Dir(msiPath),
	}
	msiexec := filepath.Join(system32, "msiexec.exe")
	proc, err := os.StartProcess(msiexec, []string{msiexec, "/qb!-", "/i", filepath.Base(msiPath)}, attr)
	if err != nil {
		return err
	}
	state, err := proc.Wait()
	if err != nil {
		return err
	}
	if !state.Success() {
		return &exec.ExitError{ProcessState: state}
	}
	return nil
}

func msiTempFile() (*tempFile, error) {
	var randBytes [32]byte
	n, err := rand.Read(randBytes[:])
	if err != nil {
		return nil, err
	}
	if n != int(len(randBytes)) {
		return nil, errors.New("Unable to generate random bytes")
	}
	sd, err := windows.SecurityDescriptorFromString("O:SYD:PAI(A;;FA;;;SY)(A;;FR;;;BA)")
	if err != nil {
		return nil, err
	}
	sa := &windows.SecurityAttributes{
		Length:             uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
		SecurityDescriptor: sd,
	}
	windir, err := windows.GetWindowsDirectory()
	if err != nil {
		return nil, err
	}
	name := filepath.Join(windir, "Temp", hex.EncodeToString(randBytes[:]))
	name16 := windows.StringToUTF16Ptr(name)
	fileHandle, err := windows.CreateFile(name16, windows.GENERIC_WRITE|windows.DELETE, 0, sa, windows.CREATE_NEW, windows.FILE_ATTRIBUTE_TEMPORARY, 0)
	runtime.KeepAlive(sd)
	if err != nil {
		return nil, err
	}
	windows.MoveFileEx(name16, nil, windows.MOVEFILE_DELAY_UNTIL_REBOOT)
	return &tempFile{
		File:           os.NewFile(uintptr(fileHandle), name),
		originalHandle: fileHandle,
	}, nil
}