KubeVirt Vulnerable to Arbitrary Host File Read and Write
Description
KubeVirt is a virtual machine management add-on for Kubernetes. The hostDisk feature in KubeVirt allows mounting a host file or directory owned by the user with UID 107 into a VM. However, prior to version 1.6.1 and 1.7.0, the implementation of this feature and more specifically the DiskOrCreate option (which creates a file if it doesn't exist) has a logic bug that allows an attacker to read and write arbitrary files owned by more privileged users on the host system. Versions 1.6.1 and 1.7.0 fix the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
kubevirt.io/kubevirtGo | < 1.6.1 | 1.6.1 |
kubevirt.io/kubevirtGo | >= 1.7.0-alpha.0, < 1.7.0-rc.0 | 1.7.0-rc.0 |
Affected products
1Patches
200d03e43e3bfMerge pull request #15076 from kubevirt-bot/cherry-pick-15037-to-release-1.6
4 files changed · +50 −19
pkg/ephemeral-disk-utils/utils.go+17 −2 modified@@ -44,14 +44,29 @@ func MockDefaultOwnershipManager() { type nonOpManager struct { } -func (no *nonOpManager) UnsafeSetFileOwnership(file string) error { +func (no *nonOpManager) UnsafeSetFileOwnership(_ string) error { return nil } -func (no *nonOpManager) SetFileOwnership(file *safepath.Path) error { +func (no *nonOpManager) SetFileOwnership(_ *safepath.Path) error { return nil } +func MockDefaultOwnershipManagerWithFailure() { + DefaultOwnershipManager = &failureManager{} +} + +type failureManager struct { +} + +func (no *failureManager) UnsafeSetFileOwnership(_ string) error { + panic("unexpected call to UnsafeSetFileOwnership") +} + +func (no *failureManager) SetFileOwnership(_ *safepath.Path) error { + panic("unexpected call to SetFileOwnership") +} + type OwnershipManager struct { user string }
pkg/host-disk/host-disk.go+7 −7 modified@@ -235,7 +235,7 @@ func (hdc *DiskImgCreator) setlessPVCSpaceToleration(toleration int) { hdc.lessPVCSpaceToleration = toleration } -func (hdc DiskImgCreator) Create(vmi *v1.VirtualMachineInstance) error { +func (hdc *DiskImgCreator) Create(vmi *v1.VirtualMachineInstance) error { for _, volume := range vmi.Spec.Volumes { if hostDisk := volume.VolumeSource.HostDisk; shouldMountHostDisk(hostDisk) { if err := hdc.mountHostDiskAndSetOwnership(vmi, volume.Name, hostDisk); err != nil { @@ -263,19 +263,19 @@ func (hdc *DiskImgCreator) mountHostDiskAndSetOwnership(vmi *v1.VirtualMachineIn } if fileNotExists { - if err := hdc.handleRequestedSizeAndCreateSparseRaw(vmi, diskDir, filepath.Base(hostDisk.Path), hostDisk); err != nil { + if err = hdc.handleRequestedSizeAndCreateSparseRaw(vmi, diskDir, filepath.Base(hostDisk.Path), hostDisk); err != nil { return err } diskPath, err = safepath.JoinNoFollow(diskDir, filepath.Base(hostDisk.Path)) if err != nil { return err } - } - // Change file ownership to the qemu user. - if err := ephemeraldiskutils.DefaultOwnershipManager.SetFileOwnership(diskPath); err != nil { - log.Log.Reason(err).Errorf("Couldn't set Ownership on %s: %v", diskPath, err) - return err + // Change file ownership to the qemu user. + if err = ephemeraldiskutils.DefaultOwnershipManager.SetFileOwnership(diskPath); err != nil { + log.Log.Reason(err).Errorf("Couldn't set Ownership on %s: %v", diskPath, err) + return err + } } return nil }
pkg/host-disk/host-disk_test.go+11 −6 modified@@ -34,15 +34,13 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/record" - - "kubevirt.io/kubevirt/pkg/libvmi" - "kubevirt.io/kubevirt/pkg/safepath" - v1 "kubevirt.io/api/core/v1" "kubevirt.io/client-go/kubecli" + ephemeraldiskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils" + "kubevirt.io/kubevirt/pkg/libvmi" libvmistatus "kubevirt.io/kubevirt/pkg/libvmi/status" - + "kubevirt.io/kubevirt/pkg/safepath" "kubevirt.io/kubevirt/pkg/testutils" ) @@ -300,7 +298,14 @@ var _ = Describe("HostDisk", func() { }) }) Context("With existing disk.img", func() { - It("Should not re-create disk.img", func() { + AfterEach(func() { + By("Switching back to the regular mock ownership manager") + ephemeraldiskutils.MockDefaultOwnershipManager() + }) + + It("Should not re-create or chown disk.img", func() { + By("Switching to an ownership manager that panics when called") + ephemeraldiskutils.MockDefaultOwnershipManagerWithFailure() By("Creating a disk.img before adding a HostDisk volume") tmpDiskImg := createTempDiskImg("volume1") By("Creating a new VMI with a HostDisk volumes")
tests/storage/storage.go+15 −4 modified@@ -253,14 +253,25 @@ var _ = Describe(SIG("Storage", func() { // Start the VirtualMachineInstance with the PVC attached vmi = newVMI(pvcName) - vmi = libvmops.RunVMIAndExpectLaunch(vmi, 180) + if imageOwnedByQEMU { + vmi = libvmops.RunVMIAndExpectLaunch(vmi, 180) - By(checkingVMInstanceConsoleOut) - Expect(console.LoginToAlpine(vmi)).To(Succeed()) + By(checkingVMInstanceConsoleOut) + Expect(console.LoginToAlpine(vmi)).To(Succeed()) + } else { + By("Starting a VirtualMachineInstance") + createdVMI := libvmops.RunVMIAndExpectScheduling(vmi, 60) + + By(fmt.Sprintf("Checking that VirtualMachineInstance start failed: starting at %v", time.Now())) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + event := watcher.New(createdVMI).Timeout(60*time.Second).SinceWatchedObjectResourceVersion().WaitFor(ctx, watcher.WarningEvent, "SyncFailed") + Expect(event.Message).To(ContainSubstring("Could not open '/var/run/kubevirt-private/vmi-disks/disk0/disk.img': Permission denied"), "VMI should not be started") + } }, Entry("[test_id:3130]with Disk PVC", newRandomVMIWithPVC, true), Entry("[test_id:3131]with CDRom PVC", newRandomVMIWithCDRom, true), - Entry("hostpath disk image file not owned by qemu", newRandomVMIWithPVC, false), + Entry("unless hostpath disk image file not owned by qemu", newRandomVMIWithPVC, false), ) })
ff3b69b08b6bMerge pull request #15037 from jean-edouard/hostpathchown
4 files changed · +50 −19
pkg/ephemeral-disk-utils/utils.go+17 −2 modified@@ -44,14 +44,29 @@ func MockDefaultOwnershipManager() { type nonOpManager struct { } -func (no *nonOpManager) UnsafeSetFileOwnership(file string) error { +func (no *nonOpManager) UnsafeSetFileOwnership(_ string) error { return nil } -func (no *nonOpManager) SetFileOwnership(file *safepath.Path) error { +func (no *nonOpManager) SetFileOwnership(_ *safepath.Path) error { return nil } +func MockDefaultOwnershipManagerWithFailure() { + DefaultOwnershipManager = &failureManager{} +} + +type failureManager struct { +} + +func (no *failureManager) UnsafeSetFileOwnership(_ string) error { + panic("unexpected call to UnsafeSetFileOwnership") +} + +func (no *failureManager) SetFileOwnership(_ *safepath.Path) error { + panic("unexpected call to SetFileOwnership") +} + type OwnershipManager struct { user string }
pkg/host-disk/host-disk.go+7 −7 modified@@ -226,7 +226,7 @@ func (hdc *DiskImgCreator) setlessPVCSpaceToleration(toleration int) { hdc.lessPVCSpaceToleration = toleration } -func (hdc DiskImgCreator) Create(vmi *v1.VirtualMachineInstance) error { +func (hdc *DiskImgCreator) Create(vmi *v1.VirtualMachineInstance) error { for _, volume := range vmi.Spec.Volumes { if hostDisk := volume.VolumeSource.HostDisk; shouldMountHostDisk(hostDisk) { if err := hdc.mountHostDiskAndSetOwnership(vmi, volume.Name, hostDisk); err != nil { @@ -249,14 +249,14 @@ func (hdc *DiskImgCreator) mountHostDiskAndSetOwnership(vmi *v1.VirtualMachineIn return err } if !fileExists { - if err := hdc.handleRequestedSizeAndCreateSparseRaw(vmi, diskDir, diskPath, hostDisk); err != nil { + if err = hdc.handleRequestedSizeAndCreateSparseRaw(vmi, diskDir, diskPath, hostDisk); err != nil { + return err + } + // Change file ownership to the qemu user. + if err = ephemeraldiskutils.DefaultOwnershipManager.UnsafeSetFileOwnership(diskPath); err != nil { + log.Log.Reason(err).Errorf("Couldn't set Ownership on %s: %v", diskPath, err) return err } - } - // Change file ownership to the qemu user. - if err := ephemeraldiskutils.DefaultOwnershipManager.UnsafeSetFileOwnership(diskPath); err != nil { - log.Log.Reason(err).Errorf("Couldn't set Ownership on %s: %v", diskPath, err) - return err } return nil }
pkg/host-disk/host-disk_test.go+11 −6 modified@@ -33,15 +33,13 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/record" - - "kubevirt.io/kubevirt/pkg/libvmi" - "kubevirt.io/kubevirt/pkg/safepath" - v1 "kubevirt.io/api/core/v1" "kubevirt.io/client-go/kubecli" + ephemeraldiskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils" + "kubevirt.io/kubevirt/pkg/libvmi" libvmistatus "kubevirt.io/kubevirt/pkg/libvmi/status" - + "kubevirt.io/kubevirt/pkg/safepath" "kubevirt.io/kubevirt/pkg/testutils" ) @@ -289,7 +287,14 @@ var _ = Describe("HostDisk", func() { }) }) Context("With existing disk.img", func() { - It("Should not re-create disk.img", func() { + AfterEach(func() { + By("Switching back to the regular mock ownership manager") + ephemeraldiskutils.MockDefaultOwnershipManager() + }) + + It("Should not re-create or chown disk.img", func() { + By("Switching to an ownership manager that panics when called") + ephemeraldiskutils.MockDefaultOwnershipManagerWithFailure() By("Creating a disk.img before adding a HostDisk volume") tmpDiskImg := createTempDiskImg("volume1") By("Creating a new VMI with a HostDisk volumes")
tests/storage/storage.go+15 −4 modified@@ -253,14 +253,25 @@ var _ = Describe(SIG("Storage", func() { // Start the VirtualMachineInstance with the PVC attached vmi = newVMI(pvcName) - vmi = libvmops.RunVMIAndExpectLaunch(vmi, 180) + if imageOwnedByQEMU { + vmi = libvmops.RunVMIAndExpectLaunch(vmi, 180) - By(checkingVMInstanceConsoleOut) - Expect(console.LoginToAlpine(vmi)).To(Succeed()) + By(checkingVMInstanceConsoleOut) + Expect(console.LoginToAlpine(vmi)).To(Succeed()) + } else { + By("Starting a VirtualMachineInstance") + createdVMI := libvmops.RunVMIAndExpectScheduling(vmi, 60) + + By(fmt.Sprintf("Checking that VirtualMachineInstance start failed: starting at %v", time.Now())) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + event := watcher.New(createdVMI).Timeout(60*time.Second).SinceWatchedObjectResourceVersion().WaitFor(ctx, watcher.WarningEvent, "SyncFailed") + Expect(event.Message).To(ContainSubstring("Could not open '/var/run/kubevirt-private/vmi-disks/disk0/disk.img': Permission denied"), "VMI should not be started") + } }, Entry("[test_id:3130]with Disk PVC", newRandomVMIWithPVC, true), Entry("[test_id:3131]with CDRom PVC", newRandomVMIWithCDRom, true), - Entry("hostpath disk image file not owned by qemu", newRandomVMIWithPVC, false), + Entry("unless hostpath disk image file not owned by qemu", newRandomVMIWithPVC, false), ) })
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-46xp-26xh-hpqhghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-64324ghsaADVISORY
- github.com/kubevirt/kubevirt/commit/00d03e43e3bf03e563136695a4732b65ed42d764ghsaWEB
- github.com/kubevirt/kubevirt/commit/ff3b69b08b6b9c8d08d23735ca8d82455f790a69ghsaWEB
- github.com/kubevirt/kubevirt/pull/15037ghsaWEB
- github.com/kubevirt/kubevirt/security/advisories/GHSA-46xp-26xh-hpqhghsaWEB
News mentions
0No linked articles in our index yet.