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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.