android_packages_modules_Vi.../pvmfw
Nikita Ioffe 831b08bc73 Disable native_coverage for pvmfw binary
The pvmfw binary is compiled against a very limited subset of Rust, and
hence building it with coverage enabled expectedly fails.

Explicitly disable coverage, as instead we will split the pvmfw binary
into separate testable rust crates.

Bug: 272222944
Test: SKIP_ABI_CHECKS=true \
      SOONG_COLLECT_JAVA_DEPS=true \
      EMMA_INSTRUMENT=true \
      EMMA_INSTRUMENT_FRAMEWORK=true \
      CLANG_COVERAGE=true \
      NATIVE_COVERAGE_PATHS='*' \
      m
Change-Id: I3cec0eca8fcac3dbc3269a8535788173f5a9eda5
2023-03-13 21:18:40 +00:00
..
avb Merge "libs/avb: Introduce libavb_baremetal" 2023-02-16 13:48:48 +00:00
src pvmfw: Refactor pKVM HVCs 2023-03-09 15:34:35 +00:00
Android.bp Disable native_coverage for pvmfw binary 2023-03-13 21:18:40 +00:00
README.md pvmfw: README: Detail boot.img header's kernel_size 2023-02-24 13:06:17 +00:00
TEST_MAPPING [avb][test] Move pvmfw_avb tests to a separate module 2023-01-13 12:18:24 +00:00
empty_file Add debug policy tests for ramdump 2023-02-14 10:31:15 +09:00
idmap.S pvmfw: Add MemoryTracker & MemorySlices 2022-11-28 22:28:05 +00:00
image.ld
platform.dts Hardcode memory base address in platform.dts 2023-03-03 17:36:01 +09:00

README.md

Protected Virtual Machine Firmware

In the context of the Android Virtualization Framework, a hypervisor (e.g. pKVM) enforces full memory isolation between its virtual machines (VMs) and the host. As a result, the host is only allowed to access memory that has been explicitly shared back by a VM. Such protected VMs (“pVMs”) are therefore able to manipulate secrets without being at risk of an attacker stealing them by compromising the Android host.

As pVMs are started dynamically by a virtual machine manager (“VMM”) running as a host process and as pVMs must not trust the host (see Why AVF?), the virtual machine it configures can't be trusted either. Furthermore, even though the isolation mentioned above allows pVMs to protect their secrets from the host, it does not help with provisioning them during boot. In particular, the threat model would prohibit the host from ever having access to those secrets, preventing the VMM from passing them to the pVM.

To address these concerns the hypervisor securely loads the pVM firmware (“pvmfw”) in the pVM from a protected memory region (this prevents the host or any pVM from tampering with it), setting it as the entry point of the virtual machine. As a result, pvmfw becomes the very first code that gets executed in the pVM, allowing it to validate the environment and abort the boot sequence if necessary. This process takes place whenever the VMM places a VM in protected mode and cant be prevented by the host.

Given the threat model, pvmfw is not allowed to trust the devices or device layout provided by the virtual platform it is running on as those are configured by the VMM. Instead, it performs all the necessary checks to ensure that the pVM was set up as expected. For functional purposes, the interface with the hypervisor, although trusted, is also validated.

Once it has been determined that the platform can be trusted, pvmfw derives unique secrets for the guest through the Boot Certificate Chain ("BCC", see Open Profile for DICE) that can be used to prove the identity of the pVM to local and remote actors. If any operation or check fails, or in case of a missing prerequisite, pvmfw will abort the boot process of the pVM, effectively preventing non-compliant pVMs and/or guests from running. Otherwise, it hands over the pVM to the guest kernel by jumping to its first instruction, similarly to a bootloader.

pvmfw currently only supports AArch64.

Integration

pvmfw Loading

When running pKVM, the physical memory from which the hypervisor loads pvmfw into guest address space is not initially populated by the hypervisor itself. Instead, it receives a pre-loaded memory region from a trusted pvmfw loader and only then becomes responsible for protecting it. As a result, the hypervisor is kept generic (beyond AVF) and small as it is not expected (nor necessary) for it to know how to interpret or obtain the content of that region.

Android Bootloader (ABL) Support

Starting in Android T, the PRODUCT_BUILD_PVMFW_IMAGE build variable controls the generation of pvmfw.img, a new ABL partition containing the pvmfw binary (sometimes called "pvmfw.bin") and following the internal format of the boot partition, intended to be verified and loaded by ABL on AVF-compatible devices.

Once ABL has verified the pvmfw.img chained static partition, the contained boot.img header may be used to obtain the size of the pvmfw.bin image (recorded in the kernel_size field), as it already does for the kernel itself. In accordance with the header format, the kernel_size bytes of the partition following the header will be the pvmfw.bin image.

Note that when it gets executed in the context of a pVM, pvmfw expects to have been loaded at 4KiB-aligned intermediate physical address (IPA) so if ABL loads the pvmfw.bin image without respecting this alignment, it is the responsibility of the hypervisor to either reject the image or copy it into guest address space with the right alignment.

To support pKVM, ABL is expected to describe the region using a reserved memory device tree node where both address and size have been properly aligned to the page size used by the hypervisor. This single region must include both the pvmfw binary image and its configuration data (see below). For example, the following node describes a region of size 0x40000 at address 0x80000000:

reserved-memory {
    ...
    pkvm_guest_firmware {
        compatible = "linux,pkvm-guest-firmware-memory";
        reg = <0x0 0x80000000 0x40000>;
        no-map;
    }
}

Configuration Data

As part of the process of loading pvmfw, the loader (typically the Android Bootloader, "ABL") is expected to pass device-specific pvmfw configuration data by appending it to the pvmfw binary and including it in the region passed to the hypervisor. As a result, the hypervisor will give the same protection to this data as it does to pvmfw and will transparently load it in guest memory, making it available to pvmfw at runtime. This enables pvmfw to be kept device-agnostic, simplifying its adoption and distribution as a centralized signed binary, while also being able to support device-specific details.

The configuration data will be read by pvmfw at the next 4KiB boundary from the end of its loaded binary. Even if the pvmfw is position-independent, it will be expected for it to also have been loaded at a 4-KiB boundary. As a result, the location of the configuration data is implicitly passed to pvmfw and known to it at build time.

Configuration Data Format

The configuration data is described using the following header:

+===============================+
|          pvmfw.bin            |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
|  (Padding to 4KiB alignment)  |
+===============================+ <-- HEAD
|      Magic (= 0x666d7670)     |
+-------------------------------+
|           Version             |
+-------------------------------+
|   Total Size = (TAIL - HEAD)  |
+-------------------------------+
|            Flags              |
+-------------------------------+
|           [Entry 0]           |
|  offset = (FIRST - HEAD)      |
|  size = (FIRST_END - FIRST)   |
+-------------------------------+
|           [Entry 1]           |
|  offset = (SECOND - HEAD)     |
|  size = (SECOND_END - SECOND) |
+-------------------------------+
|              ...              |
+-------------------------------+
|           [Entry n]           |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| (Padding to 8-byte alignment) |
+===============================+ <-- FIRST
|        {First blob: BCC}      |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- FIRST_END
| (Padding to 8-byte alignment) |
+===============================+ <-- SECOND
|        {Second blob: DP}      |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- SECOND_END
| (Padding to 8-byte alignment) |
+===============================+
|              ...              |
+===============================+ <-- TAIL

Where the version number is encoded using a "major.minor" as follows

((major << 16) | (minor & 0xffff))

and defines the format of the header (which may change between major versions), its size and, in particular, the expected number of appended blobs. Each blob is referred to by its offset in the entry array and may be mandatory or optional (as defined by this specification), where missing entries are denoted by a zero size. It is therefore not allowed to trim missing optional entries from the end of the array. The header uses the endianness of the virtual machine.

The header format itself is agnostic of the internal format of the individual blos it refers to. In version 1.0, it describes two blobs:

  • entry 0 must point to a valid BCC Handover (see below)
  • entry 1 may point to a DTBO to be applied to the pVM device tree

Virtual Platform Boot Certificate Chain Handover

The format of the BCC entry mentioned above, compatible with the BccHandover defined by the Open Profile for DICE reference implementation, is described by the following CDDL:

PvmfwBccHandover = {
  1 : bstr .size 32,     ; CDI_Attest
  2 : bstr .size 32,     ; CDI_Seal
  3 : Bcc,               ; Certificate chain
}

and contains the Compound Device Identifiers ("CDIs"), used to derive the next-stage secret, and a certificate chain, intended for pVM attestation. Note that it differs from the BccHandover defined by the specification in that its Bcc field is mandatory (while optional in the original).

The handover expected by pvmfw can be generated as follows:

  • by passing a BccHandover received from a previous boot stage (e.g. Trusted Firmware, ROM bootloader, ...) to BccHandoverMainFlow;

  • by generating a BccHandover (as an example, see Trusty) with both CDIs set to an arbitrary constant value and no Bcc, and pass it to BccHandoverMainFlow, which will both derive the pvmfw CDIs and start a valid certificate chain, making the pvmfw loader the root of the BCC.

The recommended DICE inputs at this stage are:

  • Code: hash of the pvmfw image, hypervisor (boot.img), and other target code relevant to the secure execution of pvmfw (e.g. vendor_boot.img)
  • Configuration Data: any extra input relevant to pvmfw security
  • Authority Data: must cover all the public keys used to sign and verify the code contributing to the Code input
  • Mode Decision: Set according to the specification. In particular, should only be Normal if secure boot is being properly enforced (e.g. locked device in Android Verified Boot)
  • Hidden Inputs: Factory Reset Secret (FRS, stored in a tamper evident storage and changes during every factory reset) or similar that changes as part of the device lifecycle (e.g. reset)

The resulting BccHandover is then used by pvmfw in a similar way to derive another DICE layer, passed to the guest through a /reserved-memory device tree node marked as compatible=”google,open-dice”.