Message ID | 20241125195626.856992-27-jean-philippe@linaro.org |
---|---|
State | New |
Headers | show |
Series | arm: Run Arm CCA VMs with KVM | expand |
On 11/25/24 2:56 PM, Jean-Philippe Brucker wrote: > Create an event log, in the format defined by Trusted Computing Group > for TPM2. It contains information about the VMM, the Realm parameters, > any data loaded into guest memory before boot and the initial vCPU > state. > > The guest can access this log from RAM and send it to a verifier, to > help the verifier independently compute the Realm Initial Measurement, > and check that the data we load into guest RAM is known-good images. > Without this log, the verifier has to guess where everything is loaded> and in what order. Typically these logs are backed by extensions of TPM PCRs and when you send a log to a verifier you send a TPM quote along with it for the verifer to replay the log and check the TPM quote. Also, early code in the firmware is typically serving as a root of trust that starts the chain of measurements of code and data, first measuring itself and then other parts of the firmware before it jumps into the other parts. Now here you seem to just have a log and no PCR extensions and therefore no quote over PCRs can be used. Then what prevents anyone from faking this log and presenting a completely fake log to the verifier?
On Mon, Nov 25, 2024 at 05:23:44PM -0500, Stefan Berger wrote: > > > On 11/25/24 2:56 PM, Jean-Philippe Brucker wrote: > > Create an event log, in the format defined by Trusted Computing Group > > for TPM2. It contains information about the VMM, the Realm parameters, > > any data loaded into guest memory before boot and the initial vCPU > > state. > > > > The guest can access this log from RAM and send it to a verifier, to > > help the verifier independently compute the Realm Initial Measurement, > > and check that the data we load into guest RAM is known-good images. > > Without this log, the verifier has to guess where everything is loaded> > and in what order. > > Typically these logs are backed by extensions of TPM PCRs and when you send > a log to a verifier you send a TPM quote along with it for the verifer to > replay the log and check the TPM quote. Also, early code in the firmware is > typically serving as a root of trust that starts the chain of measurements > of code and data, first measuring itself and then other parts of the > firmware before it jumps into the other parts. Now here you seem to just > have a log and no PCR extensions and therefore no quote over PCRs can be > used. Then what prevents anyone from faking this log and presenting a > completely fake log to the verifier? In addition, a measurement log is just one of the interesting features that a TPM provides to OS. The other TPM features are still relevant and useful to confidential VMs. As a high level goal I think we should be aiming to make it possible for users to move their existing VM workloads from non-confidentail to confidential environments, simply as a choice at deployment time. To make this as practical as possible, confidential VMs need to be aiming to match non-confidential VM features where ever it is practical to do so. Users & vendors should not need to build & carry around 2 sets of disk images - one setup for confidential and one setup for non-confidential. Following existing standards will reduce the work both for OS developers, app developers and users alike, to adopt the CVM world. IOW, this is a long winded way of saying that we should be looking to provide a complete *standards compliant*, trusted TPM implementation to confidential VMs, not providing a cherry-picked selection of a few TPM-like features. On the x86 side of things, the route to providing a trusted TPM is via SVSM, both for SNP and TDX. Microsoft's recently open sources openhcl similarly provides a st I don't know so much about RME. Is providing a trusted TPM a job for the RMM ? With regards, Daniel
On Tue, Nov 26, 2024 at 01:45:55PM +0000, Daniel P. Berrangé wrote: > On Mon, Nov 25, 2024 at 05:23:44PM -0500, Stefan Berger wrote: > > > > > > On 11/25/24 2:56 PM, Jean-Philippe Brucker wrote: > > > Create an event log, in the format defined by Trusted Computing Group > > > for TPM2. It contains information about the VMM, the Realm parameters, > > > any data loaded into guest memory before boot and the initial vCPU > > > state. > > > > > > The guest can access this log from RAM and send it to a verifier, to > > > help the verifier independently compute the Realm Initial Measurement, > > > and check that the data we load into guest RAM is known-good images. > > > Without this log, the verifier has to guess where everything is loaded> > > and in what order. > > > > Typically these logs are backed by extensions of TPM PCRs and when you send > > a log to a verifier you send a TPM quote along with it for the verifer to > > replay the log and check the TPM quote. Also, early code in the firmware is > > typically serving as a root of trust that starts the chain of measurements > > of code and data, first measuring itself and then other parts of the > > firmware before it jumps into the other parts. Now here you seem to just > > have a log and no PCR extensions and therefore no quote over PCRs can be > > used. Indeed, in our case it's the trusted hypervisor (RMM) that provides the equivalent to TPM quote and PCRs. In more details: 1. QEMU loads images into guest RAM by calling KVM, which calls RMM. 2. RMM calculates a hash of the image content, adds it to a rolling hash the "Realm Initial Measurement" (RIM), which I believe is equivalent to a PCR. 3. During remote attestation, the guest sends evidence containing this RIM signed by the root of trust, along with a signed token identifying the platform (hardware, firmware, RMM). 4. The verifier checks the signature and the platform token, so it trusts the RMM and the RIM. > > Then what prevents anyone from faking this log and presenting a > > completely fake log to the verifier? Absolutely, the verifier does not trust the content of the log, it only uses the log as helper to try to reconstruct the RIM. For example a log event says "I loaded image XYZ at address A", then the verifier searches image XYZ in its database of known-good images, calculates the hash that would result from loading that image at address A. Any malformed event in the log causes the hash to diverge from the trusted RIM value, and causes an attestation error. > In addition, a measurement log is just one of the interesting features > that a TPM provides to OS. The other TPM features are still relevant > and useful to confidential VMs. > > As a high level goal I think we should be aiming to make it possible for > users to move their existing VM workloads from non-confidentail to > confidential environments, simply as a choice at deployment time. To make > this as practical as possible, confidential VMs need to be aiming to > match non-confidential VM features where ever it is practical to do so. > Users & vendors should not need to build & carry around 2 sets of disk > images - one setup for confidential and one setup for non-confidential. > Following existing standards will reduce the work both for OS developers, > app developers and users alike, to adopt the CVM world. > > IOW, this is a long winded way of saying that we should be looking to > provide a complete *standards compliant*, trusted TPM implementation to > confidential VMs, not providing a cherry-picked selection of a few > TPM-like features. > > On the x86 side of things, the route to providing a trusted TPM is via > SVSM, both for SNP and TDX. Microsoft's recently open sources openhcl > similarly provides a st > > I don't know so much about RME. Is providing a trusted TPM a job for > the RMM ? Not directly, but I've heard of at least two options that are being actively worked on: * running payloads like SVSM and openhcl that emulate a TPM. In RMM 1.1 there is a concept of "planes" that enables this. * having edk2 in the VM provide a "fTPM". I'm less familiar with these, but I think both need to connect the virtual TPM to the root of trust by performing remote attestation via RMM (?). So the problem of reconstructing a RIM on the verifier side remains, even if it's not done by the application. In addition I'm wondering about lighter, container-like workloads, which will want to boot quickly and run the bare minimum software, and where edk2 or SVSM seems superfluous. Maybe people will want to tailor those workloads and avoid the extra layer? Thanks, Jean
On 11/26/24 11:21 AM, Jean-Philippe Brucker wrote: > On Tue, Nov 26, 2024 at 01:45:55PM +0000, Daniel P. Berrangé wrote: >> On Mon, Nov 25, 2024 at 05:23:44PM -0500, Stefan Berger wrote: >>> >>> >>> On 11/25/24 2:56 PM, Jean-Philippe Brucker wrote: >>>> Create an event log, in the format defined by Trusted Computing Group >>>> for TPM2. It contains information about the VMM, the Realm parameters, >>>> any data loaded into guest memory before boot and the initial vCPU >>>> state. >>>> >>>> The guest can access this log from RAM and send it to a verifier, to >>>> help the verifier independently compute the Realm Initial Measurement, >>>> and check that the data we load into guest RAM is known-good images. >>>> Without this log, the verifier has to guess where everything is loaded> >>> and in what order. >>> >>> Typically these logs are backed by extensions of TPM PCRs and when you send >>> a log to a verifier you send a TPM quote along with it for the verifer to >>> replay the log and check the TPM quote. Also, early code in the firmware is >>> typically serving as a root of trust that starts the chain of measurements >>> of code and data, first measuring itself and then other parts of the >>> firmware before it jumps into the other parts. Now here you seem to just >>> have a log and no PCR extensions and therefore no quote over PCRs can be >>> used. > > Indeed, in our case it's the trusted hypervisor (RMM) that provides the > equivalent to TPM quote and PCRs. In more details: > > 1. QEMU loads images into guest RAM by calling KVM, which calls RMM. > 2. RMM calculates a hash of the image content, adds it to a rolling hash > the "Realm Initial Measurement" (RIM), which I believe is equivalent to > a PCR. I am not familiar with RIM. A link to read more about it would be helpful. > 3. During remote attestation, the guest sends evidence containing this RIM > signed by the root of trust, along with a signed token identifying the > platform (hardware, firmware, RMM). Is this a well known manufacturer key that one would expect for signature verification or is it locally created? > 4. The verifier checks the signature and the platform token, so it trusts > the RMM and the RIM. > >>> Then what prevents anyone from faking this log and presenting a >>> completely fake log to the verifier? > > Absolutely, the verifier does not trust the content of the log, it only > uses the log as helper to try to reconstruct the RIM. For example a log > event says "I loaded image XYZ at address A", then the verifier searches > image XYZ in its database of known-good images, calculates the hash that > would result from loading that image at address A. Any malformed event in Hopefully just calculating a hash over the image will do and the location an image was loaded to, like address A (relocation?), doesn't matter...
On Mon, Dec 02, 2024 at 10:58:01AM -0500, Stefan Berger wrote: > > > On 11/26/24 11:21 AM, Jean-Philippe Brucker wrote: > > On Tue, Nov 26, 2024 at 01:45:55PM +0000, Daniel P. Berrangé wrote: > > > On Mon, Nov 25, 2024 at 05:23:44PM -0500, Stefan Berger wrote: > > > > > > > > > > > > On 11/25/24 2:56 PM, Jean-Philippe Brucker wrote: > > > > > Create an event log, in the format defined by Trusted Computing Group > > > > > for TPM2. It contains information about the VMM, the Realm parameters, > > > > > any data loaded into guest memory before boot and the initial vCPU > > > > > state. > > > > > > > > > > The guest can access this log from RAM and send it to a verifier, to > > > > > help the verifier independently compute the Realm Initial Measurement, > > > > > and check that the data we load into guest RAM is known-good images. > > > > > Without this log, the verifier has to guess where everything is loaded> > > > > and in what order. > > > > > > > > Typically these logs are backed by extensions of TPM PCRs and when you send > > > > a log to a verifier you send a TPM quote along with it for the verifer to > > > > replay the log and check the TPM quote. Also, early code in the firmware is > > > > typically serving as a root of trust that starts the chain of measurements > > > > of code and data, first measuring itself and then other parts of the > > > > firmware before it jumps into the other parts. Now here you seem to just > > > > have a log and no PCR extensions and therefore no quote over PCRs can be > > > > used. > > > > Indeed, in our case it's the trusted hypervisor (RMM) that provides the > > equivalent to TPM quote and PCRs. In more details: > > > > 1. QEMU loads images into guest RAM by calling KVM, which calls RMM. > > 2. RMM calculates a hash of the image content, adds it to a rolling hash > > the "Realm Initial Measurement" (RIM), which I believe is equivalent to > > a PCR. > > I am not familiar with RIM. A link to read more about it would be helpful. The "Learn the architecture" documentation might be a good introduction https://developer.arm.com/documentation/den0127/0200/Overview In particular the part about Realm creation: https://developer.arm.com/documentation/den0127/0200/Realm-management/Realm-creation-and-attestation The RMM specification describes exactly how the RIM is calculated, but is less palatable: https://developer.arm.com/documentation/den0137/1-0rel0/?lang=en A7.1.1 Realm Initial Measurement More specialized resource are the attestation token documentation: [1] https://datatracker.ietf.org/doc/html/draft-ffm-rats-cca-token-00 and CCA Security Model: https://developer.arm.com/documentation/DEN0096/latest/ > > > 3. During remote attestation, the guest sends evidence containing this RIM > > signed by the root of trust, along with a signed token identifying the > > platform (hardware, firmware, RMM). > > Is this a well known manufacturer key that one would expect for signature > verification or is it locally created? It comes from a well known manufacturer key, although the signing can be delegated in some models (like in the current demos): The hardware RoT creates a key pair for the RMM, which the RMM uses to sign the RIM. The RoT then signs the RMM pubkey, using the well-known key (see [1] 4.10 Token Binding). > > > 4. The verifier checks the signature and the platform token, so it trusts > > the RMM and the RIM. > > > > > > Then what prevents anyone from faking this log and presenting a > > > > completely fake log to the verifier? > > > > Absolutely, the verifier does not trust the content of the log, it only > > uses the log as helper to try to reconstruct the RIM. For example a log > > event says "I loaded image XYZ at address A", then the verifier searches > > image XYZ in its database of known-good images, calculates the hash that > > would result from loading that image at address A. Any malformed event in > > Hopefully just calculating a hash over the image will do and the location an > image was loaded to, like address A (relocation?), doesn't matter... In this case it does matter because we don't trust the host VMM which instructs RMM where to load the images. So we need to verify that the image is in the correct location, otherwise the VM could be executing the wrong part of the code, or using the wrong part of the data loaded into RAM. When loading an image the RMM hashes its content, then hashes a structure that contains both the content hash and the address, and it is that hash that is added to the RIM. Thanks, Jean
On 12/5/24 7:33 AM, Jean-Philippe Brucker wrote: > On Mon, Dec 02, 2024 at 10:58:01AM -0500, Stefan Berger wrote: >> >> >> On 11/26/24 11:21 AM, Jean-Philippe Brucker wrote: >>> On Tue, Nov 26, 2024 at 01:45:55PM +0000, Daniel P. Berrangé wrote: >>>> On Mon, Nov 25, 2024 at 05:23:44PM -0500, Stefan Berger wrote: >>>>> >>>>> >>>>> On 11/25/24 2:56 PM, Jean-Philippe Brucker wrote: >>>>>> Create an event log, in the format defined by Trusted Computing Group >>>>>> for TPM2. It contains information about the VMM, the Realm parameters, >>>>>> any data loaded into guest memory before boot and the initial vCPU >>>>>> state. >>>>>> >>>>>> The guest can access this log from RAM and send it to a verifier, to >>>>>> help the verifier independently compute the Realm Initial Measurement, >>>>>> and check that the data we load into guest RAM is known-good images. >>>>>> Without this log, the verifier has to guess where everything is loaded> >>>>> and in what order. >>>>> >>>>> Typically these logs are backed by extensions of TPM PCRs and when you send >>>>> a log to a verifier you send a TPM quote along with it for the verifer to >>>>> replay the log and check the TPM quote. Also, early code in the firmware is >>>>> typically serving as a root of trust that starts the chain of measurements >>>>> of code and data, first measuring itself and then other parts of the >>>>> firmware before it jumps into the other parts. Now here you seem to just >>>>> have a log and no PCR extensions and therefore no quote over PCRs can be >>>>> used. >>> >>> Indeed, in our case it's the trusted hypervisor (RMM) that provides the >>> equivalent to TPM quote and PCRs. In more details: >>> >>> 1. QEMU loads images into guest RAM by calling KVM, which calls RMM. >>> 2. RMM calculates a hash of the image content, adds it to a rolling hash >>> the "Realm Initial Measurement" (RIM), which I believe is equivalent to >>> a PCR. >> >> I am not familiar with RIM. A link to read more about it would be helpful. > > The "Learn the architecture" documentation might be a good introduction > https://developer.arm.com/documentation/den0127/0200/Overview > In particular the part about Realm creation: > https://developer.arm.com/documentation/den0127/0200/Realm-management/Realm-creation-and-attestation > > The RMM specification describes exactly how the RIM is calculated, but > is less palatable: > https://developer.arm.com/documentation/den0137/1-0rel0/?lang=en > A7.1.1 Realm Initial Measurement > > More specialized resource are the attestation token documentation: > [1] https://datatracker.ietf.org/doc/html/draft-ffm-rats-cca-token-00 > and CCA Security Model: > https://developer.arm.com/documentation/DEN0096/latest/ Thanks for the links. I will have a look at them when I have time. > >> >>> 3. During remote attestation, the guest sends evidence containing this RIM >>> signed by the root of trust, along with a signed token identifying the >>> platform (hardware, firmware, RMM). >> >> Is this a well known manufacturer key that one would expect for signature >> verification or is it locally created? > > It comes from a well known manufacturer key, although the signing can be > delegated in some models (like in the current demos): > > The hardware RoT creates a key pair for the RMM, which the RMM uses to > sign the RIM. The RoT then signs the RMM pubkey, using the well-known key > (see [1] 4.10 Token Binding). You should mention in the commit message that the log will be signed and user space can get the signature over the log from some filesystem or so. Stefan
On 11/25/24 2:56 PM, Jean-Philippe Brucker wrote: > Create an event log, in the format defined by Trusted Computing Group s/,// > for TPM2. It contains information about the VMM, the Realm parameters, > any data loaded into guest memory before boot and the initial vCPU s/ and/ ,and/ [move comma from above to before 'and'] > state. > > The guest can access this log from RAM and send it to a verifier, to > help the verifier independently compute the Realm Initial Measurement, > and check that the data we load into guest RAM is known-good images. > Without this log, the verifier has to guess where everything is loaded > and in what order. Mention that the verifier can pull out the signature from somewhere as well... > > Cc: Stefan Berger <stefanb@linux.vnet.ibm.com> > Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org> > --- > v2->v3: New > --- > qapi/qom.json | 9 +- > target/arm/kvm_arm.h | 27 +++ > target/arm/kvm-rme.c | 415 ++++++++++++++++++++++++++++++++++++++++++- > target/arm/Kconfig | 1 + > 4 files changed, 449 insertions(+), 3 deletions(-) > > diff --git a/qapi/qom.json b/qapi/qom.json > index 901ba67634..1de1b0d8af 100644 > --- a/qapi/qom.json > +++ b/qapi/qom.json > @@ -1094,11 +1094,18 @@ > # @measurement-algorithm: Realm measurement algorithm > # (default: sha512) > # > +# @measurement-log: Enable a measurement log for the Realm. All events > +# that contribute to the Realm Initial Measurement (RIM) are added > +# to a log in TCG TPM2 format, which is itself loaded into Realm > +# memory (unmeasured) and can then be read by a verifier to > +# reconstruct the RIM. > +# > # Since: 9.3 > ## > { 'struct': 'RmeGuestProperties', > 'data': { '*personalization-value': 'str', > - '*measurement-algorithm': 'RmeGuestMeasurementAlgorithm' } } > + '*measurement-algorithm': 'RmeGuestMeasurementAlgorithm', > + '*measurement-log': 'bool'} } > > ## > # @ObjectType: > diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h > index 77680f238a..44e95a034b 100644 > --- a/target/arm/kvm_arm.h > +++ b/target/arm/kvm_arm.h > @@ -268,6 +268,24 @@ int kvm_arm_rme_vcpu_init(CPUState *cs); > */ > void kvm_arm_rme_init_guest_ram(hwaddr base, size_t size); > > +/** > + * kvm_arm_rme_get_measurement_log > + * > + * Obtain the measurement log object if enabled, in order to get its size and > + * set its base address. > + * > + * Returns NULL if measurement log is disabled. > + */ > +Object *kvm_arm_rme_get_measurement_log(void); > + > +/** > + * kvm_arm_rme_set_ipa_size > + * @ipa_bits: number of guest physical address bits > + * > + * Set the GPA size, not counting the bit reserved for shared address range. > + */ > +void kvm_arm_rme_set_ipa_size(uint8_t ipa_bits); > + > #else > > /* > @@ -298,6 +316,15 @@ static inline void kvm_arm_rme_init_guest_ram(hwaddr base, size_t size) > { > } > > +static inline Object *kvm_arm_rme_get_measurement_log(void) > +{ > + return NULL; > +} > + > +static inline void kvm_arm_rme_set_ipa_size(uint8_t ipa_size) > +{ > +} > + > /* > * These functions should never actually be called without KVM support. > */ > diff --git a/target/arm/kvm-rme.c b/target/arm/kvm-rme.c > index bf0bcf9a38..f92cfdb5f3 100644 > --- a/target/arm/kvm-rme.c > +++ b/target/arm/kvm-rme.c > @@ -10,10 +10,12 @@ > #include "hw/boards.h" > #include "hw/core/cpu.h" > #include "hw/loader.h" > +#include "hw/tpm/tpm_log.h" > #include "kvm_arm.h" > #include "migration/blocker.h" > #include "qapi/error.h" > #include "qemu/error-report.h" > +#include "qemu/units.h" > #include "qom/object_interfaces.h" > #include "sysemu/kvm.h" > #include "sysemu/runstate.h" > @@ -25,6 +27,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(RmeGuest, RME_GUEST) > > #define RME_MAX_CFG 2 > > +#define RME_MEASUREMENT_LOG_SIZE (64 * KiB) > + > struct RmeGuest { > ConfidentialGuestSupport parent_obj; > Notifier rom_load_notifier; > @@ -32,22 +36,344 @@ struct RmeGuest { > > uint8_t *personalization_value; > RmeGuestMeasurementAlgorithm measurement_algo; > + bool use_measurement_log; > > + size_t num_cpus; > + uint8_t ipa_bits; > hwaddr ram_base; > size_t ram_size; > + > + TpmLog *log; > + GHashTable *images; > }; > > OBJECT_DEFINE_SIMPLE_TYPE_WITH_INTERFACES(RmeGuest, rme_guest, RME_GUEST, > CONFIDENTIAL_GUEST_SUPPORT, > { TYPE_USER_CREATABLE }, { }) > > +typedef struct RmeLogFiletype { > + uint32_t event_type; > + /* Description copied into the log event */ > + const char *desc; > +} RmeLogFiletype; > + > typedef struct { > hwaddr base; > hwaddr size; > + uint8_t *data; > + RmeLogFiletype *filetype; > } RmeRamRegion; > > +typedef struct { > + char signature[16]; > + char name[32]; > + char version[40]; > + uint64_t ram_size; > + uint32_t num_cpus; > + uint64_t flags; > +} EventLogVmmVersion; > + > +typedef struct { > + uint32_t id; > + uint32_t data_size; > + uint8_t data[]; > +} EventLogTagged; > + > +#define EVENT_LOG_TAG_REALM_CREATE 1 > +#define EVENT_LOG_TAG_INIT_RIPAS 2 > +#define EVENT_LOG_TAG_REC_CREATE 3 > + If these are ARM-related structures and constants from a document you may want to mention the document you got them from. > +#define REALM_PARAMS_FLAG_SVE (1 << 1) > +#define REALM_PARAMS_FLAG_PMU (1 << 2) > + > +#define REC_CREATE_FLAG_RUNNABLE (1 << 0) > + > static RmeGuest *rme_guest; > > +static int rme_init_measurement_log(MachineState *ms) > +{ > + Object *log; > + gpointer filename; > + TpmLogDigestAlgo algo; > + RmeLogFiletype *filetype; > + > + if (!rme_guest->use_measurement_log) { > + return 0; > + } > + > + switch (rme_guest->measurement_algo) { > + case RME_GUEST_MEASUREMENT_ALGORITHM_SHA256: > + algo = TPM_LOG_DIGEST_ALGO_SHA256; > + break; > + case RME_GUEST_MEASUREMENT_ALGORITHM_SHA512: > + algo = TPM_LOG_DIGEST_ALGO_SHA512; > + break; > + default: > + g_assert_not_reached(); > + } > + > + log = object_new_with_props(TYPE_TPM_LOG, OBJECT(rme_guest), > + "log", &error_fatal, > + "digest-algo", TpmLogDigestAlgo_str(algo), > + NULL); > + > + tpm_log_create(TPM_LOG(log), RME_MEASUREMENT_LOG_SIZE, &error_fatal); > + rme_guest->log = TPM_LOG(log); > + > + /* > + * Write down the image names we're expecting to encounter when handling the > + * ROM load notifications, so we can record the type of image being loaded > + * to help the verifier. > + */ > + rme_guest->images = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, > + g_free); > + > + filename = g_strdup(ms->kernel_filename); > + if (filename) { > + filetype = g_new0(RmeLogFiletype, 1); > + filetype->event_type = TCG_EV_POST_CODE2; > + filetype->desc = "KERNEL"; > + g_hash_table_insert(rme_guest->images, filename, (gpointer)filetype); > + } > + > + filename = g_strdup(ms->initrd_filename); > + if (filename) { > + filetype = g_new0(RmeLogFiletype, 1); > + filetype->event_type = TCG_EV_POST_CODE2; > + filetype->desc = "INITRD"; > + g_hash_table_insert(rme_guest->images, filename, (gpointer)filetype); > + } > + > + filename = g_strdup(ms->firmware); > + if (filename) { > + filetype = g_new0(RmeLogFiletype, 1); > + filetype->event_type = TCG_EV_EFI_PLATFORM_FIRMWARE_BLOB2; > + filetype->desc = "FIRMWARE"; > + g_hash_table_insert(rme_guest->images, filename, filetype); > + } > + > + filename = g_strdup(ms->dtb); > + if (!filename) { > + filename = g_strdup("dtb"); > + } > + filetype = g_new0(RmeLogFiletype, 1); > + filetype->event_type = TCG_EV_POST_CODE2; > + filetype->desc = "DTB"; > + g_hash_table_insert(rme_guest->images, filename, filetype); > + > + return 0; > +} > + > +static int rme_log_event_tag(uint32_t id, uint8_t *data, size_t size, > + Error **errp) > +{ > + int ret; > + EventLogTagged event = { > + .id = id, > + .data_size = size, > + }; > + GByteArray *bytes = g_byte_array_new(); > + > + if (!rme_guest->log) { > + return 0; > + } > + > + g_byte_array_append(bytes, (uint8_t *)&event, sizeof(event)); > + g_byte_array_append(bytes, data, size); > + ret = tpm_log_add_event(rme_guest->log, TCG_EV_EVENT_TAG, bytes->data, > + bytes->len, NULL, 0, errp); > + g_byte_array_free(bytes, true); > + return ret; > +} > + > +/* Log VM type and Realm Descriptor create */ > +static int rme_log_realm_create(Error **errp) > +{ > + int ret; > + ARMCPU *cpu; > + EventLogVmmVersion vmm_version = { > + .signature = "VM VERSION", > + .name = "QEMU", > + .version = "9.1", /* TODO: dynamic */ $ grep -r QEMU_VERSION_M build/ build/config-host.h:#define QEMU_VERSION_MAJOR 9 build/config-host.h:#define QEMU_VERSION_MICRO 93 build/config-host.h:#define QEMU_VERSION_MINOR 1 $ cat VERSION 9.1.93 > + .ram_size = cpu_to_le64(rme_guest->ram_size), > + .num_cpus = cpu_to_le32(rme_guest->num_cpus), > + .flags = 0, > + }; > + struct { > + uint64_t flags; > + uint8_t s2sz; > + uint8_t sve_vl; > + uint8_t num_bps; > + uint8_t num_wps; > + uint8_t pmu_num_ctrs; > + uint8_t hash_algo; > + } params = { > + .s2sz = rme_guest->ipa_bits, > + }; > + > + if (!rme_guest->log) { > + return 0; > + } > + > + ret = tpm_log_add_event(rme_guest->log, TCG_EV_NO_ACTION, > + (uint8_t *)&vmm_version, sizeof(vmm_version), > + NULL, 0, errp); > + if (ret) { > + return ret; > + } > + > + /* With KVM all CPUs have the same capability */ > + cpu = ARM_CPU(first_cpu); > + if (cpu->has_pmu) { > + params.flags |= REALM_PARAMS_FLAG_PMU; > + params.pmu_num_ctrs = FIELD_EX64(cpu->isar.reset_pmcr_el0, PMCR, N); > + } > + > + if (cpu->sve_max_vq) { > + params.flags |= REALM_PARAMS_FLAG_SVE; > + params.sve_vl = cpu->sve_max_vq - 1; > + } > + params.num_bps = FIELD_EX64(cpu->isar.id_aa64dfr0, ID_AA64DFR0, BRPS); > + params.num_wps = FIELD_EX64(cpu->isar.id_aa64dfr0, ID_AA64DFR0, WRPS); > + > + switch (rme_guest->measurement_algo) { > + case RME_GUEST_MEASUREMENT_ALGORITHM_SHA256: > + params.hash_algo = KVM_CAP_ARM_RME_MEASUREMENT_ALGO_SHA256; > + break; > + case RME_GUEST_MEASUREMENT_ALGORITHM_SHA512: > + params.hash_algo = KVM_CAP_ARM_RME_MEASUREMENT_ALGO_SHA512; > + break; > + default: > + g_assert_not_reached(); > + } > + > + return rme_log_event_tag(EVENT_LOG_TAG_REALM_CREATE, (uint8_t *)¶ms, > + sizeof(params), errp); > +} > + > +/* unmeasured images are logged with @data == NULL */ > +static int rme_log_image(RmeLogFiletype *filetype, uint8_t *data, hwaddr base, > + size_t size, Error **errp) > +{ > + int ret; > + size_t desc_size; > + GByteArray *event = g_byte_array_new(); > + struct UefiPlatformFirmwareBlob2Head head = {0}; > + struct UefiPlatformFirmwareBlob2Tail tail = {0}; > + > + if (!rme_guest->log) { > + return 0; > + } > + > + if (!filetype) { > + error_setg(errp, "cannot log image without a filetype"); > + return -1; > + } > + > + /* EV_POST_CODE2 strings are not NUL-terminated */ > + desc_size = strlen(filetype->desc); > + head.blob_description_size = desc_size; > + tail.blob_base = cpu_to_le64(base); > + tail.blob_size = cpu_to_le64(size); > + > + g_byte_array_append(event, (guint8 *)&head, sizeof(head)); > + g_byte_array_append(event, (guint8 *)filetype->desc, desc_size); > + g_byte_array_append(event, (guint8 *)&tail, sizeof(tail)); > + > + ret = tpm_log_add_event(rme_guest->log, filetype->event_type, event->data, > + event->len, data, size, errp); > + g_byte_array_free(event, true); > + return ret; > +} > + > +static int rme_log_ripas(hwaddr base, size_t size, Error **errp) > +{ > + struct { > + uint64_t base; > + uint64_t size; > + } init_ripas = { > + .base = cpu_to_le64(base), > + .size = cpu_to_le64(size), > + }; > + > + return rme_log_event_tag(EVENT_LOG_TAG_INIT_RIPAS, (uint8_t *)&init_ripas, > + sizeof(init_ripas), errp); > +} > + > +static int rme_log_rec(uint64_t flags, uint64_t pc, uint64_t gprs[8], Error **errp) > +{ $ ./scripts/checkpatch.pl ./tmp/*.patch [...] Checking ./tmp/0002-target-arm-kvm-rme-Add-measurement-log.patch... WARNING: line over 80 characters #353: FILE: target/arm/kvm-rme.c:303: +static int rme_log_rec(uint64_t flags, uint64_t pc, uint64_t gprs[8], Error **errp) May want to run this on all patches. Rest LGTM. > + struct { > + uint64_t flags; > + uint64_t pc; > + uint64_t gprs[8]; > + } rec_create = { > + .flags = cpu_to_le64(flags), > + .pc = cpu_to_le64(pc), > + .gprs[0] = cpu_to_le64(gprs[0]), > + .gprs[1] = cpu_to_le64(gprs[1]), > + .gprs[2] = cpu_to_le64(gprs[2]), > + .gprs[3] = cpu_to_le64(gprs[3]), > + .gprs[4] = cpu_to_le64(gprs[4]), > + .gprs[5] = cpu_to_le64(gprs[5]), > + .gprs[6] = cpu_to_le64(gprs[6]), > + .gprs[7] = cpu_to_le64(gprs[7]), > + }; > + > + return rme_log_event_tag(EVENT_LOG_TAG_REC_CREATE, (uint8_t *)&rec_create, > + sizeof(rec_create), errp); > +} > + > +static int rme_populate_range(hwaddr base, size_t size, bool measure, > + Error **errp); > + > +static int rme_close_measurement_log(Error **errp) > +{ > + int ret; > + hwaddr base; > + size_t size; > + RmeLogFiletype filetype = { > + .event_type = TCG_EV_POST_CODE2, > + .desc = "LOG", > + }; > + > + if (!rme_guest->log) { > + return 0; > + } > + > + base = object_property_get_uint(OBJECT(rme_guest->log), "load-addr", errp); > + if (*errp) { > + return -1; > + } > + > + size = object_property_get_uint(OBJECT(rme_guest->log), "max-size", errp); > + if (*errp) { > + return -1; > + } > + > + /* Log the log itself */ > + ret = rme_log_image(&filetype, NULL, base, size, errp); > + if (ret) { > + return ret; > + } > + > + ret = tpm_log_write_and_close(rme_guest->log, errp); > + if (ret) { > + return ret; > + } > + > + ret = rme_populate_range(base, size, /* measure */ false, errp); > + if (ret) { > + return ret; > + } > + > + g_hash_table_destroy(rme_guest->images); > + > + /* The log is now in the guest. Free this object */ > + object_unparent(OBJECT(rme_guest->log)); > + rme_guest->log = NULL; > + return 0; > +} > + > static int rme_configure_one(RmeGuest *guest, uint32_t cfg, Error **errp) > { > int ret; > @@ -120,9 +446,10 @@ static int rme_init_ram(hwaddr base, size_t size, Error **errp) > error_setg_errno(errp, -ret, > "failed to init RAM [0x%"HWADDR_PRIx", 0x%"HWADDR_PRIx")", > start, end); > + return ret; > } > > - return ret; > + return rme_log_ripas(base, size, errp); > } > > static int rme_populate_range(hwaddr base, size_t size, bool measure, > @@ -158,23 +485,42 @@ static void rme_populate_ram_region(gpointer data, gpointer err) > } > > rme_populate_range(region->base, region->size, /* measure */ true, errp); > + if (*errp) { > + return; > + } > + > + rme_log_image(region->filetype, region->data, region->base, region->size, > + errp); > } > > static int rme_init_cpus(Error **errp) > { > int ret; > CPUState *cs; > + bool logged_primary_cpu = false; > > /* > * Now that do_cpu_reset() initialized the boot PC and > * kvm_cpu_synchronize_post_reset() registered it, we can finalize the REC. > */ > CPU_FOREACH(cs) { > - ret = kvm_arm_vcpu_finalize(ARM_CPU(cs), KVM_ARM_VCPU_REC); > + ARMCPU *cpu = ARM_CPU(cs); > + > + ret = kvm_arm_vcpu_finalize(cpu, KVM_ARM_VCPU_REC); > if (ret) { > error_setg_errno(errp, -ret, "failed to finalize vCPU"); > return ret; > } > + > + if (!logged_primary_cpu) { > + ret = rme_log_rec(REC_CREATE_FLAG_RUNNABLE, cpu->env.pc, > + cpu->env.xregs, errp); > + if (ret) { > + return ret; > + } > + > + logged_primary_cpu = true; > + } > } > return 0; > } > @@ -194,6 +540,10 @@ static int rme_create_realm(Error **errp) > return -1; > } > > + if (rme_log_realm_create(errp)) { > + return -1; > + } > + > if (rme_init_ram(rme_guest->ram_base, rme_guest->ram_size, errp)) { > return -1; > } > @@ -208,6 +558,10 @@ static int rme_create_realm(Error **errp) > return -1; > } > > + if (rme_close_measurement_log(errp)) { > + return -1; > + } > + > ret = kvm_vm_enable_cap(kvm_state, KVM_CAP_ARM_RME, 0, > KVM_CAP_ARM_RME_ACTIVATE_REALM); > if (ret) { > @@ -303,6 +657,20 @@ static void rme_set_measurement_algo(Object *obj, int algo, Error **errp) > guest->measurement_algo = algo; > } > > +static bool rme_get_measurement_log(Object *obj, Error **errp) > +{ > + RmeGuest *guest = RME_GUEST(obj); > + > + return guest->use_measurement_log; > +} > + > +static void rme_set_measurement_log(Object *obj, bool value, Error **errp) > +{ > + RmeGuest *guest = RME_GUEST(obj); > + > + guest->use_measurement_log = value; > +} > + > static void rme_guest_class_init(ObjectClass *oc, void *data) > { > object_class_property_add_str(oc, "personalization-value", rme_get_rpv, > @@ -317,6 +685,12 @@ static void rme_guest_class_init(ObjectClass *oc, void *data) > rme_set_measurement_algo); > object_class_property_set_description(oc, "measurement-algorithm", > "Realm measurement algorithm ('sha256', 'sha512')"); > + > + object_class_property_add_bool(oc, "measurement-log", > + rme_get_measurement_log, > + rme_set_measurement_log); > + object_class_property_set_description(oc, "measurement-log", > + "Enable/disable Realm measurement log"); > } > > static void rme_guest_init(Object *obj) > @@ -359,6 +733,20 @@ static void rme_rom_load_notify(Notifier *notifier, void *data) > region = g_new0(RmeRamRegion, 1); > region->base = rom->addr; > region->size = rom->len; > + /* > + * TODO: double-check lifetime. Is data is still available when we measure > + * it, while writing the log. Should be fine since data is kept for the next > + * reset. > + */ > + region->data = rom->data; > + > + /* > + * rme_guest->images is destroyed after ram_regions, so we can store > + * filetype even if we don't own the struct. > + */ > + if (rme_guest->images) { > + region->filetype = g_hash_table_lookup(rme_guest->images, rom->name); > + } > > /* > * The Realm Initial Measurement (RIM) depends on the order in which we > @@ -388,6 +776,13 @@ int kvm_arm_rme_init(MachineState *ms) > return -ENODEV; > } > > + if (rme_init_measurement_log(ms)) { > + return -ENODEV; > + } > + > + rme_guest->ram_size = ms->ram_size; > + rme_guest->num_cpus = ms->smp.max_cpus; > + > error_setg(&rme_mig_blocker, "RME: migration is not implemented"); > migrate_add_blocker(&rme_mig_blocker, &error_fatal); > > @@ -430,3 +825,19 @@ int kvm_arm_rme_vm_type(MachineState *ms) > } > return 0; > } > + > +void kvm_arm_rme_set_ipa_size(uint8_t ipa_bits) > +{ > + if (rme_guest) { > + /* We request one more bit to KVM as the NS flag */ > + rme_guest->ipa_bits = ipa_bits + 1; > + } > +} > + > +Object *kvm_arm_rme_get_measurement_log(void) > +{ > + if (rme_guest) { > + return OBJECT(rme_guest->log); > + } > + return NULL; > +} > diff --git a/target/arm/Kconfig b/target/arm/Kconfig > index 7f8a2217ae..ee3a2184d0 100644 > --- a/target/arm/Kconfig > +++ b/target/arm/Kconfig > @@ -13,3 +13,4 @@ config AARCH64 > select ARM > # kvm_arch_fixup_msi_route() needs to access PCIDevice > select PCI if KVM > + select TPM_LOG if KVM
On Mon, Dec 09, 2024 at 05:08:37PM -0500, Stefan Berger wrote: > > typedef struct { > > hwaddr base; > > hwaddr size; > > + uint8_t *data; > > + RmeLogFiletype *filetype; > > } RmeRamRegion; > > +typedef struct { > > + char signature[16]; > > + char name[32]; > > + char version[40]; > > + uint64_t ram_size; > > + uint32_t num_cpus; > > + uint64_t flags; > > +} EventLogVmmVersion; > > + > > +typedef struct { > > + uint32_t id; > > + uint32_t data_size; > > + uint8_t data[]; > > +} EventLogTagged; > > + > > > > +#define EVENT_LOG_TAG_REALM_CREATE 1 > > +#define EVENT_LOG_TAG_INIT_RIPAS 2 > > +#define EVENT_LOG_TAG_REC_CREATE 3 > > + > If these are ARM-related structures and constants from a document you may > want to mention the document you got them from. Agreed. At the moment they're just numbers and structures I made up [1]. I'm not certain in which standard they should go. TCG would seem appropriate, or IETF is also used for protocols related to confidential computing attestation. Or maybe it could live in the reference verifier documentation. QEMU docs wouldn't be the best place because VMMs might been reluctant to adopt it because they don't consider it a standard (like cloud-hv and fw_cfg) When researching this I found TCG event types and payloads that only seem to be documented in their respective project: * efistub [2] with * EV_EVENT_TAG, id=0x8F3B22EC, data="Linux initrd", * EV_EVENT_TAG, id=0x8F3B22ED, data="LOADED_IMAGE::LoadOptions" * grub [3] with a few EV_IPL * systemd [4] with various EV_EVENT_TAG and EV_IPL I'm wondering if we could create a common registry somewhere for these, like IANA or somewhere informal. [1] https://github.com/veraison/cca-realm-measurements/blob/main/docs/measurement-log.md#rim-log [2] https://lore.kernel.org/all/20211119114745.1560453-1-ilias.apalodimas@linaro.org/ [3] https://www.gnu.org/software/grub/manual/grub/html_node/Measured-Boot.html [4] https://systemd.io/TPM2_PCR_MEASUREMENTS/ > > +/* Log VM type and Realm Descriptor create */ > > +static int rme_log_realm_create(Error **errp) > > +{ > > + int ret; > > + ARMCPU *cpu; > > + EventLogVmmVersion vmm_version = { > > + .signature = "VM VERSION", > > + .name = "QEMU", > > + .version = "9.1", /* TODO: dynamic */ > > $ grep -r QEMU_VERSION_M build/ > build/config-host.h:#define QEMU_VERSION_MAJOR 9 > build/config-host.h:#define QEMU_VERSION_MICRO 93 > build/config-host.h:#define QEMU_VERSION_MINOR 1 > > $ cat VERSION > 9.1.93 Ah yes that would work, thank you > > +static int rme_log_rec(uint64_t flags, uint64_t pc, uint64_t gprs[8], Error **errp) > > +{ > > $ ./scripts/checkpatch.pl ./tmp/*.patch > [...] > Checking ./tmp/0002-target-arm-kvm-rme-Add-measurement-log.patch... > WARNING: line over 80 characters > #353: FILE: target/arm/kvm-rme.c:303: > +static int rme_log_rec(uint64_t flags, uint64_t pc, uint64_t gprs[8], Error > **errp) > > May want to run this on all patches. > > Rest LGTM. Thank you! Jean
On 12/13/24 9:21 AM, Jean-Philippe Brucker wrote: > On Mon, Dec 09, 2024 at 05:08:37PM -0500, Stefan Berger wrote: >>> typedef struct { >>> hwaddr base; >>> hwaddr size; >>> + uint8_t *data; >>> + RmeLogFiletype *filetype; >>> } RmeRamRegion; >>> +typedef struct { >>> + char signature[16]; >>> + char name[32]; >>> + char version[40]; >>> + uint64_t ram_size; >>> + uint32_t num_cpus; >>> + uint64_t flags; >>> +} EventLogVmmVersion; >>> + >>> +typedef struct { >>> + uint32_t id; >>> + uint32_t data_size; >>> + uint8_t data[]; >>> +} EventLogTagged; >>> + >> >> >>> +#define EVENT_LOG_TAG_REALM_CREATE 1 >>> +#define EVENT_LOG_TAG_INIT_RIPAS 2 >>> +#define EVENT_LOG_TAG_REC_CREATE 3 >>> + >> If these are ARM-related structures and constants from a document you may >> want to mention the document you got them from. > > Agreed. At the moment they're just numbers and structures I made up [1]. I looked through old TCG specs (likely there are newer ones that mention this as well, but don't currently know) that have some definitions for EV_EVENT_TAG with a specific structure for logging the event. You seem to use the same structure: https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf 11.3.2.1: typedef struct tdTCG_PCClientTaggedEventStruct { UINT32 EventID; UINT32 EventDataSize; BYTE[EventDataSize] EventData; } TCG_PCClientTaggedEventStruct; This seems to be your EventLogTagged. EV_EVENT_TAG has tags defined for x86_64 defined in 11.3.2.3.{1 .. 13} in that spec with numbers 0 .. 0xe. You would be clashing with those numbers but otoh the are platform-specific. > I'm not certain in which standard they should go. TCG would seem > appropriate, or IETF is also used for protocols related to confidential The log is from TCG, so it would probably have to go into some sort of TCG registry. > computing attestation. Or maybe it could live in the reference verifier > documentation. QEMU docs wouldn't be the best place because VMMs might > been reluctant to adopt it because they don't consider it a standard (like > cloud-hv and fw_cfg) > > When researching this I found TCG event types and payloads that only seem > to be documented in their respective project: > * efistub [2] with > * EV_EVENT_TAG, id=0x8F3B22EC, data="Linux initrd", > * EV_EVENT_TAG, id=0x8F3B22ED, data="LOADED_IMAGE::LoadOptions" Maybe the first id is just a random number or in a vendor-specific space that I don't know about. My guess is they likely try to avoid clashes with existing numbers. > * grub [3] with a few EV_IPL > * systemd [4] with various EV_EVENT_TAG and EV_IPL > > I'm wondering if we could create a common registry somewhere for these, > like IANA or somewhere informal. > > > [1] https://github.com/veraison/cca-realm-measurements/blob/main/docs/measurement-log.md#rim-log > [2] https://lore.kernel.org/all/20211119114745.1560453-1-ilias.apalodimas@linaro.org/ > [3] https://www.gnu.org/software/grub/manual/grub/html_node/Measured-Boot.html > [4] https://systemd.io/TPM2_PCR_MEASUREMENTS/ > > >>> +/* Log VM type and Realm Descriptor create */ >>> +static int rme_log_realm_create(Error **errp) >>> +{ >>> + int ret; >>> + ARMCPU *cpu; >>> + EventLogVmmVersion vmm_version = { >>> + .signature = "VM VERSION", >>> + .name = "QEMU", >>> + .version = "9.1", /* TODO: dynamic */ >> >> $ grep -r QEMU_VERSION_M build/ >> build/config-host.h:#define QEMU_VERSION_MAJOR 9 >> build/config-host.h:#define QEMU_VERSION_MICRO 93 >> build/config-host.h:#define QEMU_VERSION_MINOR 1 >> >> $ cat VERSION >> 9.1.93 > > Ah yes that would work, thank you > >>> +static int rme_log_rec(uint64_t flags, uint64_t pc, uint64_t gprs[8], Error **errp) >>> +{ >> >> $ ./scripts/checkpatch.pl ./tmp/*.patch >> [...] >> Checking ./tmp/0002-target-arm-kvm-rme-Add-measurement-log.patch... >> WARNING: line over 80 characters >> #353: FILE: target/arm/kvm-rme.c:303: >> +static int rme_log_rec(uint64_t flags, uint64_t pc, uint64_t gprs[8], Error >> **errp) >> >> May want to run this on all patches. >> >> Rest LGTM. > > Thank you! > > Jean >
diff --git a/qapi/qom.json b/qapi/qom.json index 901ba67634..1de1b0d8af 100644 --- a/qapi/qom.json +++ b/qapi/qom.json @@ -1094,11 +1094,18 @@ # @measurement-algorithm: Realm measurement algorithm # (default: sha512) # +# @measurement-log: Enable a measurement log for the Realm. All events +# that contribute to the Realm Initial Measurement (RIM) are added +# to a log in TCG TPM2 format, which is itself loaded into Realm +# memory (unmeasured) and can then be read by a verifier to +# reconstruct the RIM. +# # Since: 9.3 ## { 'struct': 'RmeGuestProperties', 'data': { '*personalization-value': 'str', - '*measurement-algorithm': 'RmeGuestMeasurementAlgorithm' } } + '*measurement-algorithm': 'RmeGuestMeasurementAlgorithm', + '*measurement-log': 'bool'} } ## # @ObjectType: diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h index 77680f238a..44e95a034b 100644 --- a/target/arm/kvm_arm.h +++ b/target/arm/kvm_arm.h @@ -268,6 +268,24 @@ int kvm_arm_rme_vcpu_init(CPUState *cs); */ void kvm_arm_rme_init_guest_ram(hwaddr base, size_t size); +/** + * kvm_arm_rme_get_measurement_log + * + * Obtain the measurement log object if enabled, in order to get its size and + * set its base address. + * + * Returns NULL if measurement log is disabled. + */ +Object *kvm_arm_rme_get_measurement_log(void); + +/** + * kvm_arm_rme_set_ipa_size + * @ipa_bits: number of guest physical address bits + * + * Set the GPA size, not counting the bit reserved for shared address range. + */ +void kvm_arm_rme_set_ipa_size(uint8_t ipa_bits); + #else /* @@ -298,6 +316,15 @@ static inline void kvm_arm_rme_init_guest_ram(hwaddr base, size_t size) { } +static inline Object *kvm_arm_rme_get_measurement_log(void) +{ + return NULL; +} + +static inline void kvm_arm_rme_set_ipa_size(uint8_t ipa_size) +{ +} + /* * These functions should never actually be called without KVM support. */ diff --git a/target/arm/kvm-rme.c b/target/arm/kvm-rme.c index bf0bcf9a38..f92cfdb5f3 100644 --- a/target/arm/kvm-rme.c +++ b/target/arm/kvm-rme.c @@ -10,10 +10,12 @@ #include "hw/boards.h" #include "hw/core/cpu.h" #include "hw/loader.h" +#include "hw/tpm/tpm_log.h" #include "kvm_arm.h" #include "migration/blocker.h" #include "qapi/error.h" #include "qemu/error-report.h" +#include "qemu/units.h" #include "qom/object_interfaces.h" #include "sysemu/kvm.h" #include "sysemu/runstate.h" @@ -25,6 +27,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(RmeGuest, RME_GUEST) #define RME_MAX_CFG 2 +#define RME_MEASUREMENT_LOG_SIZE (64 * KiB) + struct RmeGuest { ConfidentialGuestSupport parent_obj; Notifier rom_load_notifier; @@ -32,22 +36,344 @@ struct RmeGuest { uint8_t *personalization_value; RmeGuestMeasurementAlgorithm measurement_algo; + bool use_measurement_log; + size_t num_cpus; + uint8_t ipa_bits; hwaddr ram_base; size_t ram_size; + + TpmLog *log; + GHashTable *images; }; OBJECT_DEFINE_SIMPLE_TYPE_WITH_INTERFACES(RmeGuest, rme_guest, RME_GUEST, CONFIDENTIAL_GUEST_SUPPORT, { TYPE_USER_CREATABLE }, { }) +typedef struct RmeLogFiletype { + uint32_t event_type; + /* Description copied into the log event */ + const char *desc; +} RmeLogFiletype; + typedef struct { hwaddr base; hwaddr size; + uint8_t *data; + RmeLogFiletype *filetype; } RmeRamRegion; +typedef struct { + char signature[16]; + char name[32]; + char version[40]; + uint64_t ram_size; + uint32_t num_cpus; + uint64_t flags; +} EventLogVmmVersion; + +typedef struct { + uint32_t id; + uint32_t data_size; + uint8_t data[]; +} EventLogTagged; + +#define EVENT_LOG_TAG_REALM_CREATE 1 +#define EVENT_LOG_TAG_INIT_RIPAS 2 +#define EVENT_LOG_TAG_REC_CREATE 3 + +#define REALM_PARAMS_FLAG_SVE (1 << 1) +#define REALM_PARAMS_FLAG_PMU (1 << 2) + +#define REC_CREATE_FLAG_RUNNABLE (1 << 0) + static RmeGuest *rme_guest; +static int rme_init_measurement_log(MachineState *ms) +{ + Object *log; + gpointer filename; + TpmLogDigestAlgo algo; + RmeLogFiletype *filetype; + + if (!rme_guest->use_measurement_log) { + return 0; + } + + switch (rme_guest->measurement_algo) { + case RME_GUEST_MEASUREMENT_ALGORITHM_SHA256: + algo = TPM_LOG_DIGEST_ALGO_SHA256; + break; + case RME_GUEST_MEASUREMENT_ALGORITHM_SHA512: + algo = TPM_LOG_DIGEST_ALGO_SHA512; + break; + default: + g_assert_not_reached(); + } + + log = object_new_with_props(TYPE_TPM_LOG, OBJECT(rme_guest), + "log", &error_fatal, + "digest-algo", TpmLogDigestAlgo_str(algo), + NULL); + + tpm_log_create(TPM_LOG(log), RME_MEASUREMENT_LOG_SIZE, &error_fatal); + rme_guest->log = TPM_LOG(log); + + /* + * Write down the image names we're expecting to encounter when handling the + * ROM load notifications, so we can record the type of image being loaded + * to help the verifier. + */ + rme_guest->images = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + g_free); + + filename = g_strdup(ms->kernel_filename); + if (filename) { + filetype = g_new0(RmeLogFiletype, 1); + filetype->event_type = TCG_EV_POST_CODE2; + filetype->desc = "KERNEL"; + g_hash_table_insert(rme_guest->images, filename, (gpointer)filetype); + } + + filename = g_strdup(ms->initrd_filename); + if (filename) { + filetype = g_new0(RmeLogFiletype, 1); + filetype->event_type = TCG_EV_POST_CODE2; + filetype->desc = "INITRD"; + g_hash_table_insert(rme_guest->images, filename, (gpointer)filetype); + } + + filename = g_strdup(ms->firmware); + if (filename) { + filetype = g_new0(RmeLogFiletype, 1); + filetype->event_type = TCG_EV_EFI_PLATFORM_FIRMWARE_BLOB2; + filetype->desc = "FIRMWARE"; + g_hash_table_insert(rme_guest->images, filename, filetype); + } + + filename = g_strdup(ms->dtb); + if (!filename) { + filename = g_strdup("dtb"); + } + filetype = g_new0(RmeLogFiletype, 1); + filetype->event_type = TCG_EV_POST_CODE2; + filetype->desc = "DTB"; + g_hash_table_insert(rme_guest->images, filename, filetype); + + return 0; +} + +static int rme_log_event_tag(uint32_t id, uint8_t *data, size_t size, + Error **errp) +{ + int ret; + EventLogTagged event = { + .id = id, + .data_size = size, + }; + GByteArray *bytes = g_byte_array_new(); + + if (!rme_guest->log) { + return 0; + } + + g_byte_array_append(bytes, (uint8_t *)&event, sizeof(event)); + g_byte_array_append(bytes, data, size); + ret = tpm_log_add_event(rme_guest->log, TCG_EV_EVENT_TAG, bytes->data, + bytes->len, NULL, 0, errp); + g_byte_array_free(bytes, true); + return ret; +} + +/* Log VM type and Realm Descriptor create */ +static int rme_log_realm_create(Error **errp) +{ + int ret; + ARMCPU *cpu; + EventLogVmmVersion vmm_version = { + .signature = "VM VERSION", + .name = "QEMU", + .version = "9.1", /* TODO: dynamic */ + .ram_size = cpu_to_le64(rme_guest->ram_size), + .num_cpus = cpu_to_le32(rme_guest->num_cpus), + .flags = 0, + }; + struct { + uint64_t flags; + uint8_t s2sz; + uint8_t sve_vl; + uint8_t num_bps; + uint8_t num_wps; + uint8_t pmu_num_ctrs; + uint8_t hash_algo; + } params = { + .s2sz = rme_guest->ipa_bits, + }; + + if (!rme_guest->log) { + return 0; + } + + ret = tpm_log_add_event(rme_guest->log, TCG_EV_NO_ACTION, + (uint8_t *)&vmm_version, sizeof(vmm_version), + NULL, 0, errp); + if (ret) { + return ret; + } + + /* With KVM all CPUs have the same capability */ + cpu = ARM_CPU(first_cpu); + if (cpu->has_pmu) { + params.flags |= REALM_PARAMS_FLAG_PMU; + params.pmu_num_ctrs = FIELD_EX64(cpu->isar.reset_pmcr_el0, PMCR, N); + } + + if (cpu->sve_max_vq) { + params.flags |= REALM_PARAMS_FLAG_SVE; + params.sve_vl = cpu->sve_max_vq - 1; + } + params.num_bps = FIELD_EX64(cpu->isar.id_aa64dfr0, ID_AA64DFR0, BRPS); + params.num_wps = FIELD_EX64(cpu->isar.id_aa64dfr0, ID_AA64DFR0, WRPS); + + switch (rme_guest->measurement_algo) { + case RME_GUEST_MEASUREMENT_ALGORITHM_SHA256: + params.hash_algo = KVM_CAP_ARM_RME_MEASUREMENT_ALGO_SHA256; + break; + case RME_GUEST_MEASUREMENT_ALGORITHM_SHA512: + params.hash_algo = KVM_CAP_ARM_RME_MEASUREMENT_ALGO_SHA512; + break; + default: + g_assert_not_reached(); + } + + return rme_log_event_tag(EVENT_LOG_TAG_REALM_CREATE, (uint8_t *)¶ms, + sizeof(params), errp); +} + +/* unmeasured images are logged with @data == NULL */ +static int rme_log_image(RmeLogFiletype *filetype, uint8_t *data, hwaddr base, + size_t size, Error **errp) +{ + int ret; + size_t desc_size; + GByteArray *event = g_byte_array_new(); + struct UefiPlatformFirmwareBlob2Head head = {0}; + struct UefiPlatformFirmwareBlob2Tail tail = {0}; + + if (!rme_guest->log) { + return 0; + } + + if (!filetype) { + error_setg(errp, "cannot log image without a filetype"); + return -1; + } + + /* EV_POST_CODE2 strings are not NUL-terminated */ + desc_size = strlen(filetype->desc); + head.blob_description_size = desc_size; + tail.blob_base = cpu_to_le64(base); + tail.blob_size = cpu_to_le64(size); + + g_byte_array_append(event, (guint8 *)&head, sizeof(head)); + g_byte_array_append(event, (guint8 *)filetype->desc, desc_size); + g_byte_array_append(event, (guint8 *)&tail, sizeof(tail)); + + ret = tpm_log_add_event(rme_guest->log, filetype->event_type, event->data, + event->len, data, size, errp); + g_byte_array_free(event, true); + return ret; +} + +static int rme_log_ripas(hwaddr base, size_t size, Error **errp) +{ + struct { + uint64_t base; + uint64_t size; + } init_ripas = { + .base = cpu_to_le64(base), + .size = cpu_to_le64(size), + }; + + return rme_log_event_tag(EVENT_LOG_TAG_INIT_RIPAS, (uint8_t *)&init_ripas, + sizeof(init_ripas), errp); +} + +static int rme_log_rec(uint64_t flags, uint64_t pc, uint64_t gprs[8], Error **errp) +{ + struct { + uint64_t flags; + uint64_t pc; + uint64_t gprs[8]; + } rec_create = { + .flags = cpu_to_le64(flags), + .pc = cpu_to_le64(pc), + .gprs[0] = cpu_to_le64(gprs[0]), + .gprs[1] = cpu_to_le64(gprs[1]), + .gprs[2] = cpu_to_le64(gprs[2]), + .gprs[3] = cpu_to_le64(gprs[3]), + .gprs[4] = cpu_to_le64(gprs[4]), + .gprs[5] = cpu_to_le64(gprs[5]), + .gprs[6] = cpu_to_le64(gprs[6]), + .gprs[7] = cpu_to_le64(gprs[7]), + }; + + return rme_log_event_tag(EVENT_LOG_TAG_REC_CREATE, (uint8_t *)&rec_create, + sizeof(rec_create), errp); +} + +static int rme_populate_range(hwaddr base, size_t size, bool measure, + Error **errp); + +static int rme_close_measurement_log(Error **errp) +{ + int ret; + hwaddr base; + size_t size; + RmeLogFiletype filetype = { + .event_type = TCG_EV_POST_CODE2, + .desc = "LOG", + }; + + if (!rme_guest->log) { + return 0; + } + + base = object_property_get_uint(OBJECT(rme_guest->log), "load-addr", errp); + if (*errp) { + return -1; + } + + size = object_property_get_uint(OBJECT(rme_guest->log), "max-size", errp); + if (*errp) { + return -1; + } + + /* Log the log itself */ + ret = rme_log_image(&filetype, NULL, base, size, errp); + if (ret) { + return ret; + } + + ret = tpm_log_write_and_close(rme_guest->log, errp); + if (ret) { + return ret; + } + + ret = rme_populate_range(base, size, /* measure */ false, errp); + if (ret) { + return ret; + } + + g_hash_table_destroy(rme_guest->images); + + /* The log is now in the guest. Free this object */ + object_unparent(OBJECT(rme_guest->log)); + rme_guest->log = NULL; + return 0; +} + static int rme_configure_one(RmeGuest *guest, uint32_t cfg, Error **errp) { int ret; @@ -120,9 +446,10 @@ static int rme_init_ram(hwaddr base, size_t size, Error **errp) error_setg_errno(errp, -ret, "failed to init RAM [0x%"HWADDR_PRIx", 0x%"HWADDR_PRIx")", start, end); + return ret; } - return ret; + return rme_log_ripas(base, size, errp); } static int rme_populate_range(hwaddr base, size_t size, bool measure, @@ -158,23 +485,42 @@ static void rme_populate_ram_region(gpointer data, gpointer err) } rme_populate_range(region->base, region->size, /* measure */ true, errp); + if (*errp) { + return; + } + + rme_log_image(region->filetype, region->data, region->base, region->size, + errp); } static int rme_init_cpus(Error **errp) { int ret; CPUState *cs; + bool logged_primary_cpu = false; /* * Now that do_cpu_reset() initialized the boot PC and * kvm_cpu_synchronize_post_reset() registered it, we can finalize the REC. */ CPU_FOREACH(cs) { - ret = kvm_arm_vcpu_finalize(ARM_CPU(cs), KVM_ARM_VCPU_REC); + ARMCPU *cpu = ARM_CPU(cs); + + ret = kvm_arm_vcpu_finalize(cpu, KVM_ARM_VCPU_REC); if (ret) { error_setg_errno(errp, -ret, "failed to finalize vCPU"); return ret; } + + if (!logged_primary_cpu) { + ret = rme_log_rec(REC_CREATE_FLAG_RUNNABLE, cpu->env.pc, + cpu->env.xregs, errp); + if (ret) { + return ret; + } + + logged_primary_cpu = true; + } } return 0; } @@ -194,6 +540,10 @@ static int rme_create_realm(Error **errp) return -1; } + if (rme_log_realm_create(errp)) { + return -1; + } + if (rme_init_ram(rme_guest->ram_base, rme_guest->ram_size, errp)) { return -1; } @@ -208,6 +558,10 @@ static int rme_create_realm(Error **errp) return -1; } + if (rme_close_measurement_log(errp)) { + return -1; + } + ret = kvm_vm_enable_cap(kvm_state, KVM_CAP_ARM_RME, 0, KVM_CAP_ARM_RME_ACTIVATE_REALM); if (ret) { @@ -303,6 +657,20 @@ static void rme_set_measurement_algo(Object *obj, int algo, Error **errp) guest->measurement_algo = algo; } +static bool rme_get_measurement_log(Object *obj, Error **errp) +{ + RmeGuest *guest = RME_GUEST(obj); + + return guest->use_measurement_log; +} + +static void rme_set_measurement_log(Object *obj, bool value, Error **errp) +{ + RmeGuest *guest = RME_GUEST(obj); + + guest->use_measurement_log = value; +} + static void rme_guest_class_init(ObjectClass *oc, void *data) { object_class_property_add_str(oc, "personalization-value", rme_get_rpv, @@ -317,6 +685,12 @@ static void rme_guest_class_init(ObjectClass *oc, void *data) rme_set_measurement_algo); object_class_property_set_description(oc, "measurement-algorithm", "Realm measurement algorithm ('sha256', 'sha512')"); + + object_class_property_add_bool(oc, "measurement-log", + rme_get_measurement_log, + rme_set_measurement_log); + object_class_property_set_description(oc, "measurement-log", + "Enable/disable Realm measurement log"); } static void rme_guest_init(Object *obj) @@ -359,6 +733,20 @@ static void rme_rom_load_notify(Notifier *notifier, void *data) region = g_new0(RmeRamRegion, 1); region->base = rom->addr; region->size = rom->len; + /* + * TODO: double-check lifetime. Is data is still available when we measure + * it, while writing the log. Should be fine since data is kept for the next + * reset. + */ + region->data = rom->data; + + /* + * rme_guest->images is destroyed after ram_regions, so we can store + * filetype even if we don't own the struct. + */ + if (rme_guest->images) { + region->filetype = g_hash_table_lookup(rme_guest->images, rom->name); + } /* * The Realm Initial Measurement (RIM) depends on the order in which we @@ -388,6 +776,13 @@ int kvm_arm_rme_init(MachineState *ms) return -ENODEV; } + if (rme_init_measurement_log(ms)) { + return -ENODEV; + } + + rme_guest->ram_size = ms->ram_size; + rme_guest->num_cpus = ms->smp.max_cpus; + error_setg(&rme_mig_blocker, "RME: migration is not implemented"); migrate_add_blocker(&rme_mig_blocker, &error_fatal); @@ -430,3 +825,19 @@ int kvm_arm_rme_vm_type(MachineState *ms) } return 0; } + +void kvm_arm_rme_set_ipa_size(uint8_t ipa_bits) +{ + if (rme_guest) { + /* We request one more bit to KVM as the NS flag */ + rme_guest->ipa_bits = ipa_bits + 1; + } +} + +Object *kvm_arm_rme_get_measurement_log(void) +{ + if (rme_guest) { + return OBJECT(rme_guest->log); + } + return NULL; +} diff --git a/target/arm/Kconfig b/target/arm/Kconfig index 7f8a2217ae..ee3a2184d0 100644 --- a/target/arm/Kconfig +++ b/target/arm/Kconfig @@ -13,3 +13,4 @@ config AARCH64 select ARM # kvm_arch_fixup_msi_route() needs to access PCIDevice select PCI if KVM + select TPM_LOG if KVM
Create an event log, in the format defined by Trusted Computing Group for TPM2. It contains information about the VMM, the Realm parameters, any data loaded into guest memory before boot and the initial vCPU state. The guest can access this log from RAM and send it to a verifier, to help the verifier independently compute the Realm Initial Measurement, and check that the data we load into guest RAM is known-good images. Without this log, the verifier has to guess where everything is loaded and in what order. Cc: Stefan Berger <stefanb@linux.vnet.ibm.com> Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org> --- v2->v3: New --- qapi/qom.json | 9 +- target/arm/kvm_arm.h | 27 +++ target/arm/kvm-rme.c | 415 ++++++++++++++++++++++++++++++++++++++++++- target/arm/Kconfig | 1 + 4 files changed, 449 insertions(+), 3 deletions(-)