podman

Форк
0
/
util_windows.go 
353 строки · 9.4 Кб
1
//go:build windows
2

3
package wsl
4

5
import (
6
	"encoding/base64"
7
	"errors"
8
	"fmt"
9
	"os"
10
	"path/filepath"
11
	"strings"
12
	"syscall"
13
	"unicode/utf16"
14
	"unsafe"
15

16
	"github.com/containers/storage/pkg/fileutils"
17
	"github.com/containers/storage/pkg/homedir"
18
	"github.com/sirupsen/logrus"
19
	"golang.org/x/sys/windows"
20
	"golang.org/x/sys/windows/registry"
21
)
22

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
type Luid struct {
42
	lowPart  uint32
43
	highPart int32
44
}
45

46
type LuidAndAttributes struct {
47
	luid       Luid
48
	attributes uint32
49
}
50

51
type TokenPrivileges struct {
52
	privilegeCount uint32
53
	privileges     [1]LuidAndAttributes
54
}
55

56
// Cleaner to refer to the official OS constant names, and consistent with syscall
57
const (
58
	//nolint:stylecheck
59
	SEE_MASK_NOCLOSEPROCESS = 0x40
60
	//nolint:stylecheck
61
	EWX_FORCEIFHUNG = 0x10
62
	//nolint:stylecheck
63
	EWX_REBOOT = 0x02
64
	//nolint:stylecheck
65
	EWX_RESTARTAPPS = 0x40
66
	//nolint:stylecheck
67
	SHTDN_REASON_MAJOR_APPLICATION = 0x00040000
68
	//nolint:stylecheck
69
	SHTDN_REASON_MINOR_INSTALLATION = 0x00000002
70
	//nolint:stylecheck
71
	SHTDN_REASON_FLAG_PLANNED = 0x80000000
72
	//nolint:stylecheck
73
	TOKEN_ADJUST_PRIVILEGES = 0x0020
74
	//nolint:stylecheck
75
	TOKEN_QUERY = 0x0008
76
	//nolint:stylecheck
77
	SE_PRIVILEGE_ENABLED = 0x00000002
78
	//nolint:stylecheck
79
	SE_ERR_ACCESSDENIED = 0x05
80
)
81

82
func winVersionAtLeast(major uint, minor uint, build uint) bool {
83
	var out [3]uint32
84

85
	in := []uint32{uint32(major), uint32(minor), uint32(build)}
86
	out[0], out[1], out[2] = windows.RtlGetNtVersionNumbers()
87

88
	for i, o := range out {
89
		if in[i] > o {
90
			return false
91
		}
92
		if in[i] < o {
93
			return true
94
		}
95
	}
96

97
	return true
98
}
99

100
func HasAdminRights() bool {
101
	var sid *windows.SID
102

103
	// See: https://coolaj86.com/articles/golang-and-windows-and-admins-oh-my/
104
	if err := windows.AllocateAndInitializeSid(
105
		&windows.SECURITY_NT_AUTHORITY,
106
		2,
107
		windows.SECURITY_BUILTIN_DOMAIN_RID,
108
		windows.DOMAIN_ALIAS_RID_ADMINS,
109
		0, 0, 0, 0, 0, 0,
110
		&sid); err != nil {
111
		logrus.Warnf("SID allocation error: %s", err)
112
		return false
113
	}
114
	defer func() {
115
		_ = windows.FreeSid(sid)
116
	}()
117

118
	//  From MS docs:
119
	// "If TokenHandle is NULL, CheckTokenMembership uses the impersonation
120
	//  token of the calling thread. If the thread is not impersonating,
121
	//  the function duplicates the thread's primary token to create an
122
	//  impersonation token."
123
	token := windows.Token(0)
124

125
	member, err := token.IsMember(sid)
126
	if err != nil {
127
		logrus.Warnf("Token Membership Error: %s", err)
128
		return false
129
	}
130

131
	return member || token.IsElevated()
132
}
133

134
func relaunchElevatedWait() error {
135
	e, _ := os.Executable()
136
	d, _ := os.Getwd()
137
	exe, _ := syscall.UTF16PtrFromString(e)
138
	cwd, _ := syscall.UTF16PtrFromString(d)
139
	arg, _ := syscall.UTF16PtrFromString(buildCommandArgs(true))
140
	verb, _ := syscall.UTF16PtrFromString("runas")
141

142
	shell32 := syscall.NewLazyDLL("shell32.dll")
143

144
	info := &SHELLEXECUTEINFO{
145
		fMask:        SEE_MASK_NOCLOSEPROCESS,
146
		hwnd:         0,
147
		lpVerb:       uintptr(unsafe.Pointer(verb)),
148
		lpFile:       uintptr(unsafe.Pointer(exe)),
149
		lpParameters: uintptr(unsafe.Pointer(arg)),
150
		lpDirectory:  uintptr(unsafe.Pointer(cwd)),
151
		nShow:        1,
152
	}
153
	info.cbSize = uint32(unsafe.Sizeof(*info))
154
	procShellExecuteEx := shell32.NewProc("ShellExecuteExW")
155
	if ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(info))); ret == 0 { // 0 = False
156
		err := syscall.GetLastError()
157
		if info.hInstApp == SE_ERR_ACCESSDENIED {
158
			return wrapMaybe(err, "request to elevate privileges was denied")
159
		}
160
		return wrapMaybef(err, "could not launch process, ShellEX Error = %d", info.hInstApp)
161
	}
162

163
	handle := info.hProcess
164
	defer func() {
165
		_ = syscall.CloseHandle(handle)
166
	}()
167

168
	w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
169
	switch w {
170
	case syscall.WAIT_OBJECT_0:
171
		break
172
	case syscall.WAIT_FAILED:
173
		return fmt.Errorf("could not wait for process, failed: %w", err)
174
	default:
175
		return errors.New("could not wait for process, unknown error")
176
	}
177
	var code uint32
178
	if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
179
		return err
180
	}
181
	if code != 0 {
182
		return &ExitCodeError{uint(code)}
183
	}
184

185
	return nil
186
}
187

188
func wrapMaybe(err error, message string) error {
189
	if err != nil {
190
		return fmt.Errorf("%v: %w", message, err)
191
	}
192

193
	return errors.New(message)
194
}
195

196
func wrapMaybef(err error, format string, args ...interface{}) error {
197
	if err != nil {
198
		return fmt.Errorf(format+": %w", append(args, err)...)
199
	}
200

201
	return fmt.Errorf(format, args...)
202
}
203

204
func reboot() error {
205
	const (
206
		wtLocation   = `Microsoft\WindowsApps\wt.exe`
207
		wtPrefix     = `%LocalAppData%\Microsoft\WindowsApps\wt -p "Windows PowerShell" `
208
		localAppData = "LocalAppData"
209
		pShellLaunch = `powershell -noexit "powershell -EncodedCommand (Get-Content '%s')"`
210
	)
211

212
	exe, _ := os.Executable()
213
	relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), buildCommandArgs(false))
214
	encoded := base64.StdEncoding.EncodeToString(encodeUTF16Bytes(relaunch))
215

216
	dataDir, err := homedir.GetDataHome()
217
	if err != nil {
218
		return fmt.Errorf("could not determine data directory: %w", err)
219
	}
220
	if err := os.MkdirAll(dataDir, 0755); err != nil {
221
		return fmt.Errorf("could not create data directory: %w", err)
222
	}
223
	commFile := filepath.Join(dataDir, "podman-relaunch.dat")
224
	if err := os.WriteFile(commFile, []byte(encoded), 0600); err != nil {
225
		return fmt.Errorf("could not serialize command state: %w", err)
226
	}
227

228
	command := fmt.Sprintf(pShellLaunch, commFile)
229
	if err := fileutils.Lexists(filepath.Join(os.Getenv(localAppData), wtLocation)); err == nil {
230
		wtCommand := wtPrefix + command
231
		// RunOnce is limited to 260 chars (supposedly no longer in Builds >= 19489)
232
		// For now fallback in cases of long usernames (>89 chars)
233
		if len(wtCommand) < 260 {
234
			command = wtCommand
235
		}
236
	}
237

238
	if err := addRunOnceRegistryEntry(command); err != nil {
239
		return err
240
	}
241

242
	if err := obtainShutdownPrivilege(); err != nil {
243
		return err
244
	}
245

246
	message := "To continue the process of enabling WSL, the system needs to reboot. " +
247
		"Alternatively, you can cancel and reboot manually\n\n" +
248
		"After rebooting, please wait a minute or two for podman machine to relaunch and continue installing."
249

250
	if MessageBox(message, "Podman Machine", false) != 1 {
251
		fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
252
		os.Exit(ErrorSuccessRebootRequired)
253
		return nil
254
	}
255

256
	user32 := syscall.NewLazyDLL("user32")
257
	procExit := user32.NewProc("ExitWindowsEx")
258
	if ret, _, err := procExit.Call(EWX_REBOOT|EWX_RESTARTAPPS|EWX_FORCEIFHUNG,
259
		SHTDN_REASON_MAJOR_APPLICATION|SHTDN_REASON_MINOR_INSTALLATION|SHTDN_REASON_FLAG_PLANNED); ret != 1 {
260
		return fmt.Errorf("reboot failed: %w", err)
261
	}
262

263
	return nil
264
}
265

266
func obtainShutdownPrivilege() error {
267
	const SeShutdownName = "SeShutdownPrivilege"
268

269
	advapi32 := syscall.NewLazyDLL("advapi32")
270
	OpenProcessToken := advapi32.NewProc("OpenProcessToken")
271
	LookupPrivilegeValue := advapi32.NewProc("LookupPrivilegeValueW")
272
	AdjustTokenPrivileges := advapi32.NewProc("AdjustTokenPrivileges")
273

274
	proc, _ := syscall.GetCurrentProcess()
275

276
	var hToken uintptr
277
	if ret, _, err := OpenProcessToken.Call(uintptr(proc), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, uintptr(unsafe.Pointer(&hToken))); ret != 1 {
278
		return fmt.Errorf("opening process token: %w", err)
279
	}
280

281
	var privs TokenPrivileges
282
	//nolint:staticcheck
283
	if ret, _, err := LookupPrivilegeValue.Call(uintptr(0), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(SeShutdownName))), uintptr(unsafe.Pointer(&(privs.privileges[0].luid)))); ret != 1 {
284
		return fmt.Errorf("looking up shutdown privilege: %w", err)
285
	}
286

287
	privs.privilegeCount = 1
288
	privs.privileges[0].attributes = SE_PRIVILEGE_ENABLED
289

290
	if ret, _, err := AdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&privs)), 0, uintptr(0), 0); ret != 1 {
291
		return fmt.Errorf("enabling shutdown privilege on token: %w", err)
292
	}
293

294
	return nil
295
}
296

297
func addRunOnceRegistryEntry(command string) error {
298
	k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE)
299
	if err != nil {
300
		return fmt.Errorf("could not open RunOnce registry entry: %w", err)
301
	}
302

303
	defer k.Close()
304

305
	if err := k.SetExpandStringValue("podman-machine", command); err != nil {
306
		return fmt.Errorf("could not open RunOnce registry entry: %w", err)
307
	}
308

309
	return nil
310
}
311

312
func encodeUTF16Bytes(s string) []byte {
313
	u16 := utf16.Encode([]rune(s))
314
	u16le := make([]byte, len(u16)*2)
315
	for i := 0; i < len(u16); i++ {
316
		u16le[i<<1] = byte(u16[i])
317
		u16le[(i<<1)+1] = byte(u16[i] >> 8)
318
	}
319
	return u16le
320
}
321

322
func MessageBox(caption, title string, fail bool) int {
323
	var format int
324
	if fail {
325
		format = 0x10
326
	} else {
327
		format = 0x41
328
	}
329

330
	user32 := syscall.NewLazyDLL("user32.dll")
331
	captionPtr, _ := syscall.UTF16PtrFromString(caption)
332
	titlePtr, _ := syscall.UTF16PtrFromString(title)
333
	ret, _, _ := user32.NewProc("MessageBoxW").Call(
334
		uintptr(0),
335
		uintptr(unsafe.Pointer(captionPtr)),
336
		uintptr(unsafe.Pointer(titlePtr)),
337
		uintptr(format))
338

339
	return int(ret)
340
}
341

342
func buildCommandArgs(elevate bool) string {
343
	var args []string
344
	for _, arg := range os.Args[1:] {
345
		if arg != "--reexec" {
346
			args = append(args, syscall.EscapeArg(arg))
347
			if elevate && arg == "init" {
348
				args = append(args, "--reexec")
349
			}
350
		}
351
	}
352
	return strings.Join(args, " ")
353
}
354

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.