From: Fiona Ebner <f.ebner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH qemu-server 2/2] fix #6985: ovmf: auto-enroll Microsoft UEFI CA 2023 for Windows
Date: Tue, 11 Nov 2025 14:57:54 +0100 [thread overview]
Message-ID: <20251111135808.110791-3-f.ebner@proxmox.com> (raw)
In-Reply-To: <20251111135808.110791-1-f.ebner@proxmox.com>
Microsoft's UEFI CA 2011 will expire in June 2026. It's necessary to
ensure that the new UEFI CA 2023 is enrolled for Windows to keep
updates working.
pve-edk2-firmware >= 4.2025.05-1 includes the 2023 certificate
already, so new disks are fine. Still, check during EFI disk creation
what is actually there, since there is no guarantee that a new enough
version of pve-edk2-firmware is installed.
A new 'ms-cert' drive property for EFI disks records the year of the
last known-to-be-enrolled MS UEFI CA. This avoids the need to re-check
every time if the 2023 certificate needs to be enrolled. The downside
is that this breaks backwards migration, because a disk with an
unknown option is dropped from the configuration.
Enrollment and checking for existing disks is done via virt-fw-vars,
which is a new dependency recorded in d/control. While virt-fw-vars
supports raw and qcow2 files out of the box, this is not enough,
because EFI disks can also be vmdk formatted and there are also
storages that use a protocol path like 'rbd://' or 'iscsi://' for
QEMU, which virt-fw-vars cannot handle. Thus, use a FUSE export to
cover all cases.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
debian/control | 1 +
src/PVE/QemuServer.pm | 20 +++++++++++++
src/PVE/QemuServer/Drive.pm | 9 ++++++
src/PVE/QemuServer/OVMF.pm | 59 +++++++++++++++++++++++++++++++++++++
4 files changed, 89 insertions(+)
diff --git a/debian/control b/debian/control
index 5ac6b0fc..661ac65b 100644
--- a/debian/control
+++ b/debian/control
@@ -58,6 +58,7 @@ Depends: conntrack,
pve-firewall (>= 6.0.3),
pve-ha-manager (>= 5.0.3),
pve-qemu-kvm (>= 7.1~),
+ python3-virt-firmware,
socat,
swtpm,
swtpm-tools,
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 79ff637a..74836833 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -5373,6 +5373,24 @@ my sub remove_left_over_vmstate_opts {
PVE::QemuConfig->write_config($vmid, $conf) if $found;
}
+my sub check_efi_vars {
+ my ($storecfg, $vmid, $conf) = @_;
+
+ return if PVE::QemuConfig->is_template($conf);
+ return if !$conf->{efidisk0};
+ return if $conf->{ostype} ne 'win10' && $conf->{ostype} ne 'win11';
+
+ if (
+ my $updated = PVE::QemuServer::OVMF::ensure_ms_2023_cert_enrolled(
+ $storecfg, $vmid, $conf->{efidisk0},
+ )
+ ) {
+ $conf->{efidisk0} = $updated;
+ PVE::QemuConfig->write_config($vmid, $conf);
+ }
+ return;
+}
+
# see vm_start_nolock for parameters, additionally:
# migrate_opts:
# storagemap = parsed storage map for allocating NBD disks
@@ -5548,6 +5566,8 @@ sub vm_start_nolock {
# With -blockdev, it is necessary to activate the volumes before generating the command line
PVE::Storage::activate_volumes($storecfg, $vollist);
+ check_efi_vars($storecfg, $vmid, $conf) if $conf->{bios} && $conf->{bios} eq 'ovmf';
+
# Note that for certain cases like templates, the configuration is minimized, so need to ensure
# the rest of the function here uses the same configuration that was used to build the command
($cmd, $spice_port, my $pci_devices, $conf) = config_to_command(
diff --git a/src/PVE/QemuServer/Drive.pm b/src/PVE/QemuServer/Drive.pm
index f54f9612..c772c803 100644
--- a/src/PVE/QemuServer/Drive.pm
+++ b/src/PVE/QemuServer/Drive.pm
@@ -521,6 +521,15 @@ my %efitype_fmt = (
optional => 1,
default => 0,
},
+ 'ms-cert' => {
+ type => 'string',
+ enum => [qw(2011 2023)],
+ description =>
+ "Informational marker indicating the version of the latest Microsof UEFI certificate"
+ . " that has been enrolled by Proxmox VE.",
+ optional => 1,
+ default => '2011',
+ },
);
my $efidisk_fmt = {
diff --git a/src/PVE/QemuServer/OVMF.pm b/src/PVE/QemuServer/OVMF.pm
index 08134e30..badacf54 100644
--- a/src/PVE/QemuServer/OVMF.pm
+++ b/src/PVE/QemuServer/OVMF.pm
@@ -11,7 +11,9 @@ use PVE::Tools;
use PVE::QemuServer::Blockdev;
use PVE::QemuServer::Drive qw(checked_volume_format parse_drive print_drive);
+use PVE::QemuServer::Helpers;
use PVE::QemuServer::QemuImage;
+use PVE::QemuServer::QSD;
my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
my $OVMF = {
@@ -128,6 +130,30 @@ sub get_efivars_size {
return -s $ovmf_vars;
}
+my sub is_ms_2023_cert_enrolled {
+ my ($path) = @_;
+
+ my $inside_db_section;
+ my $found_ms_2023_cert;
+
+ my $detect_ms_2023_cert = sub {
+ my ($line) = @_;
+ return if $found_ms_2023_cert;
+ $inside_db_section = undef if !$line;
+ $found_ms_2023_cert = 1
+ if $inside_db_section && $line =~ m/CN=Microsoft UEFI CA 2023/;
+ $inside_db_section = 1 if $line =~ m/^name=db guid=guid:EfiImageSecurityDatabase/;
+ return;
+ };
+
+ PVE::Tools::run_command(
+ ['virt-fw-vars', '--input', $path, '--print', '--verbose'],
+ outfunc => $detect_ms_2023_cert,
+ );
+
+ return $found_ms_2023_cert;
+}
+
sub create_efidisk($$$$$$$$) {
my ($storecfg, $storeid, $vmid, $fmt, $arch, $efidisk, $smm, $amd_sev_type) = @_;
@@ -141,6 +167,10 @@ sub create_efidisk($$$$$$$$) {
PVE::QemuServer::QemuImage::convert($ovmf_vars, $volid, $vars_size_b);
my $size = PVE::Storage::volume_size_info($storecfg, $volid, 3);
+ if ($efidisk->{'pre-enrolled-keys'} && is_ms_2023_cert_enrolled($ovmf_vars)) {
+ $efidisk->{'ms-cert'} = '2023'
+ }
+
return ($volid, $size / 1024);
}
@@ -235,4 +265,33 @@ sub print_ovmf_commandline {
return ($cmd, $machine_flags);
}
+sub ensure_ms_2023_cert_enrolled {
+ my ($storecfg, $vmid, $efidisk_str) = @_;
+
+ my $efidisk = parse_drive('efidisk0', $efidisk_str);
+ return if !$efidisk->{'pre-enrolled-keys'};
+ return if $efidisk->{'ms-cert'} && $efidisk->{'ms-cert'} eq '2023';
+
+ print "efidisk0: enrolling Microsoft UEFI CA 2023\n";
+
+ my $new_qsd = !PVE::QemuServer::Helpers::qsd_running_locally($vmid);
+ PVE::QemuServer::QSD::start($vmid) if $new_qsd;
+
+ eval {
+ my $efi_vars_path =
+ PVE::QemuServer::QSD::add_fuse_export($vmid, $efidisk, 'efidisk0-enroll');
+ PVE::Tools::run_command(
+ ['virt-fw-vars', '--inplace', $efi_vars_path, '--distro-keys', 'ms-uefi']);
+ PVE::QemuServer::QSD::remove_fuse_export($vmid, 'efidisk0-enroll');
+ };
+ my $err = $@;
+
+ PVE::QemuServer::QSD::quit($vmid) if $new_qsd;
+
+ die "efidisk0: enrolling Microsoft UEFI CA 2023 failed - $err" if $err;
+
+ $efidisk->{'ms-cert'} = '2023';
+ return print_drive($efidisk);
+}
+
1;
--
2.47.3
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2025-11-11 13:57 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-11 13:57 [pve-devel] [PATCH-SERIES qemu-server 0/2] " Fiona Ebner
2025-11-11 13:57 ` [pve-devel] [PATCH qemu-server 1/2] qsd: add remove_fuse_export() function Fiona Ebner
2025-11-14 11:50 ` [pve-devel] applied: " Thomas Lamprecht
2025-11-11 13:57 ` Fiona Ebner [this message]
2025-11-14 1:18 ` [pve-devel] [PATCH qemu-server 2/2] fix #6985: ovmf: auto-enroll Microsoft UEFI CA 2023 for Windows Thomas Lamprecht
2025-11-14 11:03 ` Fiona Ebner
2025-11-14 11:48 ` Thomas Lamprecht
2025-11-14 12:03 ` Fiona Ebner
2025-11-14 12:12 ` Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251111135808.110791-3-f.ebner@proxmox.com \
--to=f.ebner@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox