mirror of
https://github.com/containers/podman.git
synced 2025-08-24 01:50:58 +08:00

creating vsocks in windows requires admin privileges. there could be some workarounds made in the future,but the general deal has always been, you need to be admin. lets enforce this with an error until those work-arounds can be implemented. Signed-off-by: Brent Baude <bbaude@redhat.com>
353 lines
9.3 KiB
Go
353 lines
9.3 KiB
Go
//go:build windows
|
|
|
|
package wsl
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
|
|
"github.com/containers/storage/pkg/homedir"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/windows"
|
|
"golang.org/x/sys/windows/registry"
|
|
)
|
|
|
|
type SHELLEXECUTEINFO struct {
|
|
cbSize uint32
|
|
fMask uint32
|
|
hwnd syscall.Handle
|
|
lpVerb uintptr
|
|
lpFile uintptr
|
|
lpParameters uintptr
|
|
lpDirectory uintptr
|
|
nShow int
|
|
hInstApp syscall.Handle
|
|
lpIDList uintptr
|
|
lpClass uintptr
|
|
hkeyClass syscall.Handle
|
|
dwHotKey uint32
|
|
hIconOrMonitor syscall.Handle
|
|
hProcess syscall.Handle
|
|
}
|
|
|
|
type Luid struct {
|
|
lowPart uint32
|
|
highPart int32
|
|
}
|
|
|
|
type LuidAndAttributes struct {
|
|
luid Luid
|
|
attributes uint32
|
|
}
|
|
|
|
type TokenPrivileges struct {
|
|
privilegeCount uint32
|
|
privileges [1]LuidAndAttributes
|
|
}
|
|
|
|
// Cleaner to refer to the official OS constant names, and consistent with syscall
|
|
const (
|
|
//nolint:stylecheck
|
|
SEE_MASK_NOCLOSEPROCESS = 0x40
|
|
//nolint:stylecheck
|
|
EWX_FORCEIFHUNG = 0x10
|
|
//nolint:stylecheck
|
|
EWX_REBOOT = 0x02
|
|
//nolint:stylecheck
|
|
EWX_RESTARTAPPS = 0x40
|
|
//nolint:stylecheck
|
|
SHTDN_REASON_MAJOR_APPLICATION = 0x00040000
|
|
//nolint:stylecheck
|
|
SHTDN_REASON_MINOR_INSTALLATION = 0x00000002
|
|
//nolint:stylecheck
|
|
SHTDN_REASON_FLAG_PLANNED = 0x80000000
|
|
//nolint:stylecheck
|
|
TOKEN_ADJUST_PRIVILEGES = 0x0020
|
|
//nolint:stylecheck
|
|
TOKEN_QUERY = 0x0008
|
|
//nolint:stylecheck
|
|
SE_PRIVILEGE_ENABLED = 0x00000002
|
|
//nolint:stylecheck
|
|
SE_ERR_ACCESSDENIED = 0x05
|
|
)
|
|
|
|
func winVersionAtLeast(major uint, minor uint, build uint) bool {
|
|
var out [3]uint32
|
|
|
|
in := []uint32{uint32(major), uint32(minor), uint32(build)}
|
|
out[0], out[1], out[2] = windows.RtlGetNtVersionNumbers()
|
|
|
|
for i, o := range out {
|
|
if in[i] > o {
|
|
return false
|
|
}
|
|
if in[i] < o {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func HasAdminRights() bool {
|
|
var sid *windows.SID
|
|
|
|
// See: https://coolaj86.com/articles/golang-and-windows-and-admins-oh-my/
|
|
if err := windows.AllocateAndInitializeSid(
|
|
&windows.SECURITY_NT_AUTHORITY,
|
|
2,
|
|
windows.SECURITY_BUILTIN_DOMAIN_RID,
|
|
windows.DOMAIN_ALIAS_RID_ADMINS,
|
|
0, 0, 0, 0, 0, 0,
|
|
&sid); err != nil {
|
|
logrus.Warnf("SID allocation error: %s", err)
|
|
return false
|
|
}
|
|
defer func() {
|
|
_ = windows.FreeSid(sid)
|
|
}()
|
|
|
|
// From MS docs:
|
|
// "If TokenHandle is NULL, CheckTokenMembership uses the impersonation
|
|
// token of the calling thread. If the thread is not impersonating,
|
|
// the function duplicates the thread's primary token to create an
|
|
// impersonation token."
|
|
token := windows.Token(0)
|
|
|
|
member, err := token.IsMember(sid)
|
|
if err != nil {
|
|
logrus.Warnf("Token Membership Error: %s", err)
|
|
return false
|
|
}
|
|
|
|
return member || token.IsElevated()
|
|
}
|
|
|
|
func relaunchElevatedWait() error {
|
|
e, _ := os.Executable()
|
|
d, _ := os.Getwd()
|
|
exe, _ := syscall.UTF16PtrFromString(e)
|
|
cwd, _ := syscall.UTF16PtrFromString(d)
|
|
arg, _ := syscall.UTF16PtrFromString(buildCommandArgs(true))
|
|
verb, _ := syscall.UTF16PtrFromString("runas")
|
|
|
|
shell32 := syscall.NewLazyDLL("shell32.dll")
|
|
|
|
info := &SHELLEXECUTEINFO{
|
|
fMask: SEE_MASK_NOCLOSEPROCESS,
|
|
hwnd: 0,
|
|
lpVerb: uintptr(unsafe.Pointer(verb)),
|
|
lpFile: uintptr(unsafe.Pointer(exe)),
|
|
lpParameters: uintptr(unsafe.Pointer(arg)),
|
|
lpDirectory: uintptr(unsafe.Pointer(cwd)),
|
|
nShow: 1,
|
|
}
|
|
info.cbSize = uint32(unsafe.Sizeof(*info))
|
|
procShellExecuteEx := shell32.NewProc("ShellExecuteExW")
|
|
if ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(info))); ret == 0 { // 0 = False
|
|
err := syscall.GetLastError()
|
|
if info.hInstApp == SE_ERR_ACCESSDENIED {
|
|
return wrapMaybe(err, "request to elevate privileges was denied")
|
|
}
|
|
return wrapMaybef(err, "could not launch process, ShellEX Error = %d", info.hInstApp)
|
|
}
|
|
|
|
handle := info.hProcess
|
|
defer func() {
|
|
_ = syscall.CloseHandle(handle)
|
|
}()
|
|
|
|
w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
|
|
switch w {
|
|
case syscall.WAIT_OBJECT_0:
|
|
break
|
|
case syscall.WAIT_FAILED:
|
|
return fmt.Errorf("could not wait for process, failed: %w", err)
|
|
default:
|
|
return errors.New("could not wait for process, unknown error")
|
|
}
|
|
var code uint32
|
|
if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
|
|
return err
|
|
}
|
|
if code != 0 {
|
|
return &ExitCodeError{uint(code)}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func wrapMaybe(err error, message string) error {
|
|
if err != nil {
|
|
return fmt.Errorf("%v: %w", message, err)
|
|
}
|
|
|
|
return errors.New(message)
|
|
}
|
|
|
|
func wrapMaybef(err error, format string, args ...interface{}) error {
|
|
if err != nil {
|
|
return fmt.Errorf(format+": %w", append(args, err)...)
|
|
}
|
|
|
|
return fmt.Errorf(format, args...)
|
|
}
|
|
|
|
func reboot() error {
|
|
const (
|
|
wtLocation = `Microsoft\WindowsApps\wt.exe`
|
|
wtPrefix = `%LocalAppData%\Microsoft\WindowsApps\wt -p "Windows PowerShell" `
|
|
localAppData = "LocalAppData"
|
|
pShellLaunch = `powershell -noexit "powershell -EncodedCommand (Get-Content '%s')"`
|
|
)
|
|
|
|
exe, _ := os.Executable()
|
|
relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), buildCommandArgs(false))
|
|
encoded := base64.StdEncoding.EncodeToString(encodeUTF16Bytes(relaunch))
|
|
|
|
dataDir, err := homedir.GetDataHome()
|
|
if err != nil {
|
|
return fmt.Errorf("could not determine data directory: %w", err)
|
|
}
|
|
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
|
return fmt.Errorf("could not create data directory: %w", err)
|
|
}
|
|
commFile := filepath.Join(dataDir, "podman-relaunch.dat")
|
|
if err := os.WriteFile(commFile, []byte(encoded), 0600); err != nil {
|
|
return fmt.Errorf("could not serialize command state: %w", err)
|
|
}
|
|
|
|
command := fmt.Sprintf(pShellLaunch, commFile)
|
|
if _, err := os.Lstat(filepath.Join(os.Getenv(localAppData), wtLocation)); err == nil {
|
|
wtCommand := wtPrefix + command
|
|
// RunOnce is limited to 260 chars (supposedly no longer in Builds >= 19489)
|
|
// For now fallback in cases of long usernames (>89 chars)
|
|
if len(wtCommand) < 260 {
|
|
command = wtCommand
|
|
}
|
|
}
|
|
|
|
if err := addRunOnceRegistryEntry(command); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := obtainShutdownPrivilege(); err != nil {
|
|
return err
|
|
}
|
|
|
|
message := "To continue the process of enabling WSL, the system needs to reboot. " +
|
|
"Alternatively, you can cancel and reboot manually\n\n" +
|
|
"After rebooting, please wait a minute or two for podman machine to relaunch and continue installing."
|
|
|
|
if MessageBox(message, "Podman Machine", false) != 1 {
|
|
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
|
|
os.Exit(ErrorSuccessRebootRequired)
|
|
return nil
|
|
}
|
|
|
|
user32 := syscall.NewLazyDLL("user32")
|
|
procExit := user32.NewProc("ExitWindowsEx")
|
|
if ret, _, err := procExit.Call(EWX_REBOOT|EWX_RESTARTAPPS|EWX_FORCEIFHUNG,
|
|
SHTDN_REASON_MAJOR_APPLICATION|SHTDN_REASON_MINOR_INSTALLATION|SHTDN_REASON_FLAG_PLANNED); ret != 1 {
|
|
return fmt.Errorf("reboot failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func obtainShutdownPrivilege() error {
|
|
const SeShutdownName = "SeShutdownPrivilege"
|
|
|
|
advapi32 := syscall.NewLazyDLL("advapi32")
|
|
OpenProcessToken := advapi32.NewProc("OpenProcessToken")
|
|
LookupPrivilegeValue := advapi32.NewProc("LookupPrivilegeValueW")
|
|
AdjustTokenPrivileges := advapi32.NewProc("AdjustTokenPrivileges")
|
|
|
|
proc, _ := syscall.GetCurrentProcess()
|
|
|
|
var hToken uintptr
|
|
if ret, _, err := OpenProcessToken.Call(uintptr(proc), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, uintptr(unsafe.Pointer(&hToken))); ret != 1 {
|
|
return fmt.Errorf("opening process token: %w", err)
|
|
}
|
|
|
|
var privs TokenPrivileges
|
|
//nolint:staticcheck
|
|
if ret, _, err := LookupPrivilegeValue.Call(uintptr(0), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(SeShutdownName))), uintptr(unsafe.Pointer(&(privs.privileges[0].luid)))); ret != 1 {
|
|
return fmt.Errorf("looking up shutdown privilege: %w", err)
|
|
}
|
|
|
|
privs.privilegeCount = 1
|
|
privs.privileges[0].attributes = SE_PRIVILEGE_ENABLED
|
|
|
|
if ret, _, err := AdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&privs)), 0, uintptr(0), 0); ret != 1 {
|
|
return fmt.Errorf("enabling shutdown privilege on token: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func addRunOnceRegistryEntry(command string) error {
|
|
k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE)
|
|
if err != nil {
|
|
return fmt.Errorf("could not open RunOnce registry entry: %w", err)
|
|
}
|
|
|
|
defer k.Close()
|
|
|
|
if err := k.SetExpandStringValue("podman-machine", command); err != nil {
|
|
return fmt.Errorf("could not open RunOnce registry entry: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func encodeUTF16Bytes(s string) []byte {
|
|
u16 := utf16.Encode([]rune(s))
|
|
u16le := make([]byte, len(u16)*2)
|
|
for i := 0; i < len(u16); i++ {
|
|
u16le[i<<1] = byte(u16[i])
|
|
u16le[(i<<1)+1] = byte(u16[i] >> 8)
|
|
}
|
|
return u16le
|
|
}
|
|
|
|
func MessageBox(caption, title string, fail bool) int {
|
|
var format int
|
|
if fail {
|
|
format = 0x10
|
|
} else {
|
|
format = 0x41
|
|
}
|
|
|
|
user32 := syscall.NewLazyDLL("user32.dll")
|
|
captionPtr, _ := syscall.UTF16PtrFromString(caption)
|
|
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
|
ret, _, _ := user32.NewProc("MessageBoxW").Call(
|
|
uintptr(0),
|
|
uintptr(unsafe.Pointer(captionPtr)),
|
|
uintptr(unsafe.Pointer(titlePtr)),
|
|
uintptr(format))
|
|
|
|
return int(ret)
|
|
}
|
|
|
|
func buildCommandArgs(elevate bool) string {
|
|
var args []string
|
|
for _, arg := range os.Args[1:] {
|
|
if arg != "--reexec" {
|
|
args = append(args, syscall.EscapeArg(arg))
|
|
if elevate && arg == "init" {
|
|
args = append(args, "--reexec")
|
|
}
|
|
}
|
|
}
|
|
return strings.Join(args, " ")
|
|
}
|