From nobody Sun Feb 8 10:48:33 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) client-ip=170.10.129.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1667390420; cv=none; d=zohomail.com; s=zohoarc; b=JQeMBC5f6VT+8aalS0xG1EWObw0HHHVDLehVI46zgwnkZ7kA58PNzxMyoWNn1lR2FA4PtufoLTG9Ep+DwEhG4AGDQqiEifYwzLFmqc7hguxEvB/BuwOar1MME2o/YSvy6DGqzZqE72ceZdvigdJpD3WMsBcqLJOqu8v75Ic17ZQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1667390420; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=O094e8dTJ22nbRGzhsjB5nS33F+BqSgkfsdKiIgUfIU=; b=jLo/csrbVITUw9kC98SFy+TTjVQyncUkXmDekL7KXr88DInR8HOKDz585/eJvxCwU5uL8XUWG/RKSlaBbVduzRb7WAfxN/xD8isvb1rwXeRQlJUBYt3i6rd5RfI0AUJA5cyv5NWNFjSAUk/EMLH6EkvvK7NwxXE9NzUKpx/mNSs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by mx.zohomail.com with SMTPS id 1667390420682720.3258760283003; Wed, 2 Nov 2022 05:00:20 -0700 (PDT) Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-132-AWBeVYSFNLWX_Bqp5lJFKg-1; Wed, 02 Nov 2022 07:59:18 -0400 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id B47321011630; Wed, 2 Nov 2022 11:59:10 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (unknown [10.30.29.100]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8A0174EA61; Wed, 2 Nov 2022 11:59:10 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (localhost [IPv6:::1]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id 6DC241946A47; Wed, 2 Nov 2022 11:59:10 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id 163241946594 for ; Wed, 2 Nov 2022 11:59:09 +0000 (UTC) Received: by smtp.corp.redhat.com (Postfix) id 0A6AE111CB83; Wed, 2 Nov 2022 11:59:09 +0000 (UTC) Received: from localhost.localdomain.com (unknown [10.33.36.154]) by smtp.corp.redhat.com (Postfix) with ESMTP id 82ABA111CB81; Wed, 2 Nov 2022 11:59:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1667390417; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=O094e8dTJ22nbRGzhsjB5nS33F+BqSgkfsdKiIgUfIU=; b=LG8cvUrUP8FSjJzE66X0Ej+yV6czlPZ5dcMRsh9PDR4lm7Txk1mCYky/RBWUk4zkt1qqSp 4K19Nec2C/RSYtr48YUgmEC7kveB6nRwbKwyT5ratpfq7YB8o8xyZwm4HAoPcB2qfNDKVV S6DHUrU4kmZjMkGL3zrVhivR4KC4M0I= X-MC-Unique: AWBeVYSFNLWX_Bqp5lJFKg-1 X-Original-To: libvir-list@listman.corp.redhat.com From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= To: libvir-list@redhat.com Subject: [libvirt PATCH v3 07/12] tools: support automatically constructing SEV-ES vCPU state Date: Wed, 2 Nov 2022 11:58:56 +0000 Message-Id: <20221102115901.823636-8-berrange@redhat.com> In-Reply-To: <20221102115901.823636-1-berrange@redhat.com> References: <20221102115901.823636-1-berrange@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.3 X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libvir-list-bounces@redhat.com Sender: "libvir-list" X-Scanned-By: MIMEDefang 3.1 on 10.11.54.5 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1667390421513100001 The VMSA files contain the expected CPU register state for the VM. Their content varies based on a few pieces of the stack - AMD CPU architectural initial state - KVM hypervisor VM CPU initialization - QEMU userspace VM CPU initialization - AMD CPU SKU (family/model/stepping) The first three pieces of information we can obtain through code inspection. The last piece of information we can take on the command line. This allows a user to validate a SEV-ES guest merely by providing the CPU SKU information, using --cpu-family, --cpu-model, --cpu-stepping. This avoids the need to obtain or construct VMSA files directly. Signed-off-by: Daniel P. Berrang=C3=A9 Reviewed-by: J=C3=A1n Tomko --- docs/manpages/virt-qemu-sev-validate.rst | 45 +++ tools/virt-qemu-sev-validate | 467 +++++++++++++++++++++++ 2 files changed, 512 insertions(+) diff --git a/docs/manpages/virt-qemu-sev-validate.rst b/docs/manpages/virt-= qemu-sev-validate.rst index 9f86212cb9..4aebbb3c9b 100644 --- a/docs/manpages/virt-qemu-sev-validate.rst +++ b/docs/manpages/virt-qemu-sev-validate.rst @@ -245,6 +245,24 @@ Validate the measurement of a SEV-ES SMP guest booting= from disk: --build-id 13 \ --policy 7 =20 +Validate the measurement of a SEV-ES SMP guest booting from disk, with +automatically constructed VMSA: + +:: + + # virt-dom-sev-validate \ + --firmware OVMF.sev.fd \ + --num-cpus 2 \ + --cpu-family 23 \ + --cpu-model 49 \ + --cpu-stepping 0 \ + --tk this-guest-tk.bin \ + --measurement Zs2pf19ubFSafpZ2WKkwquXvACx9Wt/BV+eJwQ/taO8jhyIj/F8sw= FrybR1fZ2ID \ + --api-major 0 \ + --api-minor 24 \ + --build-id 13 \ + --policy 7 + Fetch from remote libvirt ------------------------- =20 @@ -291,6 +309,20 @@ Validate the measurement of a SEV-ES SMP guest booting= from disk: --tk this-guest-tk.bin \ --domain fedora34x86_64 =20 +Validate the measurement of a SEV-ES SMP guest booting from disk, with +automatically constructed VMSA: + +:: + + # virt-dom-sev-validate \ + --connect qemu+ssh://root@some.remote.host/system \ + --firmware OVMF.sev.fd \ + --cpu-family 23 \ + --cpu-model 49 \ + --cpu-stepping 0 \ + --tk this-guest-tk.bin \ + --domain fedora34x86_64 + Fetch from local libvirt ------------------------ =20 @@ -332,6 +364,19 @@ Validate the measurement of a SEV-ES SMP guest booting= from disk: --tk this-guest-tk.bin \ --domain fedora34x86_64 =20 +Validate the measurement of a SEV-ES SMP guest booting from disk, with +automatically constructed VMSA: + +:: + + # virt-dom-sev-validate \ + --insecure \ + --cpu-family 23 \ + --cpu-model 49 \ + --cpu-stepping 0 \ + --tk this-guest-tk.bin \ + --domain fedora34x86_64 + EXIT STATUS =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 diff --git a/tools/virt-qemu-sev-validate b/tools/virt-qemu-sev-validate index 5abcedfb1f..cdf6a24d46 100755 --- a/tools/virt-qemu-sev-validate +++ b/tools/virt-qemu-sev-validate @@ -42,6 +42,7 @@ import hmac import logging import re import socket +from struct import pack import sys import traceback from uuid import UUID @@ -72,6 +73,427 @@ class InvalidStateException(Exception): pass =20 =20 +class Field(object): + U8 =3D 0 + U16 =3D 2 + U32 =3D 4 + U64 =3D 8 + + SCALAR =3D 0 + BITMASK =3D 1 + ARRAY =3D 2 + + def __init__(self, name, size, fmt, value, order): + self.name =3D name + self.size =3D size + self.value =3D value + self.fmt =3D fmt + self.order =3D order + + +class Struct(object): + def __init__(self, size): + self._fields =3D {} + self.size =3D size + + def register_field(self, name, size, fmt=3DField.SCALAR, defvalue=3D0): + self._fields[name] =3D Field(name, size, fmt, + defvalue, len(self.fields)) + + @property + def fields(self): + return sorted(self._fields.values(), key=3Dlambda f: f.order) + + def __getattr__(self, name): + return self._fields[name] + + def __setattr__(self, name, value): + if name in ["_fields", "size"]: + super().__setattr__(name, value) + else: + self._fields[name].value =3D value + + def binary_format(self): + fmt =3D ["<"] + datalen =3D 0 + for field in self.fields: + if field.size =3D=3D Field.U8: + if field.fmt =3D=3D Field.ARRAY: + datalen +=3D len(field.value) + fmt +=3D ["%dB" % len(field.value)] + else: + datalen +=3D 1 + fmt +=3D ["B"] + elif field.size =3D=3D Field.U16: + datalen +=3D 2 + fmt +=3D ["H"] + elif field.size =3D=3D Field.U32: + datalen +=3D 4 + fmt +=3D ["L"] + elif field.size =3D=3D Field.U64: + datalen +=3D 8 + fmt +=3D ["Q"] + + pad =3D self.size - datalen + assert self.size >=3D 1 + fmt +=3D ["%dB" % pad] + + return "".join(fmt), pad + + def pack(self): + fmt, pad =3D self.binary_format() + + values =3D [] + for field in self.fields: + if field.size =3D=3D Field.U8 and field.fmt =3D=3D Field.ARRAY: + for _, k in enumerate(field.value): + values.append(k) + else: + values.append(field.value) + values.extend([0] * pad) + + return pack(fmt, *values) + + +class VMSA(Struct): + ATTR_G_SHIFT =3D 23 + ATTR_G_MASK =3D (1 << ATTR_G_SHIFT) + ATTR_B_SHIFT =3D 22 + ATTR_B_MASK =3D (1 << ATTR_B_SHIFT) + ATTR_L_SHIFT =3D 21 + ATTR_L_MASK =3D (1 << ATTR_L_SHIFT) + ATTR_AVL_SHIFT =3D 20 + ATTR_AVL_MASK =3D (1 << ATTR_AVL_SHIFT) + ATTR_P_SHIFT =3D 15 + ATTR_P_MASK =3D (1 << ATTR_P_SHIFT) + ATTR_DPL_SHIFT =3D 13 + ATTR_DPL_MASK =3D (3 << ATTR_DPL_SHIFT) + ATTR_S_SHIFT =3D 12 + ATTR_S_MASK =3D (1 << ATTR_S_SHIFT) + ATTR_TYPE_SHIFT =3D 8 + ATTR_TYPE_MASK =3D (15 << ATTR_TYPE_SHIFT) + ATTR_A_MASK =3D (1 << 8) + + ATTR_CS_MASK =3D (1 << 11) + ATTR_C_MASK =3D (1 << 10) + ATTR_R_MASK =3D (1 << 9) + + ATTR_E_MASK =3D (1 << 10) + ATTR_W_MASK =3D (1 << 9) + + def __init__(self): + super().__init__(4096) + + # From Linux arch/x86/include/asm/svm.h, we're unpacking the + # struct vmcb_save_area + + self.register_field("es_selector", Field.U16) + self.register_field("es_attrib", Field.U16, Field.BITMASK) + self.register_field("es_limit", Field.U32) + self.register_field("es_base", Field.U64) + + self.register_field("cs_selector", Field.U16) + self.register_field("cs_attrib", Field.U16, Field.BITMASK) + self.register_field("cs_limit", Field.U32) + self.register_field("cs_base", Field.U64) + + self.register_field("ss_selector", Field.U16) + self.register_field("ss_attrib", Field.U16, Field.BITMASK) + self.register_field("ss_limit", Field.U32) + self.register_field("ss_base", Field.U64) + + self.register_field("ds_selector", Field.U16) + self.register_field("ds_attrib", Field.U16, Field.BITMASK) + self.register_field("ds_limit", Field.U32) + self.register_field("ds_base", Field.U64) + + self.register_field("fs_selector", Field.U16) + self.register_field("fs_attrib", Field.U16, Field.BITMASK) + self.register_field("fs_limit", Field.U32) + self.register_field("fs_base", Field.U64) + + self.register_field("gs_selector", Field.U16) + self.register_field("gs_attrib", Field.U16, Field.BITMASK) + self.register_field("gs_limit", Field.U32) + self.register_field("gs_base", Field.U64) + + self.register_field("gdtr_selector", Field.U16) + self.register_field("gdtr_attrib", Field.U16, Field.BITMASK) + self.register_field("gdtr_limit", Field.U32) + self.register_field("gdtr_base", Field.U64) + + self.register_field("ldtr_selector", Field.U16) + self.register_field("ldtr_attrib", Field.U16, Field.BITMASK) + self.register_field("ldtr_limit", Field.U32) + self.register_field("ldtr_base", Field.U64) + + self.register_field("idtr_selector", Field.U16) + self.register_field("idtr_attrib", Field.U16, Field.BITMASK) + self.register_field("idtr_limit", Field.U32) + self.register_field("idtr_base", Field.U64) + + self.register_field("tr_selector", Field.U16) + self.register_field("tr_attrib", Field.U16, Field.BITMASK) + self.register_field("tr_limit", Field.U32) + self.register_field("tr_base", Field.U64) + + self.register_field("reserved_1", + Field.U8, Field.ARRAY, bytearray([0] * 43)) + + self.register_field("cpl", Field.U8) + + self.register_field("reserved_2", + Field.U8, Field.ARRAY, bytearray([0] * 4)) + + self.register_field("efer", Field.U64) + + self.register_field("reserved_3", + Field.U8, Field.ARRAY, bytearray([0] * 104)) + + self.register_field("xss", Field.U64) + self.register_field("cr4", Field.U64) + self.register_field("cr3", Field.U64) + self.register_field("cr0", Field.U64) + self.register_field("dr7", Field.U64) + self.register_field("dr6", Field.U64) + self.register_field("rflags", Field.U64) + self.register_field("rip", Field.U64) + + self.register_field("reserved_4", + Field.U8, Field.ARRAY, bytearray([0] * 88)) + + self.register_field("rsp", Field.U64) + + self.register_field("reserved_5", + Field.U8, Field.ARRAY, bytearray([0] * 24)) + + self.register_field("rax", Field.U64) + self.register_field("star", Field.U64) + self.register_field("lstar", Field.U64) + self.register_field("cstar", Field.U64) + self.register_field("sfmask", Field.U64) + self.register_field("kernel_gs_base", Field.U64) + self.register_field("sysenter_cs", Field.U64) + self.register_field("sysenter_esp", Field.U64) + self.register_field("sysenter_eip", Field.U64) + self.register_field("cr2", Field.U64) + + self.register_field("reserved_6", + Field.U8, Field.ARRAY, bytearray([0] * 32)) + + self.register_field("g_pat", Field.U64) + self.register_field("dbgctl", Field.U64) + self.register_field("br_from", Field.U64) + self.register_field("br_to", Field.U64) + self.register_field("last_excp_from", Field.U64) + self.register_field("last_excp_to", Field.U64) + + self.register_field("reserved_7", + Field.U8, Field.ARRAY, bytearray([0] * 72)) + + self.register_field("spec_ctrl", Field.U32) + + self.register_field("reserved_7b", + Field.U8, Field.ARRAY, bytearray([0] * 4)) + + self.register_field("pkru", Field.U32) + + self.register_field("reserved_7a", + Field.U8, Field.ARRAY, bytearray([0] * 20)) + + self.register_field("reserved_8", Field.U64) # rax duplicate + + self.register_field("rcx", Field.U64) + self.register_field("rdx", Field.U64, Field.BITMASK) + self.register_field("rbx", Field.U64) + + self.register_field("reserved_9", Field.U64) # rsp duplicate + + self.register_field("rbp", Field.U64) + self.register_field("rsi", Field.U64) + self.register_field("rdi", Field.U64) + self.register_field("r8", Field.U64) + self.register_field("r9", Field.U64) + self.register_field("r10", Field.U64) + self.register_field("r11", Field.U64) + self.register_field("r12", Field.U64) + self.register_field("r13", Field.U64) + self.register_field("r14", Field.U64) + self.register_field("r15", Field.U64) + + self.register_field("reserved_10", + Field.U8, Field.ARRAY, bytearray([0] * 16)) + + self.register_field("sw_exit_code", Field.U64) + self.register_field("sw_exit_info_1", Field.U64) + self.register_field("sw_exit_info_2", Field.U64) + self.register_field("sw_scratch", Field.U64) + + self.register_field("reserved_11", + Field.U8, Field.ARRAY, bytearray([0] * 56)) + + self.register_field("xcr0", Field.U64) + self.register_field("valid_bitmap", + Field.U8, Field.ARRAY, bytearray([0] * 16)) + self.register_field("x87_state_gpa", + Field.U64) + + def amd64_cpu_init(self): + # AMD64 Architecture Programmer=E2=80=99s Manual + # Volume 2: System Programming. + # + # 14.1.3 Processor Initialization State + # + # Values after INIT + + self.cr0 =3D (1 << 4) + self.rip =3D 0xfff0 + + self.cs_selector =3D 0xf000 + self.cs_base =3D 0xffff0000 + self.cs_limit =3D 0xffff + + self.ds_limit =3D 0xffff + + self.es_limit =3D 0xffff + self.fs_limit =3D 0xffff + self.gs_limit =3D 0xffff + self.ss_limit =3D 0xffff + + self.gdtr_limit =3D 0xffff + self.idtr_limit =3D 0xffff + + self.ldtr_limit =3D 0xffff + self.tr_limit =3D 0xffff + + self.dr6 =3D 0xffff0ff0 + self.dr7 =3D 0x0400 + self.rflags =3D 0x2 + self.xcr0 =3D 0x1 + + def kvm_cpu_init(self): + # svm_set_cr4() sets guest X86_CR4_MCE bit if host + # has X86_CR4_MCE enabled + self.cr4 =3D 0x40 + + # svm_set_efer sets guest EFER_SVME (Secure Virtual Machine enable) + self.efer =3D 0x1000 + + # init_vmcb + init_sys_seg() sets + # SVM_SELECTOR_P_MASK | SEG_TYPE_LDT + self.ldtr_attrib =3D 0x0082 + + # init_vmcb + init_sys_seg() sets + # SVM_SELECTOR_P_MASK | SEG_TYPE_BUSY_TSS16 + self.tr_attrib =3D 0x0083 + + # kvm_arch_vcpu_create() in arch/x86/kvm/x86.c + self.g_pat =3D 0x0007040600070406 + + def qemu_cpu_init(self): + # Based on logic in x86_cpu_reset() + # + # file target/i386/cpu.c + + def attr(mask): + return (mask >> VMSA.ATTR_TYPE_SHIFT) + + self.ldtr_attrib =3D attr(VMSA.ATTR_P_MASK | + (2 << VMSA.ATTR_TYPE_SHIFT)) + self.tr_attrib =3D attr(VMSA.ATTR_P_MASK | + (11 << VMSA.ATTR_TYPE_SHIFT)) + self.cs_attrib =3D attr(VMSA.ATTR_P_MASK | + VMSA.ATTR_S_MASK | + VMSA.ATTR_CS_MASK | + VMSA.ATTR_R_MASK | + VMSA.ATTR_A_MASK) + self.ds_attrib =3D attr(VMSA.ATTR_P_MASK | + VMSA.ATTR_S_MASK | + VMSA.ATTR_W_MASK | + VMSA.ATTR_A_MASK) + self.es_attrib =3D attr(VMSA.ATTR_P_MASK | + VMSA.ATTR_S_MASK | + VMSA.ATTR_W_MASK | + VMSA.ATTR_A_MASK) + self.ss_attrib =3D attr(VMSA.ATTR_P_MASK | + VMSA.ATTR_S_MASK | + VMSA.ATTR_W_MASK | + VMSA.ATTR_A_MASK) + self.fs_attrib =3D attr(VMSA.ATTR_P_MASK | + VMSA.ATTR_S_MASK | + VMSA.ATTR_W_MASK | + VMSA.ATTR_A_MASK) + self.gs_attrib =3D attr(VMSA.ATTR_P_MASK | + VMSA.ATTR_S_MASK | + VMSA.ATTR_W_MASK | + VMSA.ATTR_A_MASK) + + self.g_pat =3D 0x0007040600070406 + + def cpu_sku(self, family, model, stepping): + stepping &=3D 0xf + model &=3D 0xff + family &=3D 0xfff + + self.rdx.value =3D stepping + + if family > 0xf: + self.rdx.value |=3D 0xf00 | ((family - 0x0f) << 20) + else: + self.rdx.value |=3D family << 8 + + self.rdx.value |=3D ((model & 0xf) << 4) | ((model >> 4) << 16) + + def reset_addr(self, reset_addr): + reset_cs =3D reset_addr & 0xffff0000 + reset_ip =3D reset_addr & 0x0000ffff + + self.rip.value =3D reset_ip + self.cs_base.value =3D reset_cs + + +class OVMF(object): + + OVMF_TABLE_FOOTER_GUID =3D UUID("96b582de-1fb2-45f7-baea-a366c55a082d") + SEV_INFO_BLOCK_GUID =3D UUID("00f771de-1a7e-4fcb-890e-68c77e2fb44e") + + def __init__(self): + self.entries =3D {} + + def load(self, content): + expect =3D OVMF.OVMF_TABLE_FOOTER_GUID.bytes_le + actual =3D content[-48:-32] + if expect !=3D actual: + raise Exception("OVMF footer GUID not found") + + tablelen =3D int.from_bytes(content[-50:-48], byteorder=3D'little') + + if tablelen =3D=3D 0: + raise Exception("OVMF tables zero length") + + table =3D content[-(32 + tablelen):-50] + + self.parse_table(table) + + def parse_table(self, data): + while len(data) > 0: + entryuuid =3D UUID(bytes_le=3Ddata[-16:]) + entrylen =3D int.from_bytes(data[-18:-16], byteorder=3D'little= ') + entrydata =3D data[-entrylen:-18] + + self.entries[str(entryuuid)] =3D entrydata + + data =3D data[0:-entrylen] + + def reset_addr(self): + if str(OVMF.SEV_INFO_BLOCK_GUID) not in self.entries: + raise Exception("SEV info block GUID not found") + + reset_addr =3D int.from_bytes( + self.entries[str(OVMF.SEV_INFO_BLOCK_GUID)], "little") + return reset_addr + + class GUIDTable(abc.ABC): GUID_LEN =3D 16 =20 @@ -237,6 +659,26 @@ class ConfidentialVM(object): log.debug("VMSA CPU 1(sha256): %s", sha256(self.vmsa_cpu1).hexdigest()) =20 + def build_vmsas(self, family, model, stepping): + ovmf =3D OVMF() + ovmf.load(self.firmware) + + vmsa =3D VMSA() + vmsa.amd64_cpu_init() + vmsa.kvm_cpu_init() + vmsa.qemu_cpu_init() + + vmsa.cpu_sku(family, model, stepping) + + self.vmsa_cpu0 =3D vmsa.pack() + log.debug("VMSA CPU 0(sha256): %s", + sha256(self.vmsa_cpu0).hexdigest()) + + vmsa.reset_addr(ovmf.reset_addr()) + self.vmsa_cpu1 =3D vmsa.pack() + log.debug("VMSA CPU 1(sha256): %s", + sha256(self.vmsa_cpu1).hexdigest()) + def get_cpu_state(self): if self.num_cpus is None: raise UnsupportedUsageException( @@ -509,6 +951,12 @@ def parse_command_line(): help=3D'VMSA state for the boot CPU') vmconfig.add_argument('--vmsa-cpu1', '-1', help=3D'VMSA state for the additional CPUs') + vmconfig.add_argument('--cpu-family', type=3Dint, + help=3D'Hypervisor host CPU family number') + vmconfig.add_argument('--cpu-model', type=3Dint, + help=3D'Hypervisor host CPU model number') + vmconfig.add_argument('--cpu-stepping', type=3Dint, + help=3D'Hypervisor host CPU stepping number') vmconfig.add_argument('--tik', help=3D'TIK file for domain') vmconfig.add_argument('--tek', @@ -572,6 +1020,20 @@ def check_usage(args): raise UnsupportedUsageException( "--initrd/--cmdline require --kernel") =20 + sku =3D [args.cpu_family, args.cpu_model, args.cpu_stepping] + if sku.count(None) =3D=3D len(sku): + if args.vmsa_cpu1 is not None and args.vmsa_cpu0 is None: + raise UnsupportedUsageException( + "VMSA for additional CPU also requires VMSA for boot CPU") + else: + if args.vmsa_cpu0 is not None or args.vmsa_cpu1 is not None: + raise UnsupportedUsageException( + "VMSA files are mutually exclusive with CPU SKU") + + if sku.count(None) !=3D 0: + raise UnsupportedUsageException( + "CPU SKU needs family, model and stepping for SEV-ES domai= n") + =20 def attest(args): if args.domain is None: @@ -612,6 +1074,11 @@ def attest(args): if args.vmsa_cpu1 is not None: cvm.load_vmsa_cpu1(args.vmsa_cpu1) =20 + if args.cpu_family is not None: + cvm.build_vmsas(args.cpu_family, + args.cpu_model, + args.cpu_stepping) + if args.domain is not None: cvm.load_domain(args.connect, args.domain, --=20 2.37.3