Stripping libvirt machines for security2026-08-27, 9mins
License: CC BY-SA 4.0
Preface
Virtual machines can be great boundaries for security, but libvirt's default QEMU/KVM machines include the entire kitchen sink which is often unnecessary. Below I'll provide diff-style snippets showing how to remove various bits to reduce the guest-to-host attack surface.
Out of Scope
This will solely be covering domain modifications. You are strongly encouraged to perform other necessary steps such as:
- general hardening.
- ensuring host firmware (eg. UEFI, microcode) is up to date.
- enabling all available CPU flaw mitigations (Linux does not do this by default).
- ensuring both host and guest is fully up to date.
- not using poorly patched or end of life software. Backports are bad.
- confining services via systemd unit sandboxing or containers. Layers are good.
- running containers under gVisor if possible.
- avoiding privileged containers.
- putting each task/group into their own VM.
- use a host with sVirt, aka anything RHEL-like or Fedora.
- evaluating if you should instead use microVMs for your workloads such as Firecracker.
Ensure your machine type is up to date
If you've a particularly old VM, the declared machine type may be quite old, you can likely bump it without issue. You can simply set q35 and it'll be adjusted to the latest version available. View all supported types using qemu-system-x86_64 -machine ?.
--- <unnamed>
+++ <unnamed>
@@ -10,7 +10,7 @@
<currentMemory unit="KiB">4194304</currentMemory>
<vcpu placement="static">2</vcpu>
<os firmware="efi">
- <type arch="x86_64" machine="pc-q35-8.1">hvm</type>
+ <type arch="x86_64" machine="pc-q35-10.2">hvm</type>
<firmware>
<feature enabled="yes" name="enrolled-keys"/>
<feature enabled="yes" name="secure-boot"/>
Make any disks disposable if possible
You can set the transient flag to ensure that any changes are written to a temporary overlay as opposed to the real virtual disk. Note: this is not RAM-backed so it has no benefit forensics wise.
--- <unnamed>
+++ <unnamed>
@@ -44,6 +44,7 @@
<driver name="qemu" type="qcow2"/>
<source file="testing.qcow2"/>
<target dev="vda" bus="virtio"/>
+ <transient shareBacking="yes"/>
<address type="pci" domain="0x0000" bus="0x04" slot="0x00" function="0x0"/>
</disk>
<controller type="usb" index="0" model="qemu-xhci" ports="15">
Replace legacy slirp with passt
slirp is still used as the default usermode networking backend. You can switch to the modern passt backend for both better performance and security.
--- <unnamed>
+++ <unnamed>
@@ -129,6 +129,7 @@
<interface type="user">
<mac address="de:ad:be:ef:ca:fe"/>
<model type="virtio"/>
+ <backend type="passt"/>
<address type="pci" domain="0x0000" bus="0x01" slot="0x00" function="0x0"/>
</interface>
<serial type="pty">
Remove the TPM
There are few uses for this under Linux. You most very likely do not need it.
--- <unnamed>
+++ <unnamed>
@@ -152,9 +152,6 @@
</input>
<input type="mouse" bus="ps2"/>
<input type="keyboard" bus="ps2"/>
- <tpm model="tpm-crb">
- <backend type="emulator" version="2.0"/>
- </tpm>
<graphics type="spice" autoport="yes">
<listen type="address"/>
<image compression="off"/>
Disable VirGL
VirGL is extremely fragile and provides a massive hole in your VM. It should only be used for ultimately trusted machines. Easy support for Venus and DRM native context will hopefully land soon, but those too should be used with caution when they do.
--- <unnamed>
+++ <unnamed>
@@ -158,7 +158,7 @@
<graphics type="spice">
<listen type="none"/>
<image compression="off"/>
- <gl enable="yes"/>
+ <gl enable="no"/>
</graphics>
<sound model="ich9">
<address type="pci" domain="0x0000" bus="0x00" slot="0x1b" function="0x0"/>
@@ -166,7 +166,7 @@
<audio id="1" type="spice"/>
<video>
<model type="virtio" heads="1" primary="yes">
- <acceleration accel3d="yes"/>
+ <acceleration accel3d="no"/>
</model>
<address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x0"/>
</video>
Disable sound
If you don't need a soundcard, disable it.
--- <unnamed>
+++ <unnamed>
@@ -159,10 +159,7 @@
<listen type="address"/>
<image compression="off"/>
</graphics>
- <sound model="ich9">
- <address type="pci" domain="0x0000" bus="0x00" slot="0x1b" function="0x0"/>
- </sound>
- <audio id="1" type="spice"/>
+ <audio id="1" type="none"/>
<video>
<model type="virtio" heads="1" primary="yes"/>
<address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x0"/>
Remove spice USB redirectors
If you don't need this runtime passthrough, remove it.
--- <unnamed>
+++ <unnamed>
@@ -167,12 +167,6 @@
<model type="virtio" heads="1" primary="yes"/>
<address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x0"/>
</video>
- <redirdev bus="usb" type="spicevmc">
- <address type="usb" bus="0" port="2"/>
- </redirdev>
- <redirdev bus="usb" type="spicevmc">
- <address type="usb" bus="0" port="3"/>
- </redirdev>
<watchdog model="itco" action="reset"/>
<memballoon model="virtio">
<address type="pci" domain="0x0000" bus="0x05" slot="0x00" function="0x0"/>
Remove the tablet input
If you don't need pleasant mouse function, you can remove this. I wouldn't recommend doing this for ones you're actually interactively using.
--- <unnamed>
+++ <unnamed>
@@ -147,9 +147,6 @@
<target type="virtio" name="com.redhat.spice.0"/>
<address type="virtio-serial" controller="0" bus="0" port="2"/>
</channel>
- <input type="tablet" bus="usb">
- <address type="usb" bus="0" port="1"/>
- </input>
<input type="mouse" bus="ps2"/>
<input type="keyboard" bus="ps2"/>
<tpm model="tpm-crb">
Remove virtual graphics
For headless machines, you can switch to using serial instead. Enable support in guest first: systemctl enable --now serial-getty@ttyS0.service.
--- <unnamed>
+++ <unnamed>
@@ -155,18 +155,10 @@
<tpm model="tpm-crb">
<backend type="emulator" version="2.0"/>
</tpm>
- <graphics type="spice" autoport="yes">
- <listen type="address"/>
- <image compression="off"/>
- </graphics>
<sound model="ich9">
<address type="pci" domain="0x0000" bus="0x00" slot="0x1b" function="0x0"/>
</sound>
<audio id="1" type="spice"/>
- <video>
- <model type="virtio" heads="1" primary="yes"/>
- <address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x0"/>
- </video>
<redirdev bus="usb" type="spicevmc">
<address type="usb" bus="0" port="2"/>
</redirdev>
Disable ps2 inputs
After switching to a serial TTY you can remove these.
--- <unnamed>
+++ <unnamed>
@@ -24,6 +24,7 @@
<apic/>
<vmport state="off"/>
<smm state="on"/>
+ <ps2 state="off"/>
</features>
<cpu mode="host-passthrough" check="none" migratable="on"/>
<clock offset="utc">
@@ -150,8 +151,6 @@
<input type="tablet" bus="usb">
<address type="usb" bus="0" port="1"/>
</input>
- <input type="mouse" bus="ps2"/>
- <input type="keyboard" bus="ps2"/>
<tpm model="tpm-crb">
<backend type="emulator" version="2.0"/>
</tpm>
Remove the spice channel
Once the spice USB redirectors, sound, and display is removed you can remove its channel.
--- <unnamed>
+++ <unnamed>
@@ -143,10 +143,6 @@
<target type="virtio" name="org.qemu.guest_agent.0"/>
<address type="virtio-serial" controller="0" bus="0" port="1"/>
</channel>
- <channel type="spicevmc">
- <target type="virtio" name="com.redhat.spice.0"/>
- <address type="virtio-serial" controller="0" bus="0" port="2"/>
- </channel>
<input type="tablet" bus="usb">
<address type="usb" bus="0" port="1"/>
</input>
Complete example for a headless machine
Here is a snippet combining the above for a final result.
--- <unnamed>
+++ <unnamed>
@@ -24,6 +24,7 @@
<apic/>
<vmport state="off"/>
<smm state="on"/>
+ <ps2 state="off"/>
</features>
<cpu mode="host-passthrough" check="none" migratable="on"/>
<clock offset="utc">
@@ -129,6 +130,7 @@
<interface type="user">
<mac address="52:54:00:03:76:a1"/>
<model type="virtio"/>
+ <backend type="passt"/>
<address type="pci" domain="0x0000" bus="0x01" slot="0x00" function="0x0"/>
</interface>
<serial type="pty">
@@ -143,36 +145,7 @@
<target type="virtio" name="org.qemu.guest_agent.0"/>
<address type="virtio-serial" controller="0" bus="0" port="1"/>
</channel>
- <channel type="spicevmc">
- <target type="virtio" name="com.redhat.spice.0"/>
- <address type="virtio-serial" controller="0" bus="0" port="2"/>
- </channel>
- <input type="tablet" bus="usb">
- <address type="usb" bus="0" port="1"/>
- </input>
- <input type="mouse" bus="ps2"/>
- <input type="keyboard" bus="ps2"/>
- <tpm model="tpm-crb">
- <backend type="emulator" version="2.0"/>
- </tpm>
- <graphics type="spice" autoport="yes">
- <listen type="address"/>
- <image compression="off"/>
- </graphics>
- <sound model="ich9">
- <address type="pci" domain="0x0000" bus="0x00" slot="0x1b" function="0x0"/>
- </sound>
- <audio id="1" type="spice"/>
- <video>
- <model type="virtio" heads="1" primary="yes"/>
- <address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x0"/>
- </video>
- <redirdev bus="usb" type="spicevmc">
- <address type="usb" bus="0" port="2"/>
- </redirdev>
- <redirdev bus="usb" type="spicevmc">
- <address type="usb" bus="0" port="3"/>
- </redirdev>
+ <audio id="1" type="none"/>
<watchdog model="itco" action="reset"/>
<memballoon model="virtio">
<address type="pci" domain="0x0000" bus="0x05" slot="0x00" function="0x0"/>
Cleanup leftovers
After you remove all of these you'll end up with many unused controller elements. You can remove all pci address line elements and pci/sata/scsi controller elements and they'll be correctly added back as necessary.
Comment on this: Fediverse