* [PATCH pve-common 1/7] procfs: read CPU model and stepping from cpuinfo
2026-09-24 14:14 [PATCH 0/7] SEV: Expose chip ID and reported TCB versions Christian Ludwig
@ 2026-09-24 14:14 ` Christian Ludwig
2026-09-24 14:14 ` [PATCH qemu-server 2/7] sev: Detect SEV- Christian Ludwig
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Christian Ludwig @ 2026-09-24 14:14 UTC (permalink / raw)
To: pve-devel
[-- Attachment #1: Type: text/plain, Size: 1862 bytes --]
Report the numeric CPUID model and stepping alongside the already
existing family. Together they identify the processor generation.
That information is useful for an AMD SEV-SNP attestation client that
needs to pick the right AMD KDS endpoint, when the attestation report
itself carries no CPUID fields (report version 2).
All three values are captured from \d+ patterns, so coerce them to be
serialized as JSON numbers rather than strings. Note that this changes
how the undocumented 'family' member of the cpuinfo object in
/nodes/{node}/status is represented on the wire now.
Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
src/PVE/ProcFSTools.pm | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/PVE/ProcFSTools.pm b/src/PVE/ProcFSTools.pm
index bb8d276..ede5990 100644
--- a/src/PVE/ProcFSTools.pm
+++ b/src/PVE/ProcFSTools.pm
@@ -81,6 +81,8 @@ sub read_cpuinfo {
vendor => 'unknown',
family => 0,
model => 'unknown',
+ model_id => 0,
+ stepping => 0,
mhz => 0,
cpus => 1,
sockets => 1,
@@ -102,7 +104,11 @@ sub read_cpuinfo {
} elsif ($line =~ m/^vendor_id\s*:\s*(\S*)\s*$/i) {
$res->{vendor} = $1 if $res->{vendor} eq 'unknown';
} elsif ($line =~ m/^cpu family\s*:\s*(\d+)\s*$/i) {
- $res->{family} = $1 if !$res->{family};
+ $res->{family} = $1 + 0 if !$res->{family};
+ } elsif ($line =~ m/^model\s*:\s*(\d+)\s*$/i) {
+ $res->{model_id} = $1 + 0 if !$res->{model_id};
+ } elsif ($line =~ m/^stepping\s*:\s*(\d+)\s*$/i) {
+ $res->{stepping} = $1 + 0 if !$res->{stepping};
} elsif ($line =~ m/^cpu\s+MHz\s*:\s*(\d+\.\d+)\s*$/i) {
$res->{mhz} = $1 if !$res->{mhz};
} elsif ($line =~ m/^flags\s*:\s*(.*)$/) {
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH qemu-server 2/7] sev: Detect SEV-
2026-09-24 14:14 [PATCH 0/7] SEV: Expose chip ID and reported TCB versions Christian Ludwig
2026-09-24 14:14 ` [PATCH pve-common 1/7] procfs: read CPU model and stepping from cpuinfo Christian Ludwig
@ 2026-09-24 14:14 ` Christian Ludwig
2026-09-24 14:14 ` [PATCH qemu-server 3/7] sev: Expose chip ID and reported TCB versions Christian Ludwig
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Christian Ludwig @ 2026-09-24 14:14 UTC (permalink / raw)
To: pve-devel
[-- Attachment #1: Type: text/plain, Size: 2638 bytes --]
CPUID only announces what the silicon is capable of. SEV, SEV-ES and SNP
can all still be disabled by firmware/platform config. If KVM signals
support, Qemu is able launch VMs with that feature.
Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
.../query-machine-capabilities.c | 42 ++++++++++++++++---
1 file changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/query-machine-capabilities/query-machine-capabilities.c b/src/query-machine-capabilities/query-machine-capabilities.c
index 0a9ab805..b7e06286 100644
--- a/src/query-machine-capabilities/query-machine-capabilities.c
+++ b/src/query-machine-capabilities/query-machine-capabilities.c
@@ -120,9 +120,44 @@ int read_msr(uint32_t msr_index, uint64_t *value) {
return 0;
}
+
void query_cpu_capabilities_sev(cpu_caps_amd_sev_t *res) {
#ifdef __x86_64__
uint32_t eax, ebx, ecx, edx;
+ struct {
+ const char *path;
+ bool *result;
+ } s[] = {
+ { "/sys/module/kvm_amd/parameters/sev", &res->sev_support },
+ { "/sys/module/kvm_amd/parameters/sev_es", &res->sev_es_support },
+ { "/sys/module/kvm_amd/parameters/sev_snp", &res->sev_snp_support },
+ };
+
+ for (size_t i = 0; i < sizeof(s) / sizeof(s[0]); i++) {
+ char c;
+ FILE *fp = fopen(s[i].path, "r");
+ if (fp == NULL)
+ continue;
+ if (fscanf(fp, " %c", &c) != 1) {
+ fclose(fp);
+ continue;
+ }
+ fclose(fp);
+
+ switch (c) {
+ case '1':
+ case 'y':
+ case 'Y':
+ *s[i].result = true;
+ break;
+ default:
+ *s[i].result = false;
+ break;
+ }
+ }
+
+ if (!res->sev_support && !res->sev_es_support && !res->sev_snp_support)
+ return;
// query Encrypted Memory Capabilities, see:
// https://en.wikipedia.org/wiki/CPUID#EAX=8000001Fh:_Encrypted_Memory_Capabilities
@@ -132,12 +167,9 @@ void query_cpu_capabilities_sev(cpu_caps_amd_sev_t *res) {
: "0"(query_function)
);
- res->sev_support = (eax & (1<<1)) != 0;
- res->sev_es_support = (eax & (1<<3)) != 0;
- res->sev_snp_support = (eax & (1<<4)) != 0;
-
res->cbitpos = ebx & 0x3f;
res->reduced_phys_bits = (ebx >> 6) & 0x3f;
+
#else
memset(res, 0, sizeof(*res));
#endif
@@ -206,7 +238,7 @@ int main() {
#ifdef __x86_64__
if (strncmp(vendor, "AuthenticAMD", 12) == 0) {
- cpu_caps_amd_sev_t caps_sev;
+ cpu_caps_amd_sev_t caps_sev = { 0 };
query_cpu_capabilities_sev(&caps_sev);
ret = fprintf(file,
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH qemu-server 3/7] sev: Expose chip ID and reported TCB versions
2026-09-24 14:14 [PATCH 0/7] SEV: Expose chip ID and reported TCB versions Christian Ludwig
2026-09-24 14:14 ` [PATCH pve-common 1/7] procfs: read CPU model and stepping from cpuinfo Christian Ludwig
2026-09-24 14:14 ` [PATCH qemu-server 2/7] sev: Detect SEV- Christian Ludwig
@ 2026-09-24 14:14 ` Christian Ludwig
2026-09-24 14:14 ` [PATCH qemu-server 4/7] api: add endpoint for SEV attestation data Christian Ludwig
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Christian Ludwig @ 2026-09-24 14:14 UTC (permalink / raw)
To: pve-devel
[-- Attachment #1: Type: text/plain, Size: 5092 bytes --]
Collect the SEV chip unique ID and the SNP reported TCB version. These
identify the exact VCEK a remote attestation client needs to fetch from
AMD's KDS for a given host, and are exposed alongside the existing
capability flags.
Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
.../query-machine-capabilities.c | 81 ++++++++++++++++++-
1 file changed, 79 insertions(+), 2 deletions(-)
diff --git a/src/query-machine-capabilities/query-machine-capabilities.c b/src/query-machine-capabilities/query-machine-capabilities.c
index b7e06286..088b2683 100644
--- a/src/query-machine-capabilities/query-machine-capabilities.c
+++ b/src/query-machine-capabilities/query-machine-capabilities.c
@@ -6,7 +6,10 @@
#include <errno.h>
#include <string.h>
#include <unistd.h>
+#include <inttypes.h>
#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/psp-sev.h>
#ifdef __aarch64__
#include <sys/auxv.h>
@@ -28,6 +31,9 @@ typedef struct {
bool sev_support;
bool sev_es_support;
bool sev_snp_support;
+ uint8_t *sev_chip_id;
+ size_t sev_chip_id_len;
+ uint64_t sev_snp_reported_tcb_version;
uint8_t cbitpos;
uint8_t reduced_phys_bits;
@@ -123,7 +129,11 @@ int read_msr(uint32_t msr_index, uint64_t *value) {
void query_cpu_capabilities_sev(cpu_caps_amd_sev_t *res) {
#ifdef __x86_64__
+ int fd, ret;
uint32_t eax, ebx, ecx, edx;
+ struct sev_issue_cmd cmd;
+ struct sev_user_data_snp_status snp_status;
+ struct sev_user_data_get_id2 get_id;
struct {
const char *path;
bool *result;
@@ -170,6 +180,64 @@ void query_cpu_capabilities_sev(cpu_caps_amd_sev_t *res) {
res->cbitpos = ebx & 0x3f;
res->reduced_phys_bits = (ebx >> 6) & 0x3f;
+ /*
+ * Get ChipId and TCB_VERSION for an Attester.
+ * Only necessary for SEV-SNP.
+ */
+ if (!res->sev_snp_support)
+ return;
+
+ fd = open("/dev/sev", O_RDWR);
+ if (fd < 0)
+ return;
+
+ memset(&snp_status, 0, sizeof(snp_status));
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.cmd = SNP_PLATFORM_STATUS;
+ cmd.data = (uintptr_t)&snp_status;
+ ret = ioctl(fd, SEV_ISSUE_CMD, &cmd);
+ if (ret >= 0 && cmd.error == 0) {
+ res->sev_snp_reported_tcb_version = snp_status.reported_tcb_version;
+ } else if (cmd.error != 0) {
+ fprintf(stderr, "SEV firmware error: 0x%x\n", cmd.error);
+ }
+
+ /*
+ * Get ChipId. The ID length must not be assumed, so ask for it first by
+ * passing a zero-length buffer. That query is expected to fail with
+ * INVALID_LEN; the firmware reports the required length either way.
+ */
+ memset(&get_id, 0, sizeof(get_id));
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.cmd = SEV_GET_ID2;
+ cmd.data = (uintptr_t)&get_id;
+ ioctl(fd, SEV_ISSUE_CMD, &cmd);
+ if (get_id.length == 0)
+ goto skip_chip_id;
+
+ res->sev_chip_id = malloc(get_id.length);
+ if (res->sev_chip_id == NULL) {
+ perror("malloc");
+ goto skip_chip_id;
+ }
+ res->sev_chip_id_len = get_id.length;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.cmd = SEV_GET_ID2;
+ cmd.data = (uintptr_t)&get_id;
+ get_id.address = (uintptr_t)res->sev_chip_id;
+ get_id.length = res->sev_chip_id_len;
+ ret = ioctl(fd, SEV_ISSUE_CMD, &cmd);
+ if (ret < 0 || cmd.error != 0 || get_id.length != res->sev_chip_id_len) {
+ if (cmd.error != 0)
+ fprintf(stderr, "SEV firmware error: 0x%x\n", cmd.error);
+ free(res->sev_chip_id);
+ res->sev_chip_id = NULL;
+ res->sev_chip_id_len = 0;
+ goto skip_chip_id;
+ }
+skip_chip_id:
+ close(fd);
#else
memset(res, 0, sizeof(*res));
#endif
@@ -240,6 +308,11 @@ int main() {
if (strncmp(vendor, "AuthenticAMD", 12) == 0) {
cpu_caps_amd_sev_t caps_sev = { 0 };
query_cpu_capabilities_sev(&caps_sev);
+ char sev_chip_id[(caps_sev.sev_chip_id_len * 2) + 1];
+
+ memset(sev_chip_id, 0, (caps_sev.sev_chip_id_len * 2) + 1);
+ for (size_t i = 0; i < caps_sev.sev_chip_id_len; i++)
+ sprintf(&sev_chip_id[i*2], "%02x", caps_sev.sev_chip_id[i]);
ret = fprintf(file,
" \"amd-sev\": {"
@@ -247,13 +320,17 @@ int main() {
" \"reduced-phys-bits\": %u,"
" \"sev-support\": %s,"
" \"sev-support-es\": %s,"
- " \"sev-support-snp\": %s"
+ " \"sev-support-snp\": %s,"
+ " \"sev-chip-id\": \"0x%s\","
+ " \"sev-snp-tcb-version\": \"0x%016" PRIX64 "\""
" }",
caps_sev.cbitpos,
caps_sev.reduced_phys_bits,
caps_sev.sev_support ? "true" : "false",
caps_sev.sev_es_support ? "true" : "false",
- caps_sev.sev_snp_support ? "true" : "false"
+ caps_sev.sev_snp_support ? "true" : "false",
+ sev_chip_id,
+ caps_sev.sev_snp_reported_tcb_version
);
} else if (strncmp(vendor, "GenuineIntel", 12) == 0) {
cpu_caps_intel_tdx_t caps_tdx;
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH qemu-server 4/7] api: add endpoint for SEV attestation data
2026-09-24 14:14 [PATCH 0/7] SEV: Expose chip ID and reported TCB versions Christian Ludwig
` (2 preceding siblings ...)
2026-09-24 14:14 ` [PATCH qemu-server 3/7] sev: Expose chip ID and reported TCB versions Christian Ludwig
@ 2026-09-24 14:14 ` Christian Ludwig
2026-09-24 14:14 ` [PATCH pve-manager 5/7] api: register SEV capabilities endpoint Christian Ludwig
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Christian Ludwig @ 2026-09-24 14:14 UTC (permalink / raw)
To: pve-devel
[-- Attachment #1: Type: text/plain, Size: 2799 bytes --]
Expose the SEV chip ID and the raw reported SNP TCB security patch
levels collected by query-machine-capabilities, so that a remote
attestation client can construct the AMD KDS URL for this host's VCEK
certificate.
If query-machine-capabilities has not run yet, report no data instead
of failing the request.
Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
src/PVE/API2/Qemu/Makefile | 2 +-
src/PVE/API2/Qemu/Sev.pm | 63 ++++++++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+), 1 deletion(-)
create mode 100644 src/PVE/API2/Qemu/Sev.pm
diff --git a/src/PVE/API2/Qemu/Makefile b/src/PVE/API2/Qemu/Makefile
index c348af75..12821bb0 100644
--- a/src/PVE/API2/Qemu/Makefile
+++ b/src/PVE/API2/Qemu/Makefile
@@ -2,7 +2,7 @@ DESTDIR=
PREFIX=/usr
PERLDIR=$(PREFIX)/share/perl5
-SOURCES=Agent.pm CPU.pm CPUFlags.pm HMPPerms.pm Machine.pm
+SOURCES=Agent.pm CPU.pm CPUFlags.pm HMPPerms.pm Machine.pm Sev.pm
.PHONY: install
install:
diff --git a/src/PVE/API2/Qemu/Sev.pm b/src/PVE/API2/Qemu/Sev.pm
new file mode 100644
index 00000000..7f8ce9d7
--- /dev/null
+++ b/src/PVE/API2/Qemu/Sev.pm
@@ -0,0 +1,63 @@
+package PVE::API2::Qemu::Sev;
+
+use strict;
+use warnings;
+
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RESTHandler;
+
+use PVE::QemuServer::CPUConfig;
+
+use base qw(PVE::RESTHandler);
+
+my $sev_status_properties = {
+ 'sev-chip-id' => {
+ type => 'string',
+ default => '',
+ description => "Hex-encoded SEV chip unique ID."
+ . " Empty if unavailable.",
+ },
+ 'sev-snp-tcb-version' => {
+ type => 'string',
+ default => '',
+ description => "Hex-encoded raw reported SNP TCB version."
+ . " Empty if unavailable.",
+ },
+};
+
+__PACKAGE__->register_method({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ proxyto => 'node',
+ description => "Get this node's SEV chip identity and reported SNP TCB.",
+ permissions => {
+ check => ['perm', '/nodes/{node}', ['Sys.Audit']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ },
+ },
+ returns => {
+ type => 'object',
+ properties => $sev_status_properties,
+ },
+ code => sub {
+ my ($param) = @_;
+
+ # may not have probed yet (e.g. early boot); treat as no data, not an error
+ my $hw_caps = eval { PVE::QemuServer::CPUConfig::get_hw_capabilities() };
+ my $sev = $hw_caps->{'amd-sev'} // {};
+
+ my $res = {};
+ for my $key (keys %$sev_status_properties) {
+ $res->{$key} = $sev->{$key} // $sev_status_properties->{$key}->{default};
+ }
+
+ return $res;
+ },
+});
+
+1;
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH pve-manager 5/7] api: register SEV capabilities endpoint
2026-09-24 14:14 [PATCH 0/7] SEV: Expose chip ID and reported TCB versions Christian Ludwig
` (3 preceding siblings ...)
2026-09-24 14:14 ` [PATCH qemu-server 4/7] api: add endpoint for SEV attestation data Christian Ludwig
@ 2026-09-24 14:14 ` Christian Ludwig
2026-09-24 14:14 ` [PATCH pve-docs 6/7] qm: Describe ASIDs for SEV Christian Ludwig
2026-09-24 14:14 ` [PATCH pve-docs 7/7] sev: Describe VCEK retieval Christian Ludwig
6 siblings, 0 replies; 8+ messages in thread
From: Christian Ludwig @ 2026-09-24 14:14 UTC (permalink / raw)
To: pve-devel
[-- Attachment #1: Type: text/plain, Size: 1223 bytes --]
Wire up PVE::API2::Qemu::Sev from qemu-server, which exposes the SEV
chip ID and reported SNP TCB security patch levels.
Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
PVE/API2/Capabilities.pm | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/PVE/API2/Capabilities.pm b/PVE/API2/Capabilities.pm
index 30b03c2d..43d67fbe 100644
--- a/PVE/API2/Capabilities.pm
+++ b/PVE/API2/Capabilities.pm
@@ -9,6 +9,7 @@ use PVE::RESTHandler;
use PVE::API2::Qemu::CPU;
use PVE::API2::Qemu::CPUFlags;
use PVE::API2::Qemu::Machine;
+use PVE::API2::Qemu::Sev;
use PVE::API2::NodeCapabilities::Qemu::Migration;
use base qw(PVE::RESTHandler);
@@ -28,6 +29,11 @@ __PACKAGE__->register_method({
path => 'qemu/machines',
});
+__PACKAGE__->register_method({
+ subclass => "PVE::API2::Qemu::Sev",
+ path => 'qemu/sev',
+});
+
__PACKAGE__->register_method({
subclass => 'PVE::API2::NodeCapabilities::Qemu::Migration',
path => 'qemu/migration',
@@ -94,6 +100,7 @@ __PACKAGE__->register_method({
{ name => 'cpu-flags' },
{ name => 'machines' },
{ name => 'migration' },
+ { name => 'sev' },
];
return $result;
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH pve-docs 6/7] qm: Describe ASIDs for SEV
2026-09-24 14:14 [PATCH 0/7] SEV: Expose chip ID and reported TCB versions Christian Ludwig
` (4 preceding siblings ...)
2026-09-24 14:14 ` [PATCH pve-manager 5/7] api: register SEV capabilities endpoint Christian Ludwig
@ 2026-09-24 14:14 ` Christian Ludwig
2026-09-24 14:14 ` [PATCH pve-docs 7/7] sev: Describe VCEK retieval Christian Ludwig
6 siblings, 0 replies; 8+ messages in thread
From: Christian Ludwig @ 2026-09-24 14:14 UTC (permalink / raw)
To: pve-devel
[-- Attachment #1: Type: text/plain, Size: 1052 bytes --]
It's an important limitation that can be tuned in firmware settings on
most machines.
While there, provide update dmesg output that includes SEV-SNP.
Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
qm.adoc | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/qm.adoc b/qm.adoc
index 5b46cdc..37950fc 100644
--- a/qm.adoc
+++ b/qm.adoc
@@ -842,14 +842,20 @@ the SEV kernel parameter of kvm_amd:
----
# dmesg | grep -i sev
+[...] SEV-SNP: RMP table physical range [<range>]
[...] ccp 0000:45:00.1: sev enabled
+[...] ccp 0000:45:00.1: SEV-SNP API: <buildversion>
[...] ccp 0000:45:00.1: SEV API: <buildversion>
-[...] SEV supported: <number> ASIDs
-[...] SEV-ES supported: <number> ASIDs
+[...] SEV enabled (ASIDs ...)
+[...] SEV-ES enabled (ASIDs ...)
+[...] SEV-SNP enabled (ASIDs ...)
# cat /sys/module/kvm_amd/parameters/sev
Y
----
+The number of ASIDs gives the maximum number of confidential guests for each
+type that can run in parallel.
+
*Guest Requirements:*
* edk2-OVMF
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH pve-docs 7/7] sev: Describe VCEK retieval
2026-09-24 14:14 [PATCH 0/7] SEV: Expose chip ID and reported TCB versions Christian Ludwig
` (5 preceding siblings ...)
2026-09-24 14:14 ` [PATCH pve-docs 6/7] qm: Describe ASIDs for SEV Christian Ludwig
@ 2026-09-24 14:14 ` Christian Ludwig
6 siblings, 0 replies; 8+ messages in thread
From: Christian Ludwig @ 2026-09-24 14:14 UTC (permalink / raw)
To: pve-devel
[-- Attachment #1: Type: text/plain, Size: 1043 bytes --]
Describe where to find information in the API to retrieve the VCEK from
AMD's Key Distribution Server.
Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
qm.adoc | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/qm.adoc b/qm.adoc
index 37950fc..54b3d42 100644
--- a/qm.adoc
+++ b/qm.adoc
@@ -856,6 +856,19 @@ Y
The number of ASIDs gives the maximum number of confidential guests for each
type that can run in parallel.
+{pve} probes the host's Secure Processor at boot and exposes the data that is
+needed to attest a confidential guest through the API:
+
+----
+# pvesh get /nodes/{node}/capabilities/qemu/sev
+----
+
+This reports the chip unique ID and the reported TCB security patch levels.
+Together they identify the Versioned Chip Endorsement Key (VCEK) that the
+firmware uses to sign attestation reports on this host. A remote attestation
+service can use this information to fetch the matching certificate from AMD's
+Key Distribution Service.
+
*Guest Requirements:*
* edk2-OVMF
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread