diff --git a/README.md b/README.md index f56c1b3..90cf9cc 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ $ sev-snp-measure --help usage: sev-snp-measure [-h] [--version] [-v] --mode {sev,seves,snp,snp:ovmf-hash,snp:svsm} [--vcpus N] [--vcpu-type CPUTYPE] [--vcpu-sig VALUE] [--vcpu-family FAMILY] [--vcpu-model MODEL] [--vcpu-stepping STEPPING] [--vmm-type VMMTYPE] --ovmf - PATH [--kernel PATH] [--initrd PATH] [--append CMDLINE] + PATH [--kernel PATH] [--initrd PATH] [--append CMDLINE] [--guest-features VALUE] [--output-format {hex,base64}] [--snp-ovmf-hash HASH] [--dump-vmsa] [--vars-size VARS_SIZE] [--svsm SVSM] @@ -58,6 +58,9 @@ options: --kernel PATH Kernel file to calculate hash from --initrd PATH Initrd file to calculate hash from (use with --kernel) --append CMDLINE Kernel command line to calculate hash from (use with --kernel) + + + --guest-features Hex representation of the guest kernel features expected to be included (defaults to 0x21); see README.md for possible values. --output-format {hex,base64} Measurement output format --snp-ovmf-hash HASH Precalculated hash of the OVMF binary (hex string) @@ -97,7 +100,7 @@ from sevsnpmeasure import vcpu_types from sevsnpmeasure.sev_mode import SevMode ld = guest.calc_launch_digest(SevMode.SEV_SNP, vcpus_num, vcpu_types.CPU_SIGS["EPYC-v4"], - ovmf_path, kernel_path, initrd_path, cmdline_str) + ovmf_path, kernel_path, initrd_path, cmdline_str, guest_features) print("Calculated measurement:", ld.hex()) block = id_block.snp_calc_id_block(ld,"id_key_file","author_key_file") @@ -119,6 +122,30 @@ example, the following 3 invocations are identical: 2. `sev-snp-measure --vcpu-sig=0x800f12 ...` 3. `sev-snp-measure --vcpu-family=23 --vcpu-model=1 --vcpu-stepping=2 ...` +## SEV-SNP Guest Feature Field Values +Prior to Linux Kernel version 6.6, the default value was always calculated to `0x1`, as the kernel only supported `SNPActive`. After the release of Linux Kernel 6.6, additional features were made available some of them enabled by default. Because of this, the new default value is `0x21` which is `SNPActive + DebugSwap`. Other possible combinations my be derived by generating a 64-bit hex value from the following chart: + +| BIT FIELD | Description | +|:---------:|:------------:| +| 0 | SNPActive | +| 1 | vTOM | +| 2 | ReflectVC | +| 3 | RestrictedInjection | +| 4 | AlternateInjection | +| 5 | DebugSwap | +| 6 | PreventHostIBS | +| 7 | BTBIsolation | +| 8 | VmplSSS | +| 9 | SecureTSC | +| 10 | VmgexitParameter | +| 11 | Reserved, SBZ | +| 12 | IbsVirtualization | +| 13 | Reserved, SBZ | +| 14 | VmsaRegProt | +| 15 | SmtProtection | +| 63:16 | Reserved, SBZ | + + ## Precalculated OVMF hashes The SEV-SNP digest gets generated in multiple steps that each have a digest as output. With that digest output, you can stop at any of these steps and continue generation of the full digest later. These are the steps: diff --git a/sevsnpmeasure/cli.py b/sevsnpmeasure/cli.py index b419fa7..f9c415a 100644 --- a/sevsnpmeasure/cli.py +++ b/sevsnpmeasure/cli.py @@ -69,6 +69,10 @@ def main() -> int: help='Initrd file to calculate hash from (use with --kernel)') parser.add_argument('--append', metavar='CMDLINE', help='Kernel command line to calculate hash from (use with --kernel)') + parser.add_argument('--guest-features', metavar='VALUE', type=lambda x: int(x, 0), + default=0x21, + help="Hex representation of the guest kernel features expected to be included " + "(defaults to 0x21); see README.md for possible values"), parser.add_argument('--output-format', choices=['hex', 'base64'], help='Measurement output format', default='hex') parser.add_argument('--snp-ovmf-hash', metavar='HASH', help='Precalculated hash of the OVMF binary (hex string)') parser.add_argument('--dump-vmsa', action='store_true', @@ -104,7 +108,8 @@ def main() -> int: parser.error("--dump-vmsa is not availibe in the selected mode") ld = guest.calc_launch_digest(sev_mode, args.vcpus, vcpu_sig, args.ovmf, args.kernel, args.initrd, args.append, - args.snp_ovmf_hash, vmm_type, args.dump_vmsa, args.svsm, args.vars_size) + args.guest_features, args.snp_ovmf_hash, vmm_type, args.dump_vmsa, + args.svsm, args.vars_size) print_measurement(ld, sev_mode, args.output_format, args.verbose) except RuntimeError as e: diff --git a/sevsnpmeasure/guest.py b/sevsnpmeasure/guest.py index 1914de7..924ebcc 100644 --- a/sevsnpmeasure/guest.py +++ b/sevsnpmeasure/guest.py @@ -18,18 +18,18 @@ def calc_launch_digest(mode: SevMode, vcpus: int, vcpu_sig: int, ovmf_file: str, - kernel: str, initrd: str, append: str, snp_ovmf_hash_str: str = '', + kernel: str, initrd: str, append: str, guest_features: int, snp_ovmf_hash_str: str = '', vmm_type: VMMType = VMMType.QEMU, dump_vmsa: bool = False, svsm_file: str = '', ovmf_vars_size: int = 0) -> bytes: if snp_ovmf_hash_str and mode != SevMode.SEV_SNP: raise ValueError("SNP OVMF hash only works with SNP") if mode == SevMode.SEV_SNP: - return snp_calc_launch_digest(vcpus, vcpu_sig, ovmf_file, kernel, initrd, append, snp_ovmf_hash_str, vmm_type, - dump_vmsa=dump_vmsa) + return snp_calc_launch_digest(vcpus, vcpu_sig, ovmf_file, kernel, initrd, append, guest_features, + snp_ovmf_hash_str, vmm_type, dump_vmsa=dump_vmsa) elif mode == SevMode.SEV_ES: - return seves_calc_launch_digest(vcpus, vcpu_sig, ovmf_file, kernel, initrd, append, vmm_type=vmm_type, - dump_vmsa=dump_vmsa) + return seves_calc_launch_digest(vcpus, vcpu_sig, ovmf_file, kernel, initrd, append, + vmm_type=vmm_type, dump_vmsa=dump_vmsa) elif mode == SevMode.SEV: return sev_calc_launch_digest(ovmf_file, kernel, initrd, append) elif mode == SevMode.SEV_SNP_SVSM: @@ -88,8 +88,8 @@ def calc_snp_ovmf_hash(ovmf_file: str) -> bytes: def snp_calc_launch_digest(vcpus: int, vcpu_sig: int, ovmf_file: str, - kernel: str, initrd: str, append: str, ovmf_hash_str: str, - vmm_type: VMMType = VMMType.QEMU, dump_vmsa: bool = False) -> bytes: + kernel: str, initrd: str, append: str, guest_features: int, + ovmf_hash_str: str, vmm_type: VMMType = VMMType.QEMU, dump_vmsa: bool = False) -> bytes: gctx = GCTX() ovmf = OVMF(ovmf_file) @@ -108,7 +108,7 @@ def snp_calc_launch_digest(vcpus: int, vcpu_sig: int, ovmf_file: str, snp_update_metadata_pages(gctx, ovmf, sev_hashes, vmm_type) - vmsa = VMSA(SevMode.SEV_SNP, ovmf.sev_es_reset_eip(), vcpu_sig, vmm_type) + vmsa = VMSA(SevMode.SEV_SNP, ovmf.sev_es_reset_eip(), vcpu_sig, guest_features, vmm_type) for i, vmsa_page in enumerate(vmsa.pages(vcpus)): gctx.update_vmsa_page(vmsa_page) if dump_vmsa: @@ -149,7 +149,7 @@ def seves_calc_launch_digest(vcpus: int, vcpu_sig: int, ovmf_file: str, kernel: raise RuntimeError("Kernel specified but OVMF doesn't support kernel/initrd/cmdline measurement") sev_hashes_table = SevHashes(kernel, initrd, append).construct_table() launch_hash.update(sev_hashes_table) - vmsa = VMSA(SevMode.SEV_ES, ovmf.sev_es_reset_eip(), vcpu_sig, vmm_type) + vmsa = VMSA(SevMode.SEV_ES, ovmf.sev_es_reset_eip(), vcpu_sig, 0x0, vmm_type) for i, vmsa_page in enumerate(vmsa.pages(vcpus)): launch_hash.update(vmsa_page) if dump_vmsa: diff --git a/sevsnpmeasure/vmsa.py b/sevsnpmeasure/vmsa.py index 92e3970..91b86cc 100644 --- a/sevsnpmeasure/vmsa.py +++ b/sevsnpmeasure/vmsa.py @@ -182,15 +182,11 @@ def build_save_area(eip: int, sev_features: int, vcpu_sig: int, vmm_type: VMMTyp xcr0=0x1, ) - def __init__(self, sev_mode: SevMode, ap_eip: int, vcpu_sig: int, vmm_type: VMMType = VMMType.QEMU): - if sev_mode == SevMode.SEV_SNP: - sev_features = 0x1 - else: - sev_features = 0x0 - - self.bsp_save_area = VMSA.build_save_area(self.BSP_EIP, sev_features, vcpu_sig, vmm_type) + def __init__(self, sev_mode: SevMode, ap_eip: int, vcpu_sig: int, guest_features: int, + vmm_type: VMMType = VMMType.QEMU): + self.bsp_save_area = VMSA.build_save_area(self.BSP_EIP, guest_features, vcpu_sig, vmm_type) if ap_eip: - self.ap_save_area = VMSA.build_save_area(ap_eip, sev_features, vcpu_sig, vmm_type) + self.ap_save_area = VMSA.build_save_area(ap_eip, guest_features, vcpu_sig, vmm_type) def pages(self, vcpus: int) -> Iterator[bytes]: """ diff --git a/tests/test_guest.py b/tests/test_guest.py index effad0c..62284d5 100644 --- a/tests/test_guest.py +++ b/tests/test_guest.py @@ -17,7 +17,7 @@ class TestGuest(unittest.TestCase): # Test of we can generate a good OVMF hash - def test_snp_ovmf_hash_gen(self): + def test_snp_ovmf_hash_gen_default(self): ovmf_hash = 'cab7e085874b3acfdbe2d96dcaa3125111f00c35c6fc9708464c2ae74bfdb048a198cb9a9ccae0b3e5e1a33f5f249819' ld = guest.calc_launch_digest( SevMode.SEV_SNP, @@ -27,6 +27,24 @@ def test_snp_ovmf_hash_gen(self): "/dev/null", "/dev/null", "", + 0x21, + snp_ovmf_hash_str=ovmf_hash) + self.assertEqual( + ld.hex(), + 'a076e1b0e6cf55fd94c82e2c25245f8c15f76690b941ba37' + '9b31527f82eafe7ad489777ff510d080bac9cd14d41bc205') + + def test_snp_ovmf_hash_gen_feature_snp_only(self): + ovmf_hash = 'cab7e085874b3acfdbe2d96dcaa3125111f00c35c6fc9708464c2ae74bfdb048a198cb9a9ccae0b3e5e1a33f5f249819' + ld = guest.calc_launch_digest( + SevMode.SEV_SNP, + 1, + vcpu_types.CPU_SIGS["EPYC-v4"], + "tests/fixtures/ovmf_AmdSev_suffix.bin", + "/dev/null", + "/dev/null", + "", + 0x1, snp_ovmf_hash_str=ovmf_hash) self.assertEqual( ld.hex(), @@ -34,7 +52,7 @@ def test_snp_ovmf_hash_gen(self): '91d9cd30f3c2c0bf42daccb30d55d6625bfbf0dae5c50c6d') # Test of we can a full LD from the OVMF hash - def test_snp_ovmf_hash_full(self): + def test_snp_ovmf_hash_full_default(self): ovmf_hash = guest.calc_snp_ovmf_hash("tests/fixtures/ovmf_AmdSev_suffix.bin").hex() self.assertEqual( ovmf_hash, @@ -49,13 +67,53 @@ def test_snp_ovmf_hash_full(self): "/dev/null", "/dev/null", "console=ttyS0 loglevel=7", + 0x21, + snp_ovmf_hash_str=ovmf_hash) + self.assertEqual( + ld.hex(), + '314e4f0794187ffef05702a36546ea5fe02698041b7f7f17' + 'd9f418da2d5e4d5cff25256cef9d34888a0dd64dea438780') + + def test_snp_ovmf_hash_full_feature_snp_only(self): + ovmf_hash = guest.calc_snp_ovmf_hash("tests/fixtures/ovmf_AmdSev_suffix.bin").hex() + self.assertEqual( + ovmf_hash, + 'edcf6d1c57ce868a167c990f58c8667c698269ef9e080324' + '6419eea914186343054d557e1f17acd93b032c106bc70d25') + + ld = guest.calc_launch_digest( + SevMode.SEV_SNP, + 1, + vcpu_types.CPU_SIGS["EPYC-v4"], + "tests/fixtures/ovmf_AmdSev_suffix.bin", + "/dev/null", + "/dev/null", + "console=ttyS0 loglevel=7", + 0x1, snp_ovmf_hash_str=ovmf_hash) self.assertEqual( ld.hex(), 'f07864303ad8243132029e8110b92805c78d1135a15da75f' - '67abb9a711d78740347f24ee76f603e650ec4adf3611cc1e') + '67abb9a711d78740347f24ee76f603e650ec4adf3611cc1e' + ) + + def test_snp_ec2_default(self): + ld = guest.calc_launch_digest( + SevMode.SEV_SNP, + 1, + vcpu_types.CPU_SIGS["EPYC-v4"], + "tests/fixtures/ovmf_AmdSev_suffix.bin", + "/dev/null", + "/dev/null", + "", + 0x21, + vmm_type=vmm_types.VMMType.ec2) + self.assertEqual( + ld.hex(), + 'cd4a4690a1f679ac8f3d6e446aab8d0061d535cc94615d98' + 'c7d7dbe4b16dbceeaf7fc7944e7874b202e27041f179e7e6') - def test_snp_ec2(self): + def test_snp_ec2_feature_snp_only(self): ld = guest.calc_launch_digest( SevMode.SEV_SNP, 1, @@ -64,13 +122,14 @@ def test_snp_ec2(self): "/dev/null", "/dev/null", "", + 0x1, vmm_type=vmm_types.VMMType.ec2) self.assertEqual( ld.hex(), '760b6e51039d2d6c1fc6d38ca5c387967d158e0294883e45' '22c36f89bd61bfc9cdb975cd1ceedffbe1b23b1daf4e3f42') - def test_snp(self): + def test_snp_default(self): ld = guest.calc_launch_digest( SevMode.SEV_SNP, 1, @@ -78,13 +137,44 @@ def test_snp(self): "tests/fixtures/ovmf_AmdSev_suffix.bin", "/dev/null", "/dev/null", - "console=ttyS0 loglevel=7") + "console=ttyS0 loglevel=7", + 0x21) + self.assertEqual( + ld.hex(), + '314e4f0794187ffef05702a36546ea5fe02698041b7f7f17' + 'd9f418da2d5e4d5cff25256cef9d34888a0dd64dea438780') + + def test_snp_guest_feature_snp_only(self): + ld = guest.calc_launch_digest( + SevMode.SEV_SNP, + 1, + vcpu_types.CPU_SIGS["EPYC-v4"], + "tests/fixtures/ovmf_AmdSev_suffix.bin", + "/dev/null", + "/dev/null", + "console=ttyS0 loglevel=7", + 0x1) self.assertEqual( ld.hex(), 'f07864303ad8243132029e8110b92805c78d1135a15da75f' '67abb9a711d78740347f24ee76f603e650ec4adf3611cc1e') - def test_snp_without_kernel(self): + def test_snp_without_kernel_default(self): + ld = guest.calc_launch_digest( + SevMode.SEV_SNP, + 1, + vcpu_types.CPU_SIGS["EPYC-v4"], + "tests/fixtures/ovmf_AmdSev_suffix.bin", + None, + None, + None, + 0x21) + self.assertEqual( + ld.hex(), + '6d9054ed9872a64c968cfbcfa1247cafa792e3f9a395306d' + '95c9937aaa081c643d25f369ccbd34409dafcae90bff55f3') + + def test_snp_without_kernel_feature_snp_only(self): ld = guest.calc_launch_digest( SevMode.SEV_SNP, 1, @@ -92,13 +182,29 @@ def test_snp_without_kernel(self): "tests/fixtures/ovmf_AmdSev_suffix.bin", None, None, - None) + None, + 0x1) self.assertEqual( ld.hex(), 'e5e6be5a8fa6256f0245666bb237e2d028b7928148ce78d5' '1b8a64dc9506c377709a5b5d7ab75554593bced304fcff93') - def test_snp_with_multiple_vcpus(self): + def test_snp_with_multiple_vcpus_default(self): + ld = guest.calc_launch_digest( + SevMode.SEV_SNP, + 4, + vcpu_types.CPU_SIGS["EPYC-v4"], + "tests/fixtures/ovmf_AmdSev_suffix.bin", + "/dev/null", + "/dev/null", + "", + 0x21) + self.assertEqual( + ld.hex(), + '3aa1bdf5a87fad15960f099e82a09e428901c590f2b68d71' + 'aa246c168db5e75daf4819d017a9530c56bed2da5c0cdbd7') + + def test_snp_with_multiple_vcpus_feature_snp_only(self): ld = guest.calc_launch_digest( SevMode.SEV_SNP, 4, @@ -106,13 +212,29 @@ def test_snp_with_multiple_vcpus(self): "tests/fixtures/ovmf_AmdSev_suffix.bin", "/dev/null", "/dev/null", - "") + "", + 0x1) self.assertEqual( ld.hex(), '1c784beb8c49aa604b7fd57fbc73b36ec53a3f5fb48a2b89' '5ad6cc2ea15d18ee7cc15e3e57c792766b45f944c3e81cfe') - def test_snp_with_ovmfx64_without_kernel(self): + def test_snp_with_ovmfx64_without_default(self): + ld = guest.calc_launch_digest( + SevMode.SEV_SNP, + 1, + vcpu_types.CPU_SIGS["EPYC-v4"], + "tests/fixtures/ovmf_OvmfX64_suffix.bin", + None, + None, + None, + 0x21) + self.assertEqual( + ld.hex(), + '37a9efc939f360a9ccfaaf1a7702137b81ea00c38d0361c8' + '523285fad1b10e94ad8c1ecd7c82ff589cb120670be74a99') + + def test_snp_with_ovmfx64_without_kernel_feature_snp_only(self): ld = guest.calc_launch_digest( SevMode.SEV_SNP, 1, @@ -120,7 +242,8 @@ def test_snp_with_ovmfx64_without_kernel(self): "tests/fixtures/ovmf_OvmfX64_suffix.bin", None, None, - None) + None, + 0x1) self.assertEqual( ld.hex(), '7ef631fa7f659f7250de96c456a0eb7354bd3b9461982f38' @@ -135,7 +258,8 @@ def test_snp_with_ovmfx64_and_kernel_should_fail(self): "tests/fixtures/ovmf_OvmfX64_suffix.bin", "/dev/null", "/dev/null", - "") + "", + 0x21) self.assertEqual(str(c.exception), "Kernel specified but OVMF metadata doesn't include SNP_KERNEL_HASHES section") @@ -147,7 +271,8 @@ def test_seves(self): "tests/fixtures/ovmf_AmdSev_suffix.bin", "/dev/null", "/dev/null", - "") + "", + 0x21) self.assertEqual( ld.hex(), '2e91d54814445ad178180af09f881efe4079fc54bfddd0ec1179ecd3cdbdf772') @@ -160,7 +285,8 @@ def test_seves_with_multiple_vcpus(self): "tests/fixtures/ovmf_AmdSev_suffix.bin", "/dev/null", "/dev/null", - "") + "", + 0x21) self.assertEqual( ld.hex(), 'c05d37600072dc5ff24bafc49410f0369ba3a37c130a7bb7055ac6878be300f7') @@ -178,6 +304,7 @@ def test_seves_dump_vmsa(self): "/dev/null", "/dev/null", "", + 0x21, dump_vmsa=True) self.assertTrue(pathlib.Path("vmsa0.bin").exists()) self.assertTrue(pathlib.Path("vmsa1.bin").exists()) @@ -194,7 +321,8 @@ def test_seves_with_ovmfx64_and_kernel_should_fail(self): "tests/fixtures/ovmf_OvmfX64_suffix.bin", "/dev/null", "/dev/null", - "") + "", + 0x21) self.assertEqual(str(c.exception), "Kernel specified but OVMF doesn't support kernel/initrd/cmdline measurement") @@ -206,7 +334,8 @@ def test_sev(self): "tests/fixtures/ovmf_AmdSev_suffix.bin", "/dev/null", "/dev/null", - "console=ttyS0 loglevel=7") + "console=ttyS0 loglevel=7", + 0x21) self.assertEqual( ld.hex(), 'f0d92a1fda00249e008820bd40def6abbed2ee65fea8a8bc47e532863ca0cc6a') @@ -219,7 +348,8 @@ def test_sev_with_kernel_without_initrd_and_append(self): "tests/fixtures/ovmf_AmdSev_suffix.bin", "/dev/null", None, - None) + None, + 0x21) self.assertEqual( ld.hex(), '7332f6ef294f79919b46302e4541900a2dfc96714e2b7b4b5ccdc1899b78a195') @@ -233,7 +363,8 @@ def test_sev_with_ovmfx64_and_kernel_should_fail(self): "tests/fixtures/ovmf_OvmfX64_suffix.bin", "/dev/null", "/dev/null", - "") + "", + 0x21) self.assertEqual(str(c.exception), "Kernel specified but OVMF doesn't support kernel/initrd/cmdline measurement") @@ -251,6 +382,7 @@ def test_snp_dump_vmsa(self): "/dev/null", "/dev/null", "", + 0x21, snp_ovmf_hash_str=ovmf_hash, dump_vmsa=True) self.assertTrue(pathlib.Path("vmsa0.bin").exists()) @@ -264,7 +396,8 @@ def test_sev_with_ovmfx64_without_kernel(self): "tests/fixtures/ovmf_OvmfX64_suffix.bin", None, None, - None) + None, + 0x21) self.assertEqual( ld.hex(), 'af9d6c674b1ff04937084c98c99ca106b25c37b2c9541ac313e6e0c54426314f') @@ -279,6 +412,7 @@ def test_snp_svsm_4_vcpus(self): None, None, None, + 0x21, None, vmm_types.VMMType.QEMU, False, @@ -298,6 +432,7 @@ def test_snp_svsm_2_vcpus(self): None, None, None, + 0x21, None, vmm_types.VMMType.QEMU, False, @@ -320,6 +455,7 @@ def test_snp_svsm_dump_vmsa(self): None, None, None, + 0x21, None, vmm_types.VMMType.QEMU, True,