Files
vfio-native/scripts/libvirt-hook-cpuid-passthrough.sh

69 lines
2.7 KiB
Bash
Executable File

#!/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 <domain>`; 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