linux:apps:kvm:kvm-hardening
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| linux:apps:kvm:kvm-hardening [2026/09/13 13:19] – [How to harden it:] oscar | linux:apps:kvm:kvm-hardening [2026/09/16 16:31] (current) – [Network containing] oscar | ||
|---|---|---|---|
| Line 27: | Line 27: | ||
| * The Threat: A malicious process running inside your Debian guest could potentially exploit these hardware flaws to break out of the virtual machine boundary and read the memory of your host system. | * The Threat: A malicious process running inside your Debian guest could potentially exploit these hardware flaws to break out of the virtual machine boundary and read the memory of your host system. | ||
| - | ### How to check your current CPU model: | + | #### How to check your current CPU model: |
| 1. Open Virt-Manager and open your VM details (lightbulb icon). | 1. Open Virt-Manager and open your VM details (lightbulb icon). | ||
| Line 46: | Line 46: | ||
| To limit a KVM guest to only reach one specific gateway, you must block all other traffic using iptables or nftables on the Linux host. | To limit a KVM guest to only reach one specific gateway, you must block all other traffic using iptables or nftables on the Linux host. | ||
| Because virt-manager does not have a built-in button for this, the cleanest way is to use a libvirt hook script. This script automatically applies network restrictions every time the VM starts. | Because virt-manager does not have a built-in button for this, the cleanest way is to use a libvirt hook script. This script automatically applies network restrictions every time the VM starts. | ||
| - | ## Step 1: Find your VM's network interface | + | #### Step 1: Find your VM's network interface |
| - | Run this command on your host to find the virtual interface name while the VM is running: | + | Run this command on your host to find the virtual interface name while the VM is running |
| ``` | ``` | ||
| # virsh domiflist < | # virsh domiflist < | ||
| + | -------------------------------- | ||
| + | | ||
| + | ------------------------------------------------------------- | ||
| + | | ||
| ``` | ``` | ||
| - | Note the Interface name (e.g., vnet0 or vnet1). | + | #### Step 2: Create a Libvirt hook script |
| - | ## Step 2: Create a Libvirt hook script | + | Libvirt can execute scripts when VMs start. We will use a qemu hook to isolate the traffic. Open a terminal on your host and create the hook directory if it does not exist. |
| - | Libvirt can execute scripts when VMs start. We will use a qemu hook to isolate the traffic. | + | |
| - | + | ||
| - | Open a terminal on your host and create the hook directory if it does not exist. | + | |
| ``` | ``` | ||
| - | # mkdir -p / | + | # mkdir -p / |
| ``` | ``` | ||
| Create and edit the hook file and paste the following script (replace vnet0 and 192.168.122.1 with your actual interface and allowed gateway): | Create and edit the hook file and paste the following script (replace vnet0 and 192.168.122.1 with your actual interface and allowed gateway): | ||
| ``` | ``` | ||
| - | # nano / | + | # nano / |
| - | ------------------------------ | + | ------------------------------------------------------- |
| #!/bin/bash | #!/bin/bash | ||
| - | VM_NAME=" | + | set -euo pipefail |
| - | INTERFACE="vnet0" | + | |
| - | ALLOWED_GATEWAY=" | + | NFT="/ |
| + | VM_NAME=" | ||
| + | VM_MAC=$(/ | ||
| + | LIBVIRT_BRIDGE=" | ||
| + | HOST_INTERFACE="enp2s0" | ||
| + | BLOCKED_IPV4_RANGES="{ 192.168.0.0/16 }" | ||
| + | BLOCKED_IPV6_RANGES=" | ||
| + | TABLE=" | ||
| + | CHAIN=" | ||
| + | |||
| + | install_rules() | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | |||
| + | # Allow established and related return traffic | ||
| + | " | ||
| + | |||
| + | # Allow only VM -> 192.168.178.1 | ||
| + | "$NFT" add rule inet " | ||
| + | " | ||
| + | |||
| + | # ALLOW remaining traffic from the VM out to the internet via enp2s0 | ||
| + | " | ||
| + | } | ||
| + | |||
| + | remove_rules() | ||
| + | { | ||
| + | " | ||
| + | } | ||
| + | |||
| + | case " | ||
| + | start) | ||
| + | [[ " | ||
| + | ;; | ||
| + | |||
| + | stopped) | ||
| + | [[ " | ||
| + | ;; | ||
| + | esac | ||
| + | |||
| + | exit 0 | ||
| - | OBJECT=$1 | ||
| - | OPERATION=$2 | ||
| - | if [ " | ||
| - | if [ " | ||
| - | # 1. Allow traffic to the specific gateway IP | ||
| - | iptables -I FORWARD -m physdev --physdev-in $INTERFACE -d $ALLOWED_GATEWAY -j ACCEPT | ||
| - | iptables -I FORWARD -m physdev --physdev-out $INTERFACE -s $ALLOWED_GATEWAY -j ACCEPT | ||
| - | | ||
| - | # 2. Drop all other traffic from this interface | ||
| - | iptables -A FORWARD -m physdev --physdev-in $INTERFACE -j DROP | ||
| - | iptables -A FORWARD -m physdev --physdev-out $INTERFACE -j DROP | ||
| - | fifi | ||
| ``` | ``` | ||
| - | ## Step 3: Make the script executable and restart libvirt | + | #### Step 3: Make the script executable and restart libvirt |
| Make the script executable: | Make the script executable: | ||
| ``` | ``` | ||
| Line 98: | Line 129: | ||
| Now, every time you start this specific VM, the host firewall will block all network traffic unless it is explicitly sent to or from your chosen gateway IP. | Now, every time you start this specific VM, the host firewall will block all network traffic unless it is explicitly sent to or from your chosen gateway IP. | ||
| If you are using nftables instead of iptables, or if you need help finding the exact subnet/ | If you are using nftables instead of iptables, or if you need help finding the exact subnet/ | ||
| + | |||
| + | Check if the firewall settings are created and removed with: | ||
| + | # nft list ruleset | ||
| + | The firewall settings will look something like: | ||
| + | ``` | ||
| + | table inet vm_filter { | ||
| + | chain vm_Debian13_ESP { | ||
| + | type filter hook forward priority raw; policy accept; | ||
| + | ct state established, | ||
| + | ether saddr 51: | ||
| + | ether saddr 51: | ||
| + | ether saddr 51: | ||
| + | } | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | ## Kernel Samepage Merging (KSM) | ||
| + | In Linux, KSM (Kernel Samepage Merging) is a feature that scans your system' | ||
| + | |||
| + | * The ksm service controls the actual raw kernel thread (ksmd) that does the heavy lifting. | ||
| + | * The ksmtuned service is a user-space daemon. It actively watches your system and dynamically adjusts the tuning parameters of the kernel thread (like how fast it scans memory) based on how many QEMU/KVM instances are currently running. | ||
| + | |||
| + | #### The Security Risk: Side-Channel Attacks | ||
| + | While KSM is great for saving RAM on high-density servers, it introduces a major security flaw called a Memory Side-Channel / Cache-Leak Attack. [1, 2] | ||
| + | Because memory pages are shared between the host and the guest, a malicious process inside your Debian guest can perform precise timing measurements on memory access. By deliberately modifying a page and measuring how long it takes to process, the guest can figure out if that same data exists elsewhere in the host system' | ||
| + | |||
| + | Even if you only run a single Debian guest, KSM can still merge identical pages between your one guest and the Debian host operating system. If ksmtuned kicks in and starts aggressive page merging, the side-channel attack vector remains open between your isolated guest and your underlying hypervisor. | ||
| + | |||
| + | #### Check if KSM is running | ||
| + | To check if ksmtuned and the underlying Kernel Samepage Merging (KSM) engine are active on your Debian Trixie host, run these diagnostic commands: | ||
| + | systemctl status ksmtuned ksm | ||
| + | |||
| + | What to look for: If it says Active: active (running), it is active. If it says Active: inactive (dead) or loaded (...; masked), it is successfully disabled. | ||
| + | |||
| + | #### How to completely disable KSM | ||
| + | To ensure no memory deduplication happens at all, you should permanently stop and disable both the tuning daemon and the kernel driver: [5] | ||
| + | |||
| + | 1. Stop and mask the services so they can never be started by other system triggers: | ||
| + | |||
| + | sudo systemctl disable --now ksm ksmtuned | ||
| + | sudo systemctl mask ksm ksmtuned | ||
| + | |||
| + | 2. Force the kernel to instantly unshare any pages it has already merged: | ||
| + | |||
| + | echo 2 | sudo tee / | ||
| + | |||
| + | (Setting this to 2 completely disables the engine and forces the kernel to unmerge everything immediately. Setting it to 0 only stops it from scanning new pages). [2, 5] | ||
linux/apps/kvm/kvm-hardening.1789305565.txt.gz · Last modified: by oscar
