Skip to content

Commit f66f7c8

Browse files
Merge pull request #27650 from lstocchi/i27614
Prevent non hyper-v admin users to execute machine commands
2 parents ed132b7 + d150051 commit f66f7c8

File tree

5 files changed

+141
-8
lines changed

5 files changed

+141
-8
lines changed

pkg/machine/hyperv/hutil.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ package hyperv
55
import (
66
"errors"
77

8+
"github.com/containers/podman/v6/pkg/machine/windows"
89
"github.com/sirupsen/logrus"
9-
"golang.org/x/sys/windows"
10+
syswindows "golang.org/x/sys/windows"
1011
)
1112

1213
var (
@@ -17,7 +18,7 @@ var (
1718
)
1819

1920
func HasHyperVAdminRights() bool {
20-
sid, err := windows.CreateWellKnownSid(windows.WinBuiltinHyperVAdminsSid)
21+
sid, err := syswindows.CreateWellKnownSid(syswindows.WinBuiltinHyperVAdminsSid)
2122
if err != nil {
2223
return false
2324
}
@@ -27,7 +28,7 @@ func HasHyperVAdminRights() bool {
2728
// token of the calling thread. If the thread is not impersonating,
2829
// the function duplicates the thread's primary token to create an
2930
// impersonation token."
30-
token := windows.Token(0)
31+
token := syswindows.Token(0)
3132
member, err := token.IsMember(sid)
3233
if err != nil {
3334
logrus.Warnf("Token Membership Error: %s", err)
@@ -36,3 +37,8 @@ func HasHyperVAdminRights() bool {
3637

3738
return member
3839
}
40+
41+
// HasHyperVPermissions checks if the user has either admin rights or Hyper-V admin rights.
42+
func HasHyperVPermissions() bool {
43+
return windows.HasAdminRights() || HasHyperVAdminRights()
44+
}

pkg/machine/provider/platform_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func TestSupportedProviders(t *testing.T) {
1313
case "darwin":
1414
assert.Equal(t, []define.VMType{define.AppleHvVirt, define.LibKrun}, SupportedProviders())
1515
case "windows":
16-
assert.Equal(t, []define.VMType{define.WSLVirt, define.HyperVVirt}, SupportedProviders())
16+
assert.ElementsMatch(t, []define.VMType{define.WSLVirt, define.HyperVVirt}, SupportedProviders())
1717
case "linux":
1818
assert.Equal(t, []define.VMType{define.QemuVirt}, SupportedProviders())
1919
}
@@ -28,7 +28,7 @@ func TestInstalledProviders(t *testing.T) {
2828
case "windows":
2929
provider, err := Get()
3030
assert.NoError(t, err)
31-
assert.Contains(t, installed, provider)
31+
assert.Contains(t, installed, provider.VMType())
3232
case "linux":
3333
assert.Equal(t, []define.VMType{define.QemuVirt}, installed)
3434
}

pkg/machine/provider/platform_windows.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import (
1515
"go.podman.io/common/pkg/config"
1616
)
1717

18+
// Variable to hold permission check function for testing purposes.
19+
var (
20+
hasHyperVPermissionsFunc = hyperv.HasHyperVPermissions
21+
)
22+
1823
func Get() (vmconfigs.VMProvider, error) {
1924
cfg, err := config.Default()
2025
if err != nil {
@@ -39,17 +44,26 @@ func GetByVMType(resolvedVMType define.VMType) (vmconfigs.VMProvider, error) {
3944
case define.WSLVirt:
4045
return new(wsl.WSLStubber), nil
4146
case define.HyperVVirt:
47+
// Check permissions before returning the Hyper-V provider.
48+
// Working with Hyper-V requires users to be at least members of the Hyper-V admin group.
49+
// Init and remove actions have custom use cases and they are checked on the stubber.
50+
if !hasHyperVPermissionsFunc() {
51+
return nil, hyperv.ErrHypervUserNotInAdminGroup
52+
}
4253
return new(hyperv.HyperVStubber), nil
4354
default:
4455
}
4556
return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String())
4657
}
4758

4859
func GetAll() []vmconfigs.VMProvider {
49-
return []vmconfigs.VMProvider{
60+
providers := []vmconfigs.VMProvider{
5061
new(wsl.WSLStubber),
51-
new(hyperv.HyperVStubber),
5262
}
63+
if hasHyperVPermissionsFunc() {
64+
providers = append(providers, new(hyperv.HyperVStubber))
65+
}
66+
return providers
5367
}
5468

5569
// SupportedProviders returns the providers that are supported on the host operating system
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//go:build windows
2+
3+
package provider
4+
5+
import (
6+
"testing"
7+
8+
"github.com/containers/podman/v6/pkg/machine/define"
9+
"github.com/containers/podman/v6/pkg/machine/hyperv"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
// helper to setup mocks and ensure cleanup
15+
func mockPermissions(t *testing.T, hasHyperVPermissions bool) {
16+
origHyperVPermissionsFunc := hasHyperVPermissionsFunc
17+
t.Cleanup(func() {
18+
hasHyperVPermissionsFunc = origHyperVPermissionsFunc
19+
})
20+
21+
hasHyperVPermissionsFunc = func() bool { return hasHyperVPermissions }
22+
}
23+
24+
func TestGetByVMType_HyperV(t *testing.T) {
25+
tests := []struct {
26+
name string
27+
hasHyperVPermissions bool
28+
expectError bool
29+
}{
30+
{
31+
name: "WithHyperVPermissions",
32+
hasHyperVPermissions: true,
33+
expectError: false,
34+
},
35+
{
36+
name: "WithoutPermissions",
37+
hasHyperVPermissions: false,
38+
expectError: true,
39+
},
40+
}
41+
42+
for _, tt := range tests {
43+
t.Run(tt.name, func(t *testing.T) {
44+
mockPermissions(t, tt.hasHyperVPermissions)
45+
46+
provider, err := GetByVMType(define.HyperVVirt)
47+
48+
if tt.expectError {
49+
assert.Error(t, err)
50+
assert.Equal(t, err.Error(), hyperv.ErrHypervUserNotInAdminGroup.Error())
51+
assert.Nil(t, provider)
52+
} else {
53+
require.NoError(t, err)
54+
assert.NotNil(t, provider)
55+
assert.Equal(t, define.HyperVVirt, provider.VMType())
56+
}
57+
})
58+
}
59+
}
60+
61+
func TestGetAll_HyperV_Inclusion(t *testing.T) {
62+
tests := []struct {
63+
name string
64+
hasHyperVPermissions bool
65+
expectHyperV bool
66+
}{
67+
{"WithHyperVPermissions", true, true},
68+
{"WithoutHyperVPermissions", false, false},
69+
}
70+
71+
for _, tt := range tests {
72+
t.Run(tt.name, func(t *testing.T) {
73+
mockPermissions(t, tt.hasHyperVPermissions)
74+
75+
providers := GetAll()
76+
77+
// Check for HyperV presence
78+
hasHyperV := false
79+
for _, p := range providers {
80+
if p.VMType() == define.HyperVVirt {
81+
hasHyperV = true
82+
break
83+
}
84+
}
85+
86+
assert.Equal(t, tt.expectHyperV, hasHyperV, "Hyper-V provider presence mismatch")
87+
88+
// WSL should always be present in these scenarios
89+
hasWSL := false
90+
for _, p := range providers {
91+
if p.VMType() == define.WSLVirt {
92+
hasWSL = true
93+
break
94+
}
95+
}
96+
assert.True(t, hasWSL, "GetAll should always include WSL provider")
97+
})
98+
}
99+
}
100+
101+
func TestGetByVMType_WSL_AlwaysWorks(t *testing.T) {
102+
provider, err := GetByVMType(define.WSLVirt)
103+
require.NoError(t, err)
104+
assert.NotNil(t, provider)
105+
assert.Equal(t, define.WSLVirt, provider.VMType())
106+
}
107+
108+
func TestGetByVMType_UnsupportedProvider(t *testing.T) {
109+
provider, err := GetByVMType(define.QemuVirt)
110+
assert.Error(t, err)
111+
assert.Contains(t, err.Error(), "unsupported virtualization provider")
112+
assert.Nil(t, provider)
113+
}

winmake.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function Local-Unit {
7878
Build-Ginkgo
7979
$skippackages = 'hack,internal\domain\infra\abi,internal\domain\infra\tunnel,libpod\lock\shm,pkg\api\handlers\libpod,pkg\api\handlers\utils,pkg\bindings,'
8080
$skippackages += 'pkg\domain\infra\abi,pkg\emulation,pkg\machine\apple,pkg\machine\applehv,pkg\machine\e2e,pkg\machine\libkrun,'
81-
$skippackages += 'pkg\machine\provider,pkg\machine\proxyenv,pkg\machine\qemu,pkg\specgen\generate,pkg\systemd,test\e2e,test\utils,cmd\rootlessport,'
81+
$skippackages += 'pkg\machine\proxyenv,pkg\machine\qemu,pkg\specgen\generate,pkg\systemd,test\e2e,test\utils,cmd\rootlessport,'
8282
$skippackages += 'pkg\pidhandle'
8383
if ($null -eq $ENV:GINKGOTIMEOUT) { $ENV:GINKGOTIMEOUT = '--timeout=15m' }
8484
Run-Command "./bin/ginkgo.exe -vv -r --tags `"$remotetags`" ${ENV:GINKGOTIMEOUT} --trace --no-color --skip-package `"$skippackages`""

0 commit comments

Comments
 (0)