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:

  1. Install fresh windows

  2. Resize Windows partition

  3. Add your Linux partitions

  4. 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:

  1. Create files,efi1andefi2of 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.

  2. Create virtual loop devices for the new files.

  3. Use mdadm to create a linear disk in the order:efi1,/dev/nvme0n1p3,efi2; it will be accessible at /dev/mdX

  4. Use parted to write a partition table to the EFI partition on the virtual disk at /dev/mdX

  5. Locate and use the OVMF EFI bios, which, when installed, can be found with the commandfind / -iname "ovmf"– if you don’t have it, you can install it from the Ubuntu repos.

  6. 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.fdto/var/lib/libvirt/qemu/nvram/win10_VARS.fd

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:


1. I don’t game very much, but I spent a lot of time traveling, so having a large display and beefy specs helps a lot while traveling and running multiple VMs
2. If you’re new to kvm, I recommend usingvirt-manageras 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
3. Although I’ve never tried it. Now I’m kind of curious what would happen
4. As I write this and look back for evidence that led to my original conclusion, I can’t find anything that says this. If this is wrong, please ping me on Twitter and let me know.