Boot Your Windows Partition from Linux using KVM
Using mdadm to create a linear RAID array, I wrap a Windows partition with virtual EFI partitions and boot the Windows partition in Linux using KVM and Qemu.
Why?
In the era of COVID-19 and with a newborn baby at home, I was careful to wipe down all of my work equipment before leaving each day. Unfortunately, I managed to damage my old laptop screen with isopropyl alcohol by not wiping it off before storing it in my bag at the end of a day at work. The display still works, but there now there is a permanent watermark on the display. Given that the giant 15 inch Asus gaming laptop[1] was over five years old, I used the damage as an excuse to replace it with a newer 13 inch Dell Latitude 7300 (which, I’ll note, I highly recommend so far for those looking to run Linux).
The new laptop came with a Windows 10 OEM license. Unlike the old days where OEM licenses came with a product key on a sticker, Windows 10 OEM licenses are tied to the hardware and cannot be recovered. Rather than copy my old licensed Win10 VM (via commercially purchased key), I decided to try to save the existing licensed partition. Doing so gives me the flexibility to keep the old VM on the old laptop, and start with a fresh build of Windows.
While I’d love to part ways with Windows permanently, there are some advantages to keeping it around, especially if you can run it natively on hardware. For me in particular, there is some software I need for work that can only run on Windows. Additionally, it may be useful to have Windows running directly on your hardware for things like gaming. It is convenient to be able to boot Windows as a virtual machine from Linux, and also reboot and boot from Windows directly if necessary.
Proof of Concept
After configuring a recovery disk and re-installing Windows, I booted into a Kubuntu 20.04 livedisk to test if booting a virtual machine from a partition even works. After installing KVM,[2] I followed this tutorial to get it up and running. After a bit of troubleshooting, I found the right configuration (archived link) to get Windows up and running from Linux.
Unfortunately, running from a live disk is different from my actual set up. While running from a live disk, I could just point KVM at the hard disk and boot it from a VM. In the actual set up, I share the same EFI boot partition across two separate operating systems. Even if I could mount the boot partition as read-only, I run into the issue where my default configuration - booting Linux - would cause the VM to try to boot into Linux as well. This is a bad. idea[3]
I spent some time trying to create my own efi partition as a virtual file and having it point to the Windows partition, but this does not work because EFI needs to be on the same physical disk as the OS partition.[4] So rather than try to pass several partitions through as separate disks, I found success in building a linear RAID virtual disk that combines the EFI files and the Windows partition into a single disk. This tutorial lays out the steps correctly (archived link).
Warning | When following that guide, double check your offsets or you’ll spend a couple of iterations trying to figure out why your partition isn’t booting correctly. |
My Solution
I break my walk-through into two phases:
installing Windows in a dual-boot configuration
configuring a linear RAID virtual disk to boot the Windows partition.
I also assume you already have kvm installed on a modern Linux kernel. I am running on Kubuntu 20.04.
Configure Dual-Booting
There are plenty of guides to walk you through getting dual-boot configured. Essentially these are the steps:
Install fresh windows
Resize Windows partition
Add your Linux partitions
Install Linux (which should auto-install grub in UEFI mode)
When I got done with this step, my disk looked like this
fdisk -l /dev/nvme0n1
[...]
Device Start End Sectors Size Type
/dev/nvme0n1p1 2048 206847 204800 100M EFI System
/dev/nvme0n1p2 206848 239615 32768 16M Microsoft reserved
/dev/nvme0n1p3 239616 126068735 125829120 60G Microsoft basic data
/dev/nvme0n1p4 999223296 1000214527 991232 484M Windows recovery environment
/dev/nvme0n1p5 126068736 966019071 839950336 400.5G Linux filesystem
/dev/nvme0n1p6 966019072 999221247 33202176 15.9G Linux swap
Building the virtual disk
The tutorial I followed is fairly thorough, so I will only summarize the steps. Following this tutorial (archived link), I did the following:
Create files,
efi1
andefi2
of size 100M and 1M, respectively. The former holds the partition table and efi partition, while the while the second is padding for a backup of the partition table.Create virtual loop devices for the new files.
Use mdadm to create a linear disk in the order:
efi1
,/dev/nvme0n1p3
,efi2
; it will be accessible at /dev/mdXUse parted to write a partition table to the EFI partition on the virtual disk at /dev/mdX
Locate and use the OVMF EFI bios, which, when installed, can be found with the command
find / -iname "ovmf"
– if you don’t have it, you can install it from the Ubuntu repos.Using a Windows installation ISO, boot your virtual machine into a Windows installer, open the console, and use diskpart and bcdboot to configure the EFI volume and then create a BCD boot entry.
Note | when working through this guide, I recommend using the qemu command given in the tutorial and modifying it for your use:qemu-system-x86_64 -bios /usr/share/ovmf/ovmf_x64.bin -drive file=/dev/md0,media=disk,format=raw -cpu host -enable-kvm -m 2G -cdrom /path/to/windows.iso |
Once complete, you need to rebuild the virtual disk using mdam every time you reboot your host. I use this script:
sudo modprobe loop
sudo modprobe linear
LOOP1=$(sudo losetup -f)
sudo losetup ${LOOP1} /home/bobbytables/VM/win10/efi1
LOOP2=$(sudo losetup -f)
sudo losetup ${LOOP2} /home/bobbytables/VM/win10/efi2
sudo mdadm --build --verbose /dev/md0 --chunk=512 --level=linear \
--raid-devices=3 ${LOOP1} /dev/nvme0n1p3 ${LOOP2}
Note | Special thanks to Frédéric, who found I had a typo in my mdadm command above. |
Once I got the virtual disk working correctly, I opted to keep the nvram configuration from the initial tutorial I referenced earlier. I admit that I don’t know enough about EFI to know if I absolutely need to keep the nvram included, but I already had it configured so I left it in my VM.
Final Touches
From here, you can start the VM use the qemu commands given in the tutorial I linked. Once I got mine working via the command line, I integrated it into virt-manager to streamline the normal convenient features that virt-manager provides, like shared clipboard, share folder, and so on. You don’t have to use virt-manager for these things, but it can be convenient at times.
For this, I found the easiest way is to create a virtual machine in virt-manager first and then edit the XML with my custom changes that virt-manager does not support via the GUI. I add the /dev/md0 partition as a disk and add the bios files for EFI to the XML manually. I kept my graphics at the default QXL. My<os></os>
section XML looks like this:
<os>
<type arch="x86_64" machine="pc-q35-4.2">hvm</type>
<loader readonly="yes" type="pflash">/usr/share/OVMF/OVMF_CODE.fd</loader>
<nvram>/var/lib/libvirt/qemu/nvram/win10_VARS.fd</nvram>
<boot dev="hd"/>
</os>
Note | In the above, I copied the default nvram from/usr/share/OVMF/OVMF_VARS.fd to/var/lib/libvirt/qemu/nvram/win10_VARS.fd |
You can find an example of my virt-manager configuration here.
Windows VirtIO Drivers and Guest Agent
Details on KVM Windows VirtIO Drivers are available from linux-kvm.org.
I downloaded the latest virtio Windows drivers ISO from Fedora here. I then added that ISO to Windows and install the drivers onto the Windows guest. Specifically I added guest-agent, and then pointed the Windows Device Manager at the disk drive to find missing drivers for the features I was using. Shared clipboard should work out of the box once the guest-agent is installed.
Once I installed my drivers and reboot the guest, I can resize screen by setting the checkbox in the virt-manager menu View → Scale Display → "Auto resize VM with Window".
Conclusion
My requirement to boot a partition from Linux may be an edge case for most, but I found it quite convenient to keep my Windows partition and license while still using Linux as my primary OS. I do periodically run into license warnings, but they disappear when I reboot into Windows directly (ironically, I have the opposite problem with my Office license: it works in the VM, but not when booted directly on hardware).
Unfortunately I brush over some of the final touches, as I finished this post several months after initially starting it. If there are specific points you get stuck on, feel free to ping me on Twitter and I can try to help.
Reader Feedback
A few readers have wrote in with tips and comments. Special thanks to Georg who shares the following note and a script:
I have used it successfully on my PC to boot my Windows partition through KVM even on startup. I found that the following script in /etc/libvirt/hooks/qemu helps with that, because it automagically assembles /dev/md0 from the different files and disks, and then tears it down when the VM stops. "win10" is the name of the VM, yours may differ and you may have to change it, and "Windows-VM" is the directory I have the VM related loopback files in:
#!/bin/bash if [[ $1 == "win10" ]] && [[ $2 == "prepare" || $2 == "stopped" ]] ; then if [[ $2 == "prepare" ]] ; then # startup logic here modprobe loop modprobe linear LOOP1=$(sudo losetup -f) # echo "Loop device 1 is $LOOP1" losetup ${LOOP1} /home/user/Windows-VM/efi1 LOOP2=$(sudo losetup -f) # echo "Loop device 2 is $LOOP2" losetup ${LOOP2} /home/user/Windows-VM/efi2 mdadm --build --verbose /dev/md0 --chunk=512 --level=linear --raid-devices=5 ${LOOP1} /dev/nvme1n1p2 /dev/nvme1n1p3 /dev/nvme1n1p4 ${LOOP2} else # shutdown logic here echo "Stopping array" mdadm --stop /dev/md0 echo "removing loopback devices" losetup | grep "Windows-VM" | awk '{print $1}' | xargs sudo losetup -d fi fi
Glossary:
- EFI
EFI, short of UEFI, stands for Unified Extensible Firmware Interface. It is an OS-independent partition to store the bootloader. It is supposed to support some secure extensions as well, but it is a bit of a mixed bag in terms of security.
- KVM
KVM stands for the Kernel-based Virtual Machine. The official web page describes itself as a full virtualization solution for Linux on x86 hardware containing virtualization extensions (Intel VT or AMD-V). Additionally, the user space component for KVM exists in the QEMU emulator and virtualizer.
Footnotes:
virt-manager
as a package to bootstrap your exposure to the hypervisor. In the Ubuntu repos, if you include the recommended packages, you’ll get kvm, qemu, libvirt, and a nice GUI to help you get started