Skip to content

Commit

Permalink
Add support for OVMF hashes
Browse files Browse the repository at this point in the history
Some times we may not want to have to keep the full OVMF binary handy
when we try to validate a measurement. Most of the data we extract out
of the OVMF binary is static, except for its actual hashes.

So let's introduce a new mode that generates a precalculated launch
digest that contains the SNP ld hash at the point where we fully
ingested OVMF's contents:

  $ sev-snp-measure --mode snp:ovmf-hash --ovmf OVMF.fd
  cab7e[...]

In addition, add a new optional parameter that a user can then use to
consume the hash instead of recalculating it from their OVMF binary at
hand:

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

which is identical to the full calculation with the new OVMF.fd:

  $ sev-snp-measure --mode snp --vcpus=1 --vcpu-type=EPYC-v4 --ovmf=OVMF.fd
  d5269[...]

With this in place, we no longer need to copy full OVMF binaries around.
Instead, for almost all OVMF binaries in existence today, we can merely
share their hash values and are still able to validate the measurement
correctness.

Signed-off-by: Alexander Graf <graf@amazon.com>
  • Loading branch information
agraf committed Mar 27, 2023
1 parent 63ae739 commit bd04510
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 9 deletions.
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())

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

0 comments on commit bd04510

Please sign in to comment.