The first part of this work put the general structure for the code to parse the core event structures in place. With that merged the next step was to handle special cases and refactor the core logic to accommodate. The special cases make this work far more interesting since in it gives context specific meaning to the generic fields in the core event structures. For those interested this work was merged in https://github.com/tpm2-software/tpm2-tools/pull/1950. The rest of this post will walk through the interesting parts of the PR.
Refactoring the parsing loop
The first commit in this series restructures the initial `foreach_*` interface functions to simplify the output module. This became necessary as I was writing the code to parse the event data field. The previous approach relied on the callback functions from the caller to do farm more work than was required and adding the logic required to parse the event data in this model only made things worse. The details of this decision are in the commit message (https://github.com/tpm2-software/tpm2-tools/pull/1950/commits/f0179c1de7f56f2514bcea4c6832a28c406f88af) but for our purposes here I’ll just say it was necessary to keep from requiring too much coordination between the parsing module and the output module.
The TCG PC Client Platform Firmware Profile Specification (section 126.96.36.199 in the current version) requires that the first even in the log follow a format that includes data about the version of the format used in the log. This data is encapsulated in the generic event structure seen throughout the log. This structure is parsed and displayed by the tool though the tool doesn’t use it to check compatible versions. This may be necessary / useful in the future but for the initial implementation being permissive seemed like the right thing to do. If an incompatible log is passed it it will be treated the same as a malformed one and an error will be returned.
The test harness now has several examples of SpecID events since each eventlog instance must have one as the first event. A good example is here: https://github.com/tpm2-software/tpm2-tools/blob/92307af75df9fbe3418efb6c6e0509d291a52d7a/test/integration/fixtures/specid-vendordata.bin. Running this through the tool at the time of writing produces output like so:
- Event: pcrIndex: 0 eventType: EV_NO_ACTION digest: 0000000000000000000000000000000000000000 eventDataSize: 45 SpecID: - Signature: Spec ID Event03 platformClass: 0 specVersionMinor: 0 specVersionMajor: 2 specErrata: 0 uintnSize: 2 numberOfAlgorithms: 2 Algorithms: - Algorithm: algorithmId: sha1 digestSize: 20 - Algorithm: algorithmId: sha256 digestSize: 32 vendorInfoSize: 0
Finally this PR adds code to parse UEFI specific events. These events are generated as part of the platform firmware configuring the system and launching the OS. The reason for measuring these various components is interesting but out of scope here (see https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/). What we will cover is a bit about the implementation along with some example output.
Variables in UEFI are not like variables in a programming language. They’re bits of storage with scope and some backing that may even be persistent across reboots. This includes the various databases that back UEFI secure boot, as well as the boot configuration variables. The output of the variable holding the first boot variable on a recent Fedora VM looks like so:
- Event[X]: PCRIndex: 1 EventType: EV_EFI_VARIABLE_BOOT DigestCount: 2 Digests: - AlgorithmId: sha1 Digest: 22a4f6ee9af6dba01d3528deb64b74b582fc182b - AlgorithmId: sha256 Digest: 3197be1e300fa1600d1884c3a4bd4a90a15405bfb546cf2e6cf6095f8c362a93 EventSize: 110 Event: - VariableName: 61dfe48b-ca93-d211-aa0d-00e098032b8c UnicodeNameLength: 8 VariableDataLength: 62 UnicodeName: Boot0000 VariableData: 090100002c0055006900410070007000000004071400c9bdb87cebf8344faaea3ee4af6516a10406140021aa2c4614760345836e8ab6f46623317fff0400
Entries for the other boot variables as well as the secure boot KEK, db and dbx should be present as well.
Firmware blobs are opaque and so the event type only includes the base address where the blob is located in memory along with it’s size / length. The blob is not itself included in the event.
- Event[x]: PCRIndex: 0 EventType: EV_EFI_PLATFORM_FIRMWARE_BLOB DigestCount: 2 Digests: - AlgorithmId: sha1 Digest: 47247be85d64522117a9589f8df169c4dc6d1750 - AlgorithmId: sha256 Digest: 17d8b73c395cde3cf206083e2ebe774348b811850d957cb65c3712096814d8b1 EventSize: 16 Event: - BlobBase: 0x820000 BlobLength: 0xe0000
The event for an `EV_EFI_ACTION` type contains a string describing the action. From the event log generated by OVMF it appears that these actions correlate to transitions between firmware phases like when ExitBootServices is invoked:
- Event[X]: PCRIndex: 5 EventType: EV_EFI_ACTION DigestCount: 2 Digests: - AlgorithmId: sha1 Digest: 443a6b7b82b7af564f2e393cd9d5a388b7fa4a98 - AlgorithmId: sha256 Digest: d8043d6b7b85ad358eb3b6ae6a873ab7ef23a26352c5dc4faa5aeedacf5eb41b EventSize: 29 Event: Exit Boot Services Invocation
Finally when UEFI loads a driver from an ad-in card or a non-bootable application the `EV_EFI_BOOT_SERVICES_DRIVER` EventType is used. The structure includes information like the modules location in memory and its length. Additionally it includes the UEFI device path. Currently we dump this as a hex string though someone willing to dig around in the right UEFI spec could decode the value.
- Event[X]: PCRIndex: 2 EventType: EV_EFI_BOOT_SERVICES_DRIVER DigestCount: 2 Digests: - AlgorithmId: sha1 Digest: 855685b4dbd4b67d50e0594571055054cfe2b1e9 - AlgorithmId: sha256 Digest: dd8576b4ff346c19c56c3e4f97ce55c5afa646f9c669be0a7cdd05057a0ecdf3 EventSize: 84 Event: - ImageLocationInMemory: 0x7dcf6018 ImageLengthInMemory: 171464 ImageLinkTimeAddress: 0x0 LengthOfDevicePath: 52 DevicePath: 02010c00d041030a0000000001010600000201010600000004081800000000000026010000000000ffc30300000000007fff0400
With the above merged into the tpm2-tools project the majority of the events generated by the current OVMF release can be parsed and transformed into a textual (yaml) form. From the testing I’ve done I’d say the implementation is likely ~80% complete. The remaining work requires knowledge of UEFI structures like the
UEFI_GPT_DATA which will take some doing. Eventually I’m hoping this tool will be a good starting point for supporting the new canonical event log format.