diff --git a/README.md b/README.md index c8072e4..c45e3d3 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ comprehensive support for macOS and Windows**. * Automatic SSH port forwarding to guests * Network port forwarding * Full duplex audio - * EFI and Legacy BIOS booting + * EFI (with or without SecureBoot) and Legacy BIOS boot * Graphical user interfaces available Quickemu is a wrapper for the excellent [QEMU](https://www.qemu.org/) that @@ -62,6 +62,7 @@ See this (old) video where I explain some of my motivations for creating Quickem * [QEMU](https://www.qemu.org/) (*6.0.0 or newer*) * [bash](https://www.gnu.org/software/bash/) (*4.0 or newer*) * [Coreutils](https://www.gnu.org/software/coreutils/) + * [EDK II](https://github.com/tianocore/edk2) * [grep](https://www.gnu.org/software/grep/) * [jq](https://stedolan.github.io/jq/) * [LSB](https://wiki.linuxfoundation.org/lsb/start) diff --git a/quickemu b/quickemu index ab04ac8..1d3d0fa 100755 --- a/quickemu +++ b/quickemu @@ -175,8 +175,25 @@ function check_cpu_flag() { fi } +function efi_vars() { + local VARS_IN="" + local VARS_OUT="" + VARS_IN="${1}" + VARS_OUT="${2}" + + if [ ! -e "${VARS_OUT}" ]; then + if [ -e "${VARS_IN}" ]; then + cp "${VARS_IN}" "${VARS_OUT}" + else + echo "ERROR! ${VARS_IN} was not found. Please install edk2." + exit 1 + fi + fi +} + function vm_boot() { local BALLOON="-device virtio-balloon" + local BOOT_STATUS="" local CPU="" local DISK_USED="" local DISPLAY_DEVICE="" @@ -194,13 +211,15 @@ function vm_boot() { local KERNEL_NAME="Unknown" local KERNEL_NODE="" local KERNEL_VER="?" - local LSB_DESCRIPTION="" + local LSB_DESCRIPTION="Unknown OS" local MAC_BOOTLOADER="" local MAC_MISSING="" local MAC_DISK_DEV="ide-hd,bus=ahci.2" local MOUSE="usb-tablet" local NET_DEVICE="virtio-net" local OSK="" + local QEMU_VER="" + local SMM="off" local USB_HOST_PASSTHROUGH_CONTROLLER="qemu-xhci" local VIDEO="" @@ -210,6 +229,8 @@ function vm_boot() { if command -v lsb_release &>/dev/null; then LSB_DESCRIPTION=$(lsb_release --description --short) + elif [ -e /etc/os-release ]; then + LSB_DESCRIPTION=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2) fi echo "Quickemu ${VERSION} using ${QEMU} v${QEMU_VER_LONG}" @@ -306,7 +327,6 @@ function vm_boot() { # Always Boot macOS using EFI if [ "${guest_os}" == "macos" ]; then boot="efi" - echo " - BOOT: EFI (${guest_os})" if [ -e "${VMDIR}/OVMF_CODE.fd" ] && [ -e "${VMDIR}/OVMF_VARS-1024x768.fd" ]; then EFI_CODE="${VMDIR}/OVMF_CODE.fd" EFI_VARS="${VMDIR}/OVMF_VARS-1024x768.fd" @@ -328,6 +348,7 @@ function vm_boot() { echo " Use 'quickget' to download the required files." exit 1 fi + BOOT_STATUS="EFI (macOS), OVMF ($(basename "${EFI_CODE}")), SecureBoot (${secureboot})." elif [[ "${boot}" == *"efi"* ]]; then EFI_VARS="${VMDIR}/OVMF_VARS.fd" @@ -338,36 +359,73 @@ function vm_boot() { mv "${VMDIR}/OVMF_VARS_4M.fd" "${EFI_VARS}" fi - if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ] || - [ -e "/usr/share/OVMF/x64/OVMF_CODE.fd" ] || - [ -e "/usr/share/OVMF/OVMF_CODE.fd" ]; then - echo " - BOOT: EFI (${guest_os})" - - if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ]; then - EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.fd" - elif [ -e "/usr/share/OVMF/x64/OVMF_CODE.fd" ]; then - EFI_CODE="/usr/share/OVMF/x64/OVMF_CODE.fd" - elif [ -e "/usr/share/OVMF/OVMF_CODE.fd" ]; then - EFI_CODE="/usr/share/OVMF/OVMF_CODE.fd" - fi + # OVMF_CODE_4M.fd is for booting guests in non-Secure Boot mode. + # While this image technically supports Secure Boot, it does so + # without requiring SMM support from QEMU + + # OVMF_CODE.secboot.fd is like OVMF_CODE_4M.fd, but will abort if QEMU + # does not support SMM. - if [ ! -e "${EFI_VARS}" ]; then - if [ -e "/usr/share/OVMF/OVMF_VARS_4M.fd" ]; then - cp "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}" - elif [ -e "/usr/share/OVMF/x64/OVMF_VARS.fd" ]; then - cp "/usr/share/OVMF/x64/OVMF_VARS.fd" "${EFI_VARS}" - elif [ -e "/usr/share/OVMF/OVMF_VARS.fd" ]; then - cp "/usr/share/OVMF/OVMF_VARS.fd" "${EFI_VARS}" + # https://bugzilla.redhat.com/show_bug.cgi?id=1929357#c5 + case ${secureboot} in + on) + if [ -e "/usr/share/OVMF/OVMF_CODE_4M.secboot.fd" ]; then + EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.secboot.fd" + efi_vars "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}" + elif [ -e "/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd" ]; then + EFI_CODE="/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd" + efi_vars "/usr/share/edk2/ovmf/OVMF_VARS.fd" "${EFI_VARS}" + elif [ -e "/usr/share/OVMF/x64/OVMF_CODE.secboot.fd" ]; then + EFI_CODE="/usr/share/OVMF/x64/OVMF_CODE.secboot.fd" + efi_vars "/usr/share/OVMF/x64/OVMF_VARS.fd" "${EFI_VARS}" + else + echo "ERROR! SecureBoot was requested but no SecureBoot capable firmware was found." + echo " Please install OVMF firmware." + exit 1 fi - fi - else - boot="legacy" - echo " - BOOT: Legacy BIOS (${guest_os}) - EFI requested but no EFI firmware found." + ;; + *) + if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ]; then + EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.fd" + efi_vars "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}" + elif [ -e "/usr/share/edk2/ovmf/OVMF_CODE.fd" ]; then + EFI_CODE="/usr/share/edk2/ovmf/OVMF_CODE.fd" + efi_vars "/usr/share/edk2/ovmf/OVMF_VARS.fd" "${EFI_VARS}" + elif [ -e "/usr/share/OVMF/x64/OVMF_CODE.fd" ]; then + EFI_CODE="/usr/share/OVMF/x64/OVMF_CODE.fd" + efi_vars "/usr/share/OVMF/x64/OVMF_VARS.fd" "${EFI_VARS}" + else + echo "ERROR! EFI boot requested but no EFI firmware found." + echo " Please install OVMF firmware." + exit 1 + fi + ;; + esac + + # Make sure EFI_VARS references an actual, writeable, file + if [ ! -f "${EFI_VARS}" ] || [ ! -w "${EFI_VARS}" ]; then + echo " - EFI: ERROR! ${EFI_VARS} is not a regular file or not writeable." + echo " Deleting ${EFI_VARS}. Please re-run quickemu." + rm -f "${EFI_VARS}" + exit 1 + fi + + # If EFI_CODE references a symlink, resolve it to the real file. + if [ -L "${EFI_CODE}" ]; then + echo " - EFI: WARNING! ${EFI_CODE} is a symlink." + echo -n " Resolving to... " + EFI_CODE=$(realpath "${EFI_CODE}") + echo "${EFI_CODE}" fi + BOOT_STATUS="EFI (${guest_os^}), OVMF (${EFI_CODE}), SecureBoot (${secureboot})." else - echo " - BOOT: Legacy BIOS (${guest_os})" + BOOT_STATUS="Legacy BIOS (${guest_os^})" + boot="legacy" + secureboot="off" fi + echo " - BOOT: ${BOOT_STATUS}" + # Make any OS specific adjustments case ${guest_os} in freebsd|linux|openbsd) @@ -443,6 +501,7 @@ function vm_boot() { if [ -z "${disk_size}" ]; then disk_size="64G" fi + SMM="on" ;; *) CPU="-cpu host,kvm=on" @@ -454,6 +513,11 @@ function vm_boot() { ;; esac + # Disable suspend to RAM if SecureBoot/SMM is enabled + if [ "${secureboot}" == "on" ] || [ "${SMM}" == "on" ]; then + GUEST_TWEAKS="${GUEST_TWEAKS} -global ICH9-LPC.disable_s3=1" + fi + echo " - Disk: ${disk_img} (${disk_size})" if [ ! -f "${disk_img}" ]; then # If there is no disk image, create a new image. @@ -531,20 +595,6 @@ function vm_boot() { echo " - CD-ROM: ${fixed_iso}" fi - # Enable TPM - if [ "${tpm}" == "on" ]; then - if command -v swtpm &>/dev/null; then - swtpm socket \ - --ctrl type=unixio,path="${VMDIR}/${VMNAME}.swtpm-sock" \ - --terminate \ - --tpmstate dir="${VMDIR}" \ - --tpm2 & - echo " - TPM: ${VMDIR}/${VMNAME}.swtpm-sock (${!})" - else - echo " - TPM: swtpm is not installed, TPM not available!" - fi - fi - # Determine a sane resolution for Linux guests. if [ "${guest_os}" == "linux" ]; then local X_RES=1152 @@ -716,12 +766,29 @@ function vm_boot() { enable_usb_passthrough + echo "#!/usr/bin/env bash" > "${VMDIR}/${VMNAME}.sh" + + # Start TPM + if [ "${tpm}" == "on" ]; then + local tpm_args=() + # shellcheck disable=SC2054 + tpm_args+=(socket + --ctrl type=unixio,path="${VMDIR}/${VMNAME}.swtpm-sock" + --terminate + --tpmstate dir="${VMDIR}" + --tpm2) + echo "${SWTPM} ${tpm_args[@]} &" >> "${VMDIR}/${VMNAME}.sh" + ${SWTPM} "${tpm_args[@]}" >> "${VMDIR}/${VMNAME}.log" & + echo " - TPM: ${VMDIR}/${VMNAME}.swtpm-sock (${!})" + sleep 1 + fi + # Boot the VM local args=() # shellcheck disable=SC2054,SC2206,SC2140 args+=(-name ${VMNAME},process=${VMNAME} -pidfile "${VMDIR}/${VMNAME}.pid" - -enable-kvm -machine q35,vmport=off ${GUEST_TWEAKS} + -enable-kvm -machine q35,smm=${SMM},vmport=off ${GUEST_TWEAKS} ${CPU} ${SMP} -m ${RAM_VM} ${BALLOON} -smbios type=2,manufacturer="Wimpys World",product="Quickemu",version="${VERSION}",serial="jvzclfjbeyq.pbz",location="wimpysworld.com",asset="${VMNAME}" @@ -754,8 +821,9 @@ function vm_boot() { # - https://turlucode.com/qemu-disk-io-performance-comparison-native-or-threads-windows-10-version/ if [[ "${boot}" == *"efi"* ]]; then # shellcheck disable=SC2054 - args+=(-drive if=pflash,format=raw,file="${EFI_CODE}",readonly=on - -drive if=pflash,format=raw,file="${EFI_VARS}") + args+=(-global driver=cfi.pflash01,property=secure,value=on + -drive if=pflash,format=raw,unit=0,file="${EFI_CODE}",readonly=on + -drive if=pflash,format=raw,unit=1,file="${EFI_VARS}") fi if [ -n "${floppy}" ]; then @@ -841,9 +909,7 @@ function vm_boot() { SHELL_ARGS="${SHELL_ARGS//)/\\)}" SHELL_ARGS="${SHELL_ARGS//Wimpys World/\"Wimpys World\"}" - echo "#!/usr/bin/env bash" > "${VMDIR}/${VMNAME}.sh" echo "${QEMU}" "${SHELL_ARGS}" >> "${VMDIR}/${VMNAME}.sh" - ${QEMU} "${args[@]}" > "${VMDIR}/${VMNAME}.log" & # If output is 'none' then SPICE was requested. @@ -909,6 +975,7 @@ macos_release="" port_forwards=() preallocation="off" ram="" +secureboot="off" tpm="off" usb_devices=() @@ -1054,6 +1121,14 @@ if [ -n "${VM}" ] && [ -e "${VM}" ]; then if [ -n "${disk}" ]; then disk_size="${disk}" fi + + if [ "${tpm}" == "on" ]; then + SWTPM=$(command -v swtpm) + if [ ! -e "${SWTPM}" ]; then + echo "ERROR! TPM is enabled, but swtpm was not found." + exit 1 + fi + fi else echo "ERROR! Virtual machine configuration not found." usage