Parsing the TPM2 Event Log from Userspace Part 2

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.

SpecID Event

The TCG PC Client Platform Firmware Profile Specification (section 9.4.5.1 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[0]:
  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[0]:
          algorithmId: sha1
          digestSize: 20
        - Algorithm[1]:
          algorithmId: sha256
          digestSize: 32
      vendorInfoSize: 0

UEFI Events

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.

UEFI Variables

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.

UEFI Blobs

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

UEFI Actions

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

UEFI Services

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

Conclusion

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_DEVICE_PATH

and 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.

Parsing the TPM2 Event Log from Userspace

I’ve written a few posts in the past about some code I’ve written to parse / display the TPM2 UEFI / pre-boot event log. Back when I wrote this stuff up initially, getting access to the event log on Linux systems was a PITA. There hadn’t been any work done to expose the event log to the OS and so I wrote my code to run under the Grub shell and eventually the UEFI shell:
https://twobit.org/2016/05/31/tpm2-uefi-measurements-and-event-log/
https://twobit.org/2019/05/21/uefi-tpm2-examples/

In the intervening years (yes it really has been that long) some important things have changed: The UEFI firmware is now exposing the event log through an ACPI table (this was previously not required by the spec). Also the the Linux kernel is now consuming this table and exposing the event log through the securityfs typically at /sys/kernel/security/tpm0/binary_bios_measurements. This is enough for us to parse and display the event log in your favorite shell from userspace!

ASIDE: Configuring a Linux system to expose the event log through securityfs requires enabling secure boot. The easiest way to do this quickly is to use the latest Fedora release (31 when this was written). I did this by running Fedora 31 as a VM, but this requires using an OVMF image with secure boot enabled. How to do this is beyond the scope of this post but it may show up in a future post.

This code was ported from my past efforts in the UEFI shell and significantly cleaned up to separate the display logic from the parsing logic. It was merged into the tpm2-tools project in pull request #1898, commit 164737a7cd1bdfe5659cab0da58d2d93d925c27d as the tpm2_eventlog tool. The parser is a simple routine that extracts data from the binary log while ensuring that the various size fields in the log don’t cause it to walk off the end of the data buffer.

The event log is a series of concatenated event structures (format is documented here: https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/) and so the most natural interface to the parsers is a simple foreach function that invokes a user supplied callback for each event structure in the log. The parsers validates the event structure before invoking the callback both for safety and to prevent the need for duplicate validation code in each callback.

Within each event structure is a series of variable length digests, one for each digest supported and enabled for the TPM2 on the platform. A second foreach function is supplied for iterating over these digests in a method similar the foreach that iterates over the event structures. This is useful since the digest structures are variable length and byte aligned within the larger event structure so walking through the binary log requires some pointer arithmetic.

As part of porting this over to the tpm2-tools project the output format was updated to use yaml since it’s the preferred format for the project when displaying non-standard text formats. Interestingly enough the ‘field: value’ ascii format I had been using only required a few ‘-‘s and some indentation changes to pass yamllint.

All of this would be a bit over engineered if all we had to do was to convert the log from binary to text. The binary log produced by the firmware is in host byte order and so it’s not suitable for exchange between hosts (among other reasons). Other log formats are being standardized for this purpose, most notable is the TCG “canonical event log format” presented at the linux security summit 2018 as applied to IMA: https://events19.linuxfoundation.org/wp-content/uploads/2017/11/A-Canonical-Event-Log-Structure-for-IMA-David-Safford-Monty-Wiseman-GE.pdf. My expectation is that, when the TCG publishes this new event log format, developers handling these event logs will need to be able to dump them as text for debugging purposes but also transform between these formats as well.

For now, the tpm2_eventlog only accepts the binary firmware log as input and displays yaml as output. Now that this tool has been merged I’m working on decoding the event blobs defined in the spec and will hopefully have that merged before the next release.

TPM2 UEFI Measurements and Event Log

UPDATE: I’ve continued this work as “pure” UEFI shell executables and so some of the links to this work with Grub2 have gone dead. The updated version of this work can be found here: https://twobit.org/2019/05/21/uefi-tpm2-examples/. Work to integrate these tools into Grub2 is still on-going.

Has it really been over 6 months since I last wrote something on the blog? Crap. The standard excuses apply: busy busy busy. I got sucked into the OSS TPM2 software stack project @ Intel in a huge way.

I screwed up something fundamental along the way though: I moved on to this new project before finishing off an older project. So I’m here to set things right. The OpenXT summit is in a week so I’m cleaning out my backlog and it’s time to write up the stuff I was working on back before I fell down the TPM2 TSS hole. Come to think of it I’ve got some interesting stuff to write about the TPM2 TSS work but first things first: TPM2 in EFI and Grub2.

This is likely the first post in what will become a series on the subject. I’ll try to keep this post to an overview of the TPM2 EFI protocol, the basics of what I’ve been adding to grub and I’ll do a slightly deeper dive in to the pre-boot TPM2 event log that’s maintained by the TPM2 EFI driver / firmware. Future posts will pick up the remaining bits and get into the details of how this was integrated into meta-measured.

TPM2 UEFI recap

I left off my last post describing some oddities I ran into in the TCG TPM 2.0 EFI Protocol specification. With that issue out of the way I spent some time playing around with the functions exposed by the protocol (in the UEFI sense) and what they would allow us to do in the pre-boot environment. Naturally there’s a purpose here: measuring stuff.

Before we get too far into the post here’s a pointer to the code I’ve been developing: . It’s been a few months since I seriously slowed down work on this to focus on the TSS for Linux but I intend to revive it so that I can finish it off in the near future. Intel has already demonstrated this code @ the TCG event @ RSA 2016 so it’s functional but still just ‘demo quality’.

The Protocol

The stuff we want to measure here is the code and the data that was run as part of booting our system. In this case we want to extend the measurement chain from the firmware up through Grub2 to the OS. Despite past efforts on a Grub2 fork known as “trusted grub” the majority of Linux systems have a gap between measurements taken by the firmware and measurements that are taken by kernel or user space code.

In our efforts to fill this gap, let’s start by taking a quick look at the functions that are exposed by the TPM2 UEFI protocol. In UEFI the concept of a ‘protocol’ isn’t what I typically think of when I hear the word. When I hear ‘protocol’ the first thing that comes to mind is a network protocol like TCP or IP. In the context of UEFI it’s a bit different: it defines the interface between UEFI applications and some set of services offered by the UEFI runtime.

The interaction model is very basic: You provide the UEFI runtime with a UUID identifying the protocol you want to use and it will return to you a structure. This structure is protocol specific and it’s effectively a table of function pointers. For the TPM2 there’s 7 commands and thus 7 entries in this structure, one for each function.

The TPM2 EFI Protocol Specification documents this structure and the functions it exposes in section 6. My implementation pulls the structure directly from the spec as follows:

typedef struct tdEFI_TCG2_PROTOCOL {
  EFI_TCG2_GET_CAPABILITY                      GetCapability;
  EFI_TCG2_GET_EVENT_LOG                       GetEventLog;
  EFI_TCG2_HASH_LOG_EXTEND_EVENT               HashLogExtendEvent;
  EFI_TCG2_SUBMIT_COMMAND                      SubmitCommand;
  EFI_TCG2_GET_ACTIVE_PCR_BANKS                GetActivePcrBanks;
  EFI_TCG2_SET_ACTIVE_PCR_BANKS                SetActivePcrBanks;
  EFI_TCG2_GET_RESULT_OF_SET_ACTIVE_PCR_BANKS  GetResultOfSetActivePcrBanks;
} GRUB_PACKED EFI_TCG2_PROTOCOL;

Each of these types is a function with a prototype / parameters etc just like any other function in C. The spec has all of the gory details, including the updated data about the unpacked structure returned by GetCapability. But given our stated goals above: measuring stuff, we really need just one command: HashLogExtendEvent.

This is probably a good time to reflect on how great UEFI is. In the PC BIOS world we would be writing assembly code to interact with the TPM using memory mapped IO or something. In UEFI we call a function to get a table of pointers to other functions and we then call one of these functions. All of this can be done in C. Pretty great IMHO. And if you’re working in the Grub2 environment Grub already has wrappers for invoking the UEFI command to get access to the protocol structure and to invoke functions from it.

On my Minnowboard Max (MBM) this is pretty easy. Add an FTDI serial cable (the one on sparkfun is way over priced but really nice) and you don’t even need a monitor. Anyways, back to it.

HashLogExtendEvent

The TPM is a pretty complicated little thing but it’s fundamental function: extending data into PCRs, is pretty simple. All you need is a buffer holding data, the size of the buffer, and a data structure describing the extend event. This last bit of data is used by the UEFI firmware to update the log of extend events (aka: “stuff we’ve measured”).

In my grub code I’ve broken this down into two functions. The first is a thin wrapper over the HashLogExtendEvent function that takes the data buffer,size and EFI_TCG2_EVENT structure as a parameter. The second is more of a convenience function that builds up the EFI_TCG2_EVENT structure for the caller using “standard” values. The two prototypes are as follows:

grub_err_t
grub_tpm2_extend (EFI_TCG2_PROTOCOL *tpm2_prot,
                  grub_efi_uint64_t  flags,
                  char              *data,
                  grub_efi_uint64_t  data_size,
                  EFI_TCG2_EVENT    *event)
grub_err_t
grub_tpm2_extend_buf (grub_uint8_t      *buf,
                      grub_efi_uint64_t  buf_len,
                      const char        *desc,
                      grub_uint32_t      pcr)

The EFI_TCG2_EVENT structure is pretty simple but it’s annoying to calculate the size values for each call so the convenience function is a good way to keep code sizes manageable. Take a look at the code here if you’re interested in the details.

When Grub does something like load a module of code, or a command from a config file or a kernel / initrd image we measure it into a TPM PCR using the grub_tpm2_extend_buf function providing it the data to hash, the length of the memory buffer holding said data, a brief description of the event (like “linux kernel” or “loadable module”) and the PCR we want to extend with the measurement.

Probably worth noting is that the current implementation uses the string obtained from the grub.cfg file in the Event field. For now this is a debugging mechanism. For a deployed implementation taking input directly from the user (the grub.cfg file should be considered as such) is pretty risky. Instead it’s probably better to use a generic string that describes what the input data is, but doesn’t contain the data directly.

TPM2 Preboot Event Log

It’s very difficult to extract meaning from the contents of a TPMs PCR. Up till now we’ve covered the bits necessary to extend data into a PCR from within Grub. We can extend value after value into it and at some time in the future read it back. But when someone looks at the value they’ll likely want to recreate it to verify every component that went into generating the final value. The name of the UEFI protocol function that extends a value into the PCR is particularly descriptive because, like it says, it also updates the preboot measurement log. This log is where we go to get the data we need to understand our PCR values.

Being able to parse this log and view the contents is also an integral part of verifying or debugging the work I’ve been doing. In this vein, after I managed to get a call to extend a value into a PCR doing more than returning an error code (yeah it happens a lot to me for some reason) I immediately wrote a bit of code to parse and dump / “prettyprint” the audit log.

Grub has a built in shell and an interface to load code modules and commands. This seemed like the “right” mechanism to load commands that I could execute on demand. If you boot grub without a config file you’ll land immediately at the shell and so I found myself debugging by running grub and then executing commands to extend arbitrary data into the log and then parsing the log and dumping it to the console output. If you’ve got a serial console hooked up you can capture this output using minicom or whatever. The commands that parse the log can be found in a module I’m calling tpm2cmd until I come up with a better name. This module has a few commands in it but to dump the event log the tpm2dumplog command is what you’ll need.

The code that walks through the event log data structure is pretty boring. It’s tightly packed so there’s a bit of work to be done in calculating offsets but it’s not rocket surgery. The interesting part is in interpreting the log and figuring out what to do with the data.

Let’s capture an audit log using these new commands. First we need to be able to boot grub and get to the grub shell. I’ve been integrating this work into the meta-measured OpenEmbedded meta-layer so if you want to see this at work you can build the core-image-tpm image or download one from my meta-measured autobuilder. If you want to build Grub by hand and install it on a thumb drive the results should be the same. Just grab the code from my github. I won’t cover the details for this here though.

Assuming you’ve got the latest core-image-tpm image from meta-measured built, just dd the ISO on to a thumb drive and boot it (on a UEFI system with a TPM2 of course). If you’re grabbing the ISO from my autobuilder the target MACHINE is an MBM running the 32bit firmware so YMMV on any other platform. When you finally get to the grub menu hit ‘c’ to get a shell. From here the tpm2cmd module provides a few functions (tpm2dumpcaps tpm2dumplog and tpm2extend) that you can execute to play around. But before we get too far into that it’s important to note that the firmware can support either the new TPM2 event log format or the old TPM 1.2 format. The capabilities structure will tell you which your firmware supports. On the Minnowboard Max I only get the 1.2 foramt:

grub> tpm2dumpcaps
TPM2 Capabilities:
  Size: 0x1c
  StructureVersion:
    Major: 0x01
    Minor: 0x00
  ProtocolVersion:
    Major: 0x01
    Minor: 0x00
  HashAlgorithmBitmap: 0x00000003
    EFI_TCG2_BOOT_HASH_ALG_SHA1: true
    EFI_TCG2_BOOT_HASH_ALG_SHA256: true
    EFI_TCG2_BOOT_HASH_ALG_SHA384: false
    EFI_TCG2_BOOT_HASH_ALG_SHA512: false
    EFI_TCG2_BOOT_HASH_ALG_SM3_256: false
  SupportedEventLogs: 0x00000001
    EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2: true
    EFI_TCG2_EVENT_LOG_FORMAT_TCG_2: false
  TPMPresentFlag: 0x01 : true
  MaxCommandSize: 0x0f80
  MaxResponseSize: 0x0f80
  ManufacturerID: 0x494e5443
  NumberOfPcrBanks: 0x00000000
  ActivePcrBanks: 0x00000000

The tpm2dumplog function is smart enough to check this structure before walking the log. It will prefer the TPM2 event log format if both are supported. There’s even a --format option so you can select which you want if both are supported but I haven’t had a chance to test this since I’ve not yet found a system with firmware supporting the 2.0 format. Booting straight into the grub shell and executing the tpm2dumplog command produces the log file below. Let’s pick a few example entries and see if we can figure out what they mean.

grub> tpm2
Possible commands are:

 tpm2dumpcaps tpm2dumplog tpm2extend tpm2sendlog
grub> tpm2dump
Possible commands are:

 tpm2dumpcaps tpm2dumplog
grub> tpm2dumplog 
TPM2 EventLog
  start: 0x79373000
  end: 0x79373c98
  truncated: false
prettyprint_tpm12_event at: 0x79373000
  PCRIndex: 0
  EventType: EV_S_CRTM_VERSION (0x00000008)
  digest: 1489f923c4dca729178b3e3233458550d8dddf29
  EventSize: 2
  Event: 
prettyprint_tpm12_event at: 0x79373022
  PCRIndex: 0
  EventType: EV_EFI_PLATFORM_FIRMWARE_BLOB (0x80000008)
  digest: 76bd373351e3531ae3e2257e0b07951a2f61ae42
  EventSize: 16
  Event: 
prettyprint_tpm12_event at: 0x79373052
  PCRIndex: 0
  EventType: EV_EFI_PLATFORM_FIRMWARE_BLOB (0x80000008)
  digest: f46823e31fcb3059dbd9a69e5cc679a4465ea318
  EventSize: 16
  Event: 
prettyprint_tpm12_event at: 0x79373082
  PCRIndex: 0
  EventType: EV_EFI_PLATFORM_FIRMWARE_BLOB (0x80000008)
  digest: 87ce3bc3cd17fe797cc04d507c2f8f3b6c418552
  EventSize: 16
  Event: 
prettyprint_tpm12_event at: 0x793730b2
  PCRIndex: 0
  EventType: EV_EFI_PLATFORM_FIRMWARE_BLOB (0x80000008)
  digest: 8725eb98a49d6227459c6c90699c5187c5f11e16
  EventSize: 16
  Event: 
prettyprint_tpm12_event at: 0x793730e2
  PCRIndex: 7
  EventType: EV_EFI_VARIABLE_DRIVER_CONFIG (0x80000001)
  digest: 57cd4dc19442475aa82743484f3b1caa88e142b8
  EventSize: 53
  Event: a????
prettyprint_tpm12_event at: 0x79373137
  PCRIndex: 7
  EventType: EV_EFI_VARIABLE_DRIVER_CONFIG (0x80000001)
  digest: 9b1387306ebb7ff8e795e7be77563666bbf4516e
  EventSize: 36
  Event: a????
prettyprint_tpm12_event at: 0x7937317b
  PCRIndex: 7
  EventType: EV_EFI_VARIABLE_DRIVER_CONFIG (0x80000001)
  digest: 9afa86c507419b8570c62167cb9486d9fc809758
  EventSize: 38
  Event: a????
prettyprint_tpm12_event at: 0x793731c1
  PCRIndex: 7
  EventType: EV_EFI_VARIABLE_DRIVER_CONFIG (0x80000001)
  digest: 5bf8faa078d40ffbd03317c93398b01229a0e1e0
  EventSize: 36
  Event: ?:=?E????geo
prettyprint_tpm12_event at: 0x79373205
  PCRIndex: 7
  EventType: EV_EFI_VARIABLE_DRIVER_CONFIG (0x80000001)
  digest: 734424c9fe8fc71716c42096f4b74c88733b175e
  EventSize: 38
  Event: ?:=?E????geo
prettyprint_tpm12_event at: 0x7937324b
  PCRIndex: 7
  EventType: EV_SEPARATOR (0x00000004)
  digest: 9069ca78e7450a285173431b3e52c5c25299e473
  EventSize: 4
  Event: 
prettyprint_tpm12_event at: 0x7937326f
  PCRIndex: 1
  EventType: EV_EFI_HANDOFF_TABLES (0x80000009)
  digest: ed620fde0f449cec32fc0eaa040d04e1f1888b25
  EventSize: 24
  Event: 
prettyprint_tpm12_event at: 0x793732a7
  PCRIndex: 5
  EventType: EV_EFI_VARIABLE_BOOT (0x80000002)
  digest: aae4f77a57d91bf7beeee06e053a73eec78cc9ec
  EventSize: 62
  Event: a????
prettyprint_tpm12_event at: 0x79373305
  PCRIndex: 5
  EventType: EV_EFI_VARIABLE_BOOT (0x80000002)
  digest: d2ab4e0ed4185d5c846fb56980907a65aa8d3103
  EventSize: 168
  Event: a????
prettyprint_tpm12_event at: 0x793733cd
  PCRIndex: 5
  EventType: EV_EFI_VARIABLE_BOOT (0x80000002)
  digest: 7c7e40269acd5fb401b6f49034b46699bc5c5777
  EventSize: 136
  Event: a????
prettyprint_tpm12_event at: 0x79373475
  PCRIndex: 5
  EventType: EV_EFI_VARIABLE_BOOT (0x80000002)
  digest: abb0830c9bef09a2c108ceba8f0ff8438991b20a
  EventSize: 206
  Event: a????
prettyprint_tpm12_event at: 0x79373563
  PCRIndex: 5
  EventType: EV_EFI_VARIABLE_BOOT (0x80000002)
  digest: 79d23fb4ea34f740725783540657c37775fb83b0
  EventSize: 239
  Event: a????
prettyprint_tpm12_event at: 0x79373672
  PCRIndex: 5
  EventType: EV_EFI_VARIABLE_BOOT (0x80000002)
  digest: b8d59f3cde8b1fc5b89ff0b61f7096903734accb
  EventSize: 117
  Event: a????
prettyprint_tpm12_event at: 0x79373707
  PCRIndex: 5
  EventType: EV_EFI_VARIABLE_BOOT (0x80000002)
  digest: 22d22e14e623d1714cd605fef81e114ad8882d78
  EventSize: 121
  Event: a????
prettyprint_tpm12_event at: 0x793737a0
  PCRIndex: 5
  EventType: EV_EFI_ACTION (0x80000007)
  digest: cd0fdb4531a6ec41be2753ba042637d6e5f7f256
  EventSize: 40
  Event: Calling EFI Application from Boot Option
prettyprint_tpm12_event at: 0x793737e8
  PCRIndex: 0
  EventType: EV_SEPARATOR (0x00000004)
  digest: 9069ca78e7450a285173431b3e52c5c25299e473
  EventSize: 4
  Event: 
prettyprint_tpm12_event at: 0x7937380c
  PCRIndex: 1
  EventType: EV_SEPARATOR (0x00000004)
  digest: 9069ca78e7450a285173431b3e52c5c25299e473
  EventSize: 4
  Event: 
prettyprint_tpm12_event at: 0x79373830
  PCRIndex: 2
  EventType: EV_SEPARATOR (0x00000004)
  digest: 9069ca78e7450a285173431b3e52c5c25299e473
  EventSize: 4
  Event: 
prettyprint_tpm12_event at: 0x79373854
  PCRIndex: 3
  EventType: EV_SEPARATOR (0x00000004)
  digest: 9069ca78e7450a285173431b3e52c5c25299e473
  EventSize: 4
  Event: 
prettyprint_tpm12_event at: 0x79373878
  PCRIndex: 4
  EventType: EV_SEPARATOR (0x00000004)
  digest: 9069ca78e7450a285173431b3e52c5c25299e473
  EventSize: 4
  Event: 
prettyprint_tpm12_event at: 0x7937389c
  PCRIndex: 5
  EventType: EV_SEPARATOR (0x00000004)
  digest: 9069ca78e7450a285173431b3e52c5c25299e473
  EventSize: 4
  Event: 
prettyprint_tpm12_event at: 0x793738c0
  PCRIndex: 6
  EventType: EV_SEPARATOR (0x00000004)
  digest: 9069ca78e7450a285173431b3e52c5c25299e473
  EventSize: 4
  Event: 
prettyprint_tpm12_event at: 0x793738e4
  PCRIndex: 5
  EventType: EV_EFI_ACTION (0x80000007)
  digest: b6ae9742d3936a4291cfed8df775bc4657e368c0
  EventSize: 47
  Event: Returning from EFI Application from Boot Option
prettyprint_tpm12_event at: 0x79373933
  PCRIndex: 4
  EventType: EV_EFI_BOOT_SERVICES_APPLICATION (0x80000003)
  digest: 2d13d06efd0330decd93f992991b97218410688d
  EventSize: 64
  Event: ?kx
prettyprint_tpm12_event at: 0x79373993
  PCRIndex: 4
  EventType: EV_EFI_BOOT_SERVICES_APPLICATION (0x80000003)
  digest: e0fcc7f88f096737f8a95d7a86e7a4b33c365ebc
  EventSize: 145
  Event: Pqx
prettyprint_tpm12_event at: 0x79373a44
  PCRIndex: 9
  EventType: EV_IPL (0x0000000d)
  digest: 5c88780f029068d6f5863b943575556d7b98c558
  EventSize: 76
  Event: Grub2 command: serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
prettyprint_tpm12_event at: 0x79373ab0
  PCRIndex: 9
  EventType: EV_IPL (0x0000000d)
  digest: 392dc11bf1eea9e5e933e0e401fba80fda90f912
  EventSize: 28
  Event: Grub2 command: default=boot
prettyprint_tpm12_event at: 0x79373aec
  PCRIndex: 9
  EventType: EV_IPL (0x0000000d)
  digest: b993a2e236f21b72a05e401d241a5e7617367738
  EventSize: 26
  Event: Grub2 command: timeout=10
prettyprint_tpm12_event at: 0x79373b26
  PCRIndex: 9
  EventType: EV_IPL (0x0000000d)
  digest: f6afd73c21afe5a5136e7fdf7068d71fb4fc014b
  EventSize: 148
  Event: Grub2 command: menuentry boot {
linux /vmlinuz LABEL=boot root=/dev/ram0 console=ttyS0,115200 console=ttyPCH0,115200 console=tty0 
initrd /initrd
}
prettyprint_tpm12_event at: 0x79373bda
  PCRIndex: 9
  EventType: EV_IPL (0x0000000d)
  digest: 02775bb98cd27a98178eee72a1cd66bec285c839
  EventSize: 158
  Event: Grub2 command: menuentry install {
linux /vmlinuz LABEL=install-efi root=/dev/ram0 console=ttyS0,115200 console=ttyPCH0,115200
console=tty0 
initrd /initrd
}
prettyprint_tpm12_event at: 0x79373c98
  PCRIndex: 9
  EventType: EV_IPL (0x0000000d)
  digest: 69dd51ec4450a2e7b6f3da83370998908aa3370e
  EventSize: 27
  Event: Grub2 command: tpm2dumplog
grub>

The fields here are documented in the specification (version 1.22 of the TPM EFI Protocol spec this time though and in section 3.1.3) so I won’t cover them in detail. Instead let’s look at the first two events:

prettyprint_tpm12_event at: 0x79373000
  PCRIndex: 0
  EventType: EV_S_CRTM_VERSION (0x00000008)
  digest: 1489f923c4dca729178b3e3233458550d8dddf29
  EventSize: 2
  Event: 
prettyprint_tpm12_event at: 0x79373022
  PCRIndex: 0
  EventType: EV_EFI_PLATFORM_FIRMWARE_BLOB (0x80000008)
  digest: 76bd373351e3531ae3e2257e0b07951a2f61ae42
  EventSize: 16
  Event: 

Not a lot of data in these entries. Notably the ‘Event’ field is blank (this is where a textual description of the event should be). The first EventType is the telling bit though (that and that it’s the first thing measrued). This is the first measurement and thus the CRTM, well the CRTM version number at least? The second is much more generic: just some blob of firmware from the platform. What I do find puzzling though is that the first event is listed as 2 bytes long and the second as 16. This length is of the textual event data, the description that looks to be missing. What’s really happening here is that my code treats this field as a NULL terminated string, and the first byte is NULL so the code just doesn’t print anything. This is probably something worth investigating in the future (note to self).

This provides us with an interesting view of the boot process. Neither of these measurements (or the string of events that land in PCR[0]) have much meaning from this angle but the MBM firmware can be built from the EDK2 sources so it may be that with enough code reading we could divine where these values came from. The best thing we have to go on (without additional cooperation from the platform firmware provider) is the EventType and the PCR index where the data was extended. The TCG PC Client Platform Firmware Profile defines “PCR Usage” in section 2.2.4 and PCR[0] is for “SRTM, BIOS, Host Platform Extensions, Embedded Option ROMs and PI Drivers” so basically “firmware”.

For the code that measures the bits that grub loads and depends upon (modules and configuration data) we use PCRs 8 and 9. According to the PC Client PlatformPlatform Firmware Profile 8 and 9 are “Defined for use by the Static OS” which I guess includes the bootloader. According to convention (passed down to me by word of mouth) the even numbered PCRs are where binary data gets hashed and the odd PCRs are for configuration data. So I’m using PCR[8] for Grub modules and binary blobs (kernel, initrd) loaded by grub through the linux command. PCR[9] is where we extend the configuration data loaded by Grub and the commands that Grub executes for the user when they’re in the Grub shell.

The first event in the log for PCR[9] recorded by the core-image-tpm image from meta-measured is the command to set up the serial port. This comes straight from the grub.cfg produced by the openembedded-core recipe:

prettyprint_tpm12_event at: 0x79373a44
  PCRIndex: 9
  EventType: EV_IPL (0x0000000d)
  digest: 5c88780f029068d6f5863b943575556d7b98c558
  EventSize: 76
  Event: Grub2 command: serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1

And you can verify this is the correct value on the console if you want to check my hashing logic:

$ echo -n "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1" | sha1sum
5c88780f029068d6f5863b943575556d7b98c558  -

Transferring the Pre-Boot Event Log to the Kernel

What’s conspicuously missing though are any measurements of Grub’s components (they would be in PCR[8]). The reason for this: building the TPM2 image from meta-measured produces an “embedded” Grub2 configuration with all modules built directly into the EFI executable so no modules to load. So what about the kernel and initrd you ask? In this example I’ve booted into the Grub shell so we haven’t booted a kernel yet. If we do boot the kernel we end up in a weird situation. You’d expect to see the following entries (that I’ve caused on my system manually by executing linux (hd0,msdos2)/vmlinux and a similar command for the initrd):

prettyprint_tpm12_event at: 0x79373e13
  PCRIndex: 9
  EventType: EV_IPL (0x0000000d)
  digest: bfbd2797ce79ccf12c5e1abbf00c10950e12a8db
  EventSize: 42
  Event: Grub2 command: linux (hd0,msdos2)/vmlinuz
prettyprint_tpm12_event at: 0x79373e5d
  PCRIndex: 8
  EventType: EV_IPL (0x0000000d)
  digest: f50f32b01c3b927462c179d24b6ab8018b66e7bc
  EventSize: 40
  Event: Grub2 Linux initrd: (hd0,msdos2)/initrd

But once the kernel takes over the preboot event log is destroyed. The TPM2 spec is different from the 1.2 version in that there isn’t an ACPI table defined for the preboot audit log. It lives only in the UEFI runtime and when Grub calls the ExitBootServices function the event log is destroyed. So all of this code to measure various bits of Grub will produce data that the kernel and user space will never be able to analyze unless we come up with a mechanism to transfer the log to the kernel. This is surprisingly more difficult than I was hoping.

The two solutions I’ve come up with / run across are:

  1. Create a new UEFI config table and copy the contents of the audit log into it.
  2. Have the kernel copy the table before calling ExitBootServices itself, which requires that Grub be patched to not call this function itself (an idea I got from a conversation with Matthew Garrett).

Both of these approaches have their merits and their drawbacks. We’ll discuss those in more detail in the next post.

TrEE vs TCG

I’ve been learning a bit about UEFI and the new TPM2 specifications these past few weeks. My work in this space isn’t ready for public consumption but I’ve run into some interesting data worth mentioning here. I got really stuck on this on account of my UEFI stills being very weak. Thankfully others ran into the same issue and shared their knowledge. I’m putting this up in the hopes of keeping others from falling down the same hole that took up so much of my time.

Dueling Specifications

When I started poking at the 2.0 TPM on my Minnowboard Max, knowing the interplay between the Microsoft TrEE and the TCG TPM 2.0 EFI Protocol specifications would have saved me a pile of time. AFAIK Microsoft is a very big contributor to the TPM 2.0 specs and Windows 8 and 10 both build on it for security infrastructure. The new TPM specs from the TCG are still in draft form and out for pubic review which is good, but Microsoft is shipping real live systems that use with version 2.0 TPMs.

I don’t participate in any of the TCG stuff other than writing software that uses the TPM so I’ve got no inside knowledge here, but I speculate that it’s hard to ship systems when a core component doesn’t have a published interface specification. So what to do? Microsoft published their own version of the TPM UEFI version 2 Protocol specification under the Trusted Execution Environment EFI Protocol name. You can argue that this isn’t the same as the TCG EFI Protocol Specification for TPM Family 2.0 Revision 1.0 but by UEFI law these two protocols have the same GUID and so they’re the same protocol. Period.

Same GUID, Subtle Differences

So Microsoft published a version of the specification early to support their customers and developer community. I don’t see that they had any other choice. But how does this cause problems you ask? Grab your Minnowboard Max, install the 32bit firmware so you get the Intel PTT (firmware TPM2), code up an EFI application that calls the GetCapability function from the protocol and you’ll see. The short version is that there is a subtle difference in the two specs that causes incompatibility. The TCG spec clearly states that all structures in the spec are packed. The Microsoft spec on the other hand says nothing about struct packing outside of the code samples. Their samples however use macros #pragma pack(1) to show which structures should be packed and the TREE_BOOT_SERVICE_CAPABILITY (synonymous with the TCG EFI_TCG2_BOOT_SERVICE_CAPABILITY structure) is not defined with this macro in effect.

The end result is that since Microsoft is (AFAIK) the only platform currently supporting TPM2, all TPM2 hardware / firmware out there implements their TrEE spec. The UEFI protocol implemented on these platforms will return to you an unpacked struct when you call the GetCapability function and if you’re like me and you’re working from the TCG draft spec you’ll be banging your head against the wall for a bit.

Lessons Learned

Pretty interesting first exposure to UEFI and TPM2. Painful in that my UEFI debugging skills are pretty weak. Interesting caus I’ve learned a ton. Checking structure packing is now at the top of my debugging check list. It’s also very interesting to see dueling specs like this. The reality of having to ship hardware / firmware / software before a specification is finalized has got to be a tricky business so it’s hard to fault Microsoft for this.

The relevant upstreams know about this issue thanks to Matthew Garrett and I’m sure they’ll resolve it before the TCG spec is finalized. Most likely the TCG draft spec will be updated and the capability structure will officially not be packed. Don’t bank on this though. YMMV and every other possible disclaimer.

An additional foot note is that the current TCG spec prescribes conflicting version numbers for the StructureVersion and ProtocolVersion fields in the capability structure too. I’ve reported this as part of the public review process as well.