#!/bin/bash # libvirt qemu hook: drive the kvm_amd cpuid_passthrough switch around a guest's life. # # The switch must be OFF while a guest cold-boots - raw CPUID changes shape mid # enumeration and Windows hangs - and ON once the guest is up, where it clears the # TIMER detection. This applies only to the domains you opt in with # `vm-native-cpuid enable `; it does nothing to any other guest. # # On start it sets the brand string from the guest's declared SKU and forces N, # then a detached watcher flips Y once the guest's network is up; on stop it sets N. # cpuid_passthrough and brand_string are one global kvm_amd parameter, so a single # hardened guest is served at a time; a second is logged and left alone. # # Exits 0 on every path. The hook never calls virsh (that deadlocks libvirtd); the # watcher that does is a transient systemd unit and runs after this returns. CONF=/etc/vfio-native/cpuid-passthrough.conf ENABLED=yes DOMAINS="" [ -r "$CONF" ] && . "$CONF" [ "$ENABLED" = yes ] || exit 0 PARAM=/sys/module/kvm_amd/parameters [ -w "$PARAM/cpuid_passthrough" ] || exit 0 # stock module, or not an AMD host DOMAIN=$1 OPERATION=$2 case " $DOMAINS " in *" $DOMAIN "*) ;; *) exit 0 ;; esac # not an opted-in domain RUN=/run/vfio-native ACTIVE=$RUN/cpuid-active WATCH=/usr/lib/vfio-native/cpuid-passthrough-watch case "$OPERATION" in prepare) # the guest's declared SKU, straight from the -cpu model-id in the XML on stdin BRAND=$(grep -oE "model-id=[^'\"]+" | head -1 | sed 's/^model-id=//') [ -n "$BRAND" ] || exit 0 # not a full-fidelity guest mkdir -p "$RUN" held=$( [ -e "$ACTIVE" ] && cut -d: -f1 "$ACTIVE" ) if [ -n "$held" ] && [ "$held" != "$DOMAIN" ]; then logger -t vfio-cpuid "cpuid passthrough held by $held; $DOMAIN left unhardened (one guest at a time)" exit 0 fi printf '%s' "$BRAND" > "$PARAM/brand_string" echo N > "$PARAM/cpuid_passthrough" # safe for the cold boot printf '%s:%s' "$DOMAIN" "$BRAND" > "$ACTIVE" logger -t vfio-cpuid "$DOMAIN start: brand='$BRAND' cpuid_passthrough=N, watcher armed" # transient systemd unit, not a bare background job: libvirt reaps anything left # in the hook's own process tree; systemd-run escapes it [ -x "$WATCH" ] && systemd-run --collect --quiet \ --unit="vfio-cpuid-watch-${DOMAIN}" "$WATCH" "$DOMAIN" "$BRAND" ;; release|stopped) held=$( [ -e "$ACTIVE" ] && cut -d: -f1 "$ACTIVE" ) [ "$held" = "$DOMAIN" ] || exit 0 systemctl stop "vfio-cpuid-watch-${DOMAIN}.service" 2>/dev/null echo N > "$PARAM/cpuid_passthrough" rm -f "$ACTIVE" logger -t vfio-cpuid "$DOMAIN $OPERATION: cpuid_passthrough=N" ;; esac exit 0