Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OVMF hashes #11

Merged
merged 1 commit into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,19 @@ Clone the Github repo and run the script directly from the local directory:

```
$ sev-snp-measure --help
usage: sev-snp-measure [-h] [--version] [-v] --mode {sev,seves,snp} [--vcpus N]
usage: sev-snp-measure [-h] [--version] [-v] --mode {sev,seves,snp,snp:ovmf-hash} [--vcpus N]
[--vcpu-type CPUTYPE] [--vcpu-sig VALUE] [--vcpu-family FAMILY]
[--vcpu-model MODEL] [--vcpu-stepping STEPPING] --ovmf PATH [--kernel PATH]
[--initrd PATH] [--append CMDLINE] [--output-format {hex,base64}]
[--snp-ovmf-hash HASH]

Calculate AMD SEV/SEV-ES/SEV-SNP guest launch measurement

optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
-v, --verbose
--mode {sev,seves,snp}
--mode {sev,seves,snp,snp:ovmf-hash}
Guest mode
--vcpus N Number of guest vcpus
--vcpu-type CPUTYPE Type of guest vcpu (EPYC, EPYC-v1, EPYC-v2, EPYC-IBPB, EPYC-v3, EPYC-v4,
Expand All @@ -56,6 +57,7 @@ optional arguments:
--append CMDLINE Kernel command line to calculate hash from (use with --kernel)
--output-format {hex,base64}
Measurement output format
--snp-ovmf-hash HASH Precalculated hash of the OVMF binary (hex string)
```

For example:
Expand Down Expand Up @@ -93,6 +95,26 @@ 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 ...`

## 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:

1. OVMF
2. (optional) -kernel, -initrd, -append arguments
3. Initial state of all vCPUs

In situations where only minor OVMF changes happen, you may not want to copy the full OVMF binary to the validation system. In these situations, you can cut digest calculation after the `OVMF` step and use its hash instead of the full binary.

To generate a hash, use the `--mode snp:ovmf-hash` parameter:

$ sev-snp-measure --mode snp:ovmf-hash --ovmf OVMF.fd
cab7e085874b3acfdbe2d96dcaa3125111f00c35c6fc9708464c2ae74bfdb048a198cb9a9ccae0b3e5e1a33f5f249819

On a different machine that only has access to an older but compatible OVMF binary, you can then ingest the hash again to generate a full measurement:

$ sev-snp-measure --mode snp --vcpus=1 --vcpu-type=EPYC-v4 --ovmf=OVMF.fd.old --ovmf-hash cab7e[...]
d52697c3e056fb8d698d19cc29adfbed5a8ec9170cb9eb63c2ac957d22b4eb647e25780162036d063a0cf418b8830acc

## Related projects

* libvirt tools: [virt-dom-sev-validate](https://gitlab.com/berrange/libvirt/-/blob/lgtm/tools/virt-dom-sev-validate.py),
Expand Down
9 changes: 7 additions & 2 deletions sevsnpmeasure/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def main() -> int:
description='Calculate AMD SEV/SEV-ES/SEV-SNP guest launch measurement')
parser.add_argument('--version', action='version', version=f'%(prog)s {VERSION}')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('--mode', choices=['sev', 'seves', 'snp'], help='Guest mode', required=True)
parser.add_argument('--mode', choices=['sev', 'seves', 'snp', 'snp:ovmf-hash'], help='Guest mode', required=True)
parser.add_argument('--vcpus', metavar='N', type=int, help='Number of guest vcpus', default=None)
parser.add_argument('--vcpu-type', metavar='CPUTYPE', choices=list(vcpu_types.CPU_SIGS.keys()),
help=f"Type of guest vcpu ({', '.join(vcpu_types.CPU_SIGS.keys())})",
Expand All @@ -41,8 +41,13 @@ def main() -> int:
parser.add_argument('--append', metavar='CMDLINE',
help='Kernel command line to calculate hash from (use with --kernel)')
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)')
args = parser.parse_args()

if args.mode == 'snp:ovmf-hash':
print(guest.calc_snp_ovmf_hash(args.ovmf).hex())
return 0

if args.mode != 'sev' and args.vcpus is None:
parser.error(f"missing --vcpus N in guest mode '{args.mode}'")

Expand All @@ -58,7 +63,7 @@ def main() -> int:
parser.error(f"missing --vcpu-type or --vcpu-sig or --vcpu-family in guest mode '{args.mode}'")

sev_mode = SevMode.from_str(args.mode)
ld = guest.calc_launch_digest(sev_mode, args.vcpus, vcpu_sig, args.ovmf, args.kernel, args.initrd, args.append)
ld = guest.calc_launch_digest(sev_mode, args.vcpus, vcpu_sig, args.ovmf, args.kernel, args.initrd, args.append, args.snp_ovmf_hash)

if args.output_format == "hex":
measurement = ld.hex()
Expand Down
4 changes: 2 additions & 2 deletions sevsnpmeasure/gctx.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class GCTX(object):
# 51 are cleared.
VMSA_GPA = 0xFFFFFFFFF000

def __init__(self):
self._ld = ZEROS
def __init__(self, seed: bytes = ZEROS):
self._ld = seed

def ld(self) -> bytes:
return self._ld
Expand Down
24 changes: 21 additions & 3 deletions sevsnpmeasure/guest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@


def calc_launch_digest(mode: SevMode, vcpus: int, vcpu_sig: int, ovmf_file: str,
kernel: str, initrd: str, append: str) -> bytes:
kernel: str, initrd: str, append: str, snp_ovmf_hash_str: str = '') -> 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)
return snp_calc_launch_digest(vcpus, vcpu_sig, ovmf_file, kernel, initrd, append, snp_ovmf_hash_str)
elif mode == SevMode.SEV_ES:
return seves_calc_launch_digest(vcpus, vcpu_sig, ovmf_file, kernel, initrd, append)
elif mode == SevMode.SEV:
Expand All @@ -38,11 +41,26 @@ def snp_update_metadata_pages(gctx, ovmf) -> None:
raise ValueError("unknown OVMF metadata section type")


def snp_calc_launch_digest(vcpus: int, vcpu_sig: int, ovmf_file: str, kernel: str, initrd: str, append: str) -> bytes:
def calc_snp_ovmf_hash(ovmf_file: str) -> bytes:
ovmf = OVMF(ovmf_file)

gctx = GCTX()
gctx.update_normal_pages(ovmf.gpa(), ovmf.data())
return gctx.ld()


def snp_calc_launch_digest(vcpus: int, vcpu_sig: int, ovmf_file: str, kernel: str, initrd: str, append: str, ovmf_hash_str: str) -> bytes:

gctx = GCTX()
ovmf = OVMF(ovmf_file)

# Allow users to provide a precalculated OVMF hash.
# Ignores the contents of the OVMF file in front of us.
if ovmf_hash_str:
ovmf_hash = bytearray.fromhex(ovmf_hash_str)
gctx = GCTX(seed = ovmf_hash)
else:
gctx.update_normal_pages(ovmf.gpa(), ovmf.data())
dubek marked this conversation as resolved.
Show resolved Hide resolved

if kernel:
sev_hashes_table_gpa = ovmf.sev_hashes_table_gpa()
Expand Down
40 changes: 40 additions & 0 deletions tests/test_guest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,46 @@

class TestGuest(unittest.TestCase):

# Test of we can generate a good OVMF hash
def test_snp_ovmf_hash_gen(self):
ovmf_hash = 'cab7e085874b3acfdbe2d96dcaa3125111f00c35c6fc9708464c2ae74bfdb048a198cb9a9ccae0b3e5e1a33f5f249819'
ld = guest.calc_launch_digest(
SevMode.SEV_SNP,
1,
vcpu_types.CPU_SIGS["EPYC-v4"],
"tests/fixtures/ovmf_suffix.bin",
"/dev/null",
"/dev/null",
"",
snp_ovmf_hash_str = ovmf_hash)
self.assertEqual(
ld.hex(),
'6a23d4774a60f6238506b531e0cb60a698a198db100476f6'
'fadb724f60c144bed9c71a3903b9ca425ff82b376c381b33')

# Test of we can a full LD from the OVMF hash
def test_snp_ovmf_hash_full(self):
ovmf_hash = guest.calc_snp_ovmf_hash("tests/fixtures/ovmf_suffix.bin").hex()
self.assertEqual(
ovmf_hash,
'4ef91bfd7241908300ac19305a694753cbc8db28104f356f'
'd7860cc7b4119db285ce80586c19bd358a731d5267cee60e')

ld = guest.calc_launch_digest(
SevMode.SEV_SNP,
1,
vcpu_types.CPU_SIGS["EPYC-v4"],
"tests/fixtures/ovmf_suffix.bin",
"/dev/null",
"/dev/null",
"",
snp_ovmf_hash_str = ovmf_hash)

self.assertEqual(
ld.hex(),
'38859e76ac5fa5009c8249eb2f44dafb33a2a1f41efd65ce'
'b13f042864abab87d018dc64da21628b320a98642f25ae6c')

def test_snp(self):
ld = guest.calc_launch_digest(
SevMode.SEV_SNP,
Expand Down