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 Certificate Authority

Certificate authorities use some serious security measures to protect their signing keys (or at least they’re supposed to). These high security requirements are the realm of the traditional hardware security module (HSM). These things address threat models that include attacks against both software and hardware, sometimes very sophisticated ones (like decapping). You get what you pay for, and so HSMs can get pretty pricey. These extreme levels of security and the associated price seems reasonable to me, at least for the CAs whose keys are trusted by the likes of Chrome and Firefox (and subsequently all of their users).

Not all CAs need that kind of security though. Definitely not the CA I use for various test applications. For this I’ve always just kept the keys on a thumb drive as a way to keep them off of my laptop.

The type, strength and cost of the security measures appropriate for an application aren’t binary. The two example applications above are likely the extremes since the key protection schemes have such radically different security properties: HSM vs commodity OS storing keys / files on disk. But between these two applications are a spectrum of others with varying threat models.

root CA signing key in TPM2

What these applications are isn’t the interesting part so long as we’re willing to admit that they exist. What *is* interesting are the technologies available to mitigate relevant threats. Historically, there haven’t been many options available between the two extremes described above. Recently however some products have emerged to fill this space including TPM2. Microsoft added TPM2 to their logo requirements for Windows 10 so it’s effectively ubiquitous in newer laptops. TPM2 is an interesting option because it mitigates key theft through the use of “shielded locations” where sensitive operations (use of private keys) are carried out separate from the main CPU. For my particular application this is “good enough”.

background

I’ve always used the OpenSSL tools and the associated commands to manage my local CA. The available documentation and collective knowledge on the internet make this tool indispensable and I’ve got my workflow scripted. What I want to do is integrate the TPM2 OpenSSL engine into my existing scripts and configurations.

Documentation for building and installing tpm2tss openssl engine is here: https://github.com/tpm2-software/tpm2-tss-engine/blob/master/INSTALL.md. The rest of this document assumes you have it installed and properly configured.

make your root key

Using the TPM2 to protect your CA signing keys is surprisingly easy. I typically shy away from using the word “easy” when talking about the TPM but in this case, and thanks to the TPM2 OpenSSL Engine it really is. The `tpm2tss` engine provides a binary `tpm2tss-genkey` for key key generation. For this example a simple RSA 2k key is generated:


$ tpm2tss-genkey --alg rsa --keysize 2048 ca-root.key.tss

I’ve given the root key the extension `.tss` because it’s in a form unique to the tpm2tss engine.

Once you’ve got your ca root key we can use the `openssl` command line tool to generate a CSR for it.
NOTE: The details of the openssl configuration file used for root CA signing keys is beyond the scope of this document. I’m using unmodified versions of these same files from the exceptional OpenSSL Certificate Authority by Jamie Nguyen, openssl.cnf.


$ openssl req \     
    -config openssl.cnf \
    -new -x509 \  
    -engine tpm2tss \
    -key ca-root.key.tss \   
    -keyform engine \                                                              
    -new -x509 \
    -days 7300 \  
    -sha256 \     
    -extensions v3_ca \                                                            
    -out ca-root.cert

Notice that the options for this command include `-engine tpm2tss` as well as `-keyform engine`. This causes openssl to generate & sign a CSR for the root key. The output is a self signed cert for the `ca-root.key.tss` key. It’s possible to include the engine configuration in the `openssl.cnf`. They’re provided on the commend line here for emphasis.

issuing subkeys

The rest of this is CA stuff is mechanical: We use the `openssl` tool `req` and `ca` commands to issue subkeys for various purposes while providing the new engine specific command line options. All of this is signing operations and certificate generation and all supported by the tpm2tss openssl engine. I followed Jamie Nguyen’s documentation above substituting in use of the `tpm2tss` engine on the command line where appropriate and everything worked as expected.

conclusions etc

TPM2 is a powerful tool and thanks to the `tpm2-tss-engine` we can continue to use the `openssl` command line tools we all know and love while benefiting from the protections offered by TPM2. The example above is just an example though. Root signing keys are rarely used and so storing them offline or in a token like a yubikey is often the best choice. TPM2 may be a better fit for intermediate keys, like those on a signing server integrated into a CI pipeline. Might be fun to build an “ideal” home CA architecture with a yubikey for the root keys and an embedded platform with a TPM2 for issuing credentials as an an intermediate CA for some application.

UEFI TPM2 examples

I’ve fallen way behind in writing up the highlights from the last few years spent building the TSS2 libraries and plumbing. Recently though I’ve had a few people reach out asking about accessing various bits of data exposed by the TCG2 UEFI protocol driver. Back when I first got involved in the TPM2 effort at Intel I wrote a few tools for the Grub2 shell (never upstreamed) that did just this and wrote about it here.

Having these as UEFI executables sounds useful but I never had an existing repo where they would fit well and I didn’t want to create a new one. I’ve made a lot of progress on the TCTI for UEFI that I presented at the Linux Security Summit last year and since it’s the only UEFI specific repo in the tpm2-software org, it’s kinda the natural place for them.

I’ve added these UEFI shell executables to the ‘examples’ directory of the tpm2-tcti-uefi repo. These are very much example applications and not something I’d call utilities. They take no command line options and are intended only as examples of how to use a specific interface, either the TCG2 UEFI protocol driver or a TSS2 API. I’m not against adding features to these examples but I think this work should be weighed against the cost of making the existing tpm2-tools usable from the UEFI shell. Till then these aren’t part of a supported interface in the tpm2-tcti-uefi (see the README).

But before we get into the example applications we’ll take a look at the test harness and the script we use to execute the examples. This script integrates all of the stuff necessary to automate the execution of the UEFI executables (qemu, OVMF and swtpm) in the autotools test harness.  Better yet, we can also use it to run these examples and capture the output.

test harness

Autotools has a very simple but very powerful test harness. It does the bare minimum which is executing test applications and collecting their output. Anything more complex requires that we provide our own test setup script. To associate this script with the test executables Automake requires us to use a path suffix and in this case .efi seemed appropriate. For the executables we list in the TESTS variable the ones with the .efi suffix will be invoked by running the script at $(srcdir)/lib/efi-test-setup.sh, passing it the path to the executable file. The `check` make target then works as expected (even in parallel). Pretty cool.

To make this concrete by way of example: if we execute the make check target with TESTS=test/hello-world.efi set in the environment:make check TESTS=test/hello-world.efi,autotools will invoke the test script like so:

$(srcdir)/lib/efi-test-setup.sh --startup-template=$(srcdir)/lib/startup.nsh.template test/hello-world.efi

The executables under the example directory are not executed by the test harness when make check is run by default. Using the trick above we can cause them to be executed like so:

make check TESTS=example/tcg2-get-pcr-banks.efi

The remaining sections will rely on this method to invoke each example executable.

tcg2-get-pcr-banks.efi

This example demonstrates how to query the TCG2 UEFI protocol driver for the currently active PCR banks. This can be discovered by querying the TPM2 device directly using the TSS2 APIs however the UEFI protocol driver makes this available through a much more simple interface. Execute the example code with the following command:

make check TESTS=example/tcg2-get-pcr-banks.efi

The output from this “test” will end up in the file example/tcg2-get-pcr-banks.log and should look something like:

GetActivePcrBanks: 0x00000007
    EFI_TCG2_BOOT_HASH_ALG_SHA1: true
    EFI_TCG2_BOOT_HASH_ALG_SHA256: true
    EFI_TCG2_BOOT_HASH_ALG_SHA384: true
    EFI_TCG2_BOOT_HASH_ALG_SHA512: false
    EFI_TCG2_BOOT_HASH_ALG_SM3_256: false

tpm2-get-pcrs.efi

The next logical thing to do after we’ve queried for the active PCR banks is to get their contents. The TCG2 UEFI protocol does not expose this data to us and so we use the UEFI TCTI and the TSS2 System API (libtss2-sys). Again we use the check target, this time passing in the path to versions of the TSS2 libraries built for UEFI (see the section titled Dependencies in doc/examples.md for more info):

make check -L$(pwd)/../$(pwd)/../tss2uefi_root/usr/local/lib check TESTS=example/tpm2-get-pcrs.efi

The output will end up in example/tcg2-get-pcr-banks.log and should show output from the EFI executable for each bank. Below is example output from a single PCR bank:

PCR bank: EFI_TCG2_BOOT_HASH_ALG_SHA384
  PCR: 0
    00000000: C5 A5 4D 51 F2 3E C4 10-F8 CF DE E8 83 D7 46 8E  *..MQ.>........F.*
    00000010: A3 C4 A9 EB C1 1F 32 90-0A 7E 24 22 89 55 C1 B7  *......2...$".U..*
    00000020: 4F 0F 50 0F D1 7D 67 76-D9 20 E3 BD D6 4F A7 AC  *O.P...gv. ...O..*
  PCR: 1
    00000000: 61 AB A4 CC CC 39 D1 A6-94 F1 DF 3C C8 B2 17 8F  *a....9.....<....*
    00000010: 85 59 33 76 13 DE 05 09-1B 40 7A BF D4 36 58 2D  *.Y3v.....@z..6X-*
    00000020: 7C 81 E9 68 A1 7F F5 C2-56 FD 48 76 86 06 82 D9  *...h....V.Hv....*
  PCR: 2
    00000000: 51 89 23 B0 F9 55 D0 8D-A0 77 C9 6A AB A5 22 B9  *Q.#..U...w.j..".*
    00000010: DE CE DE 61 C5 99 CE A6-C4 18 89 CF BE A4 AE 4D  *...a...........M*
    00000020: 50 52 9D 96 FE 4D 1A FD-AF B6 5E 7F 95 BF 23 C4  *PR...M....^...#.*
  PCR: 3
    00000000: 51 89 23 B0 F9 55 D0 8D-A0 77 C9 6A AB A5 22 B9  *Q.#..U...w.j..".*
    00000010: DE CE DE 61 C5 99 CE A6-C4 18 89 CF BE A4 AE 4D  *...a...........M*
    00000020: 50 52 9D 96 FE 4D 1A FD-AF B6 5E 7F 95 BF 23 C4  *PR...M....^...#.*
  PCR: 4
    00000000: 2E D8 40 1D 9A 80 E5 07-D8 B6 C9 0D 6E 14 2E A9  *..@.........n...*
    00000010: 96 89 44 C7 79 2D AC C4-FA 14 4E 24 68 6B 3B DC  *..D.y-....N$hk;.*
    00000020: 31 B0 CA EB CD 26 54 6B-82 34 59 D6 0E DA 9F A0  *1....&Tk.4Y.....*
  PCR: 5
    00000000: 51 89 23 B0 F9 55 D0 8D-A0 77 C9 6A AB A5 22 B9  *Q.#..U...w.j..".*
    00000010: DE CE DE 61 C5 99 CE A6-C4 18 89 CF BE A4 AE 4D  *...a...........M*
    00000020: 50 52 9D 96 FE 4D 1A FD-AF B6 5E 7F 95 BF 23 C4  *PR...M....^...#.*
  PCR: 6
    00000000: 51 89 23 B0 F9 55 D0 8D-A0 77 C9 6A AB A5 22 B9  *Q.#..U...w.j..".*
    00000010: DE CE DE 61 C5 99 CE A6-C4 18 89 CF BE A4 AE 4D  *...a...........M*
    00000020: 50 52 9D 96 FE 4D 1A FD-AF B6 5E 7F 95 BF 23 C4  *PR...M....^...#.*
  PCR: 7
    00000000: 98 44 1C 7F 76 25 D1 00-58 C4 76 83 AE C4 86 CE  *.D..v%..X.v.....*
    00000010: 31 1C 63 32 35 EB 55 55-93 A7 EE 79 11 21 E3 57  *1.c25.UU...y.!.W*
    00000020: 8A E7 2D 04 EC EF 66 1F-27 2D 59 05 8B 77 AF 35  *..-...f.'-Y..w.5*
  PCR: 8
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  PCR: 9
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  PCR: 10
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  PCR: 11
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  PCR: 12
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  PCR: 13
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  PCR: 14
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  PCR: 15
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  PCR: 16
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  PCR: 17
    00000000: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000010: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000020: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
  PCR: 18
    00000000: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000010: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000020: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
  PCR: 19
    00000000: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000010: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000020: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
  PCR: 20
    00000000: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000010: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000020: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
  PCR: 21
    00000000: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000010: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000020: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
  PCR: 22
    00000000: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000010: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
    00000020: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF  *................*
  PCR: 23
    00000000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
    00000020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*

tcg2-get-eventlog.efi

Another interesting thing UEFI does for us is maintain a log of the data extended into the TPM2 PCRs. The structure and format of this log is documented in the TCG UEFI specs. This example code doesn’t do anything other than query the TCG2 UEFI protocol driver for the log, parse it and then print it to the console formatted for human eyes.

The log format is pretty simple but it’s binary so extracting individual fields and mapping them to C data types takes a bit of effort. The logic that parses the log is integrated with the formatting code so it’s not particularly useful outside of this context but it may be a useful example. The output of the log is quite verbose but a few select events are shown here:

EVENT: 2
  PCRIndex: 0
  EventType: EV_S_CRTM_VERSION (0x8)
  DigestCount: 3
  TCG_DIGEST2: 0
    AlgorithmId: EFI_TCG2_BOOT_HASH_ALG_SHA1 (0x4)
    Digest: 20 bytes
    00000000: 14 89 F9 23 C4 DC A7 29-17 8B 3E 32 33 45 85 50  *...#...)..>23E.P*
    00000010: D8 DD DF 29                                      *...)*
  TCG_DIGEST2: 1
    AlgorithmId: EFI_TCG2_BOOT_HASH_ALG_SHA256 (0xB)
    Digest: 32 bytes
    00000000: 96 A2 96 D2 24 F2 85 C6-7B EE 93 C3 0F 8A 30 91  *....$.........0.*
    00000010: 57 F0 DA A3 5D C5 B8 7E-41 0B 78 63 0A 09 CF C7  *W...]...A.xc....*
  TCG_DIGEST2: 2
    AlgorithmId: EFI_TCG2_BOOT_HASH_ALG_SHA384 (0xC)
    Digest: 48 bytes
    00000000: 1D D6 F7 B4 57 AD 88 0D-84 0D 41 C9 61 28 3B AB  *....W.....A.a(;.*
    00000010: 68 8E 94 E4 B5 93 59 EA-45 68 65 81 E9 0F EC CE  *h.....Y.Ehe.....*
    00000020: A3 C6 24 B1 22 61 13 F8-24 F3 15 EB 60 AE 0A 7C  *..$."a..$...`...*
  Event: 2 bytes
  00000000: 00 00                                            *..*

This is a pretty good example of the TPM2 log format since it clearly shows multiple and variable length hashes. Further, the same three hashes are shown as enabled by the tcg2-get-pcr-banks.efiexample as all were executed under the same OVMF image. The format from the 1.2 TPM is much easier by comparison since only the fixed width SHA1 hash is supported.

Conclusion

Upstreaming my original work for the Grub2 shell was blocked on the merge of mjg59’s patches using the verifiers framework. Now that his work has gone into upstream I’m hoping to port these over to the Grub2 shell and send them upstream too (eventually). For the short term they’re available as reference and example code from the tpm2-tcti-uefi repo.

TCG TSS2 async APIs and event driven programming

UPDATE 2018-07-03: Example code ported to latest (2.0) release of the tpm2-tss and tpm2-abrmd.

API design is hard. Over the past year I’ve been contributing to an effort to standardize a set of APIs for interacting with TPM2 devices through the Trusted Computing Group’s (TCG) TPM2 Software Stack (TSS) working group (WG). The available APIs and implementations predate my involvement but I’m hoping to contribute some use cases to motivate the need for various aspects of the API and architecture. According to Joshua Bloch use-cases are core to API design so I figure having some thoughts and examples documented and available would be helpful.

Use-case driven design: asynchronous function calls

After watching Mr. Bloch’s talk a few times in the last year, the API design principle that’s stuck with me most is that the design should be motivated / driven by use cases. A use-case adopted by the TSS WG that I’ve found particularly interesting is support for event driven programming environments / frameworks. On several occasions this use case has come into question and generally the argument against the feature comes from the trade-offs involved: detractors argue that the utility of and demand for the use case does not outweigh the complexity that it introduces to the API.

But even though event driven programming is integral to many languages and frameworks, we don’t have any examples of applications using the TPM in such an environment. There aren’t many applications using the TPM to begin with and since the TSS for TPM 1.2 devices never supported async I/O (AFAIK) there was never an opportunity to do so. Since the TSS2 APIs include support for asynchronous operations I thought it was worth while to write some example code to demonstrate use of this part of the API in a popular programming framework.

You know what they say:
if-you-build-it

Integrating SAPI with GLib and GIO

The TSS2 APIs are limited to C. While some bindings to higher level languages have been emerging I’m expecting that C will be the primary language for interacting with the TPM for some time. There is no shortage of options for event frameworks in C, but for the purposes of this post I’m going to focus exclusively on GLib and the GIO libraries.

Before we dive any deeper though I want to include a quick note on event driven programming in general: The term “event driven” doesn’t necessarily mean asynchronous or even non-blocking. Further, not all asynchronous I/O operations are truly asynchronous. Many depend on polling file descriptors with some help from the kernel through the select or poll system calls. I don’t intend to get into the merits or purity of the various approaches though. In this post we’re just using the tools available in a way that works with GLib and GIO. If you’re interested in a “deep-dive” into event driven programming in C there are much better resources out there: start here

GLib and GIO primitives

At the heart of the GLib event model are 3 objects: the GMainLoop, the associated GMainContext and the GSource. These objects are very well documented so I’m going to assume general familiarity. For our purposes we only need to know a few things:

  1. GMainLoop is a thread that processes events. If used properly the GMainLoop will handle the asynchronous I/O operations so we don’t have to polling or threading code ourselves.
  2. GMainContext is a container used by the GMainLoop. It holds a collection of GSources that make up the events in the GMainLoop.
  3. GSource is an object that holds a number of function pointers that are invoked in response to some event.

Let’s do a quick example to illustrate before we mix in the TSS2 stuff. The following code is an example of a GMainLoop with a single timeout event source. The event source will invoke a callback after a specified time. The timeout event will occur on the interval so long as the callback returns G_SOURCE_CONTINUE.

#include <glib.h>
#include <stdlib.h>

#define TIMEOUT_COUNT_MAX 10
#define TIMEOUT_INTERVAL  100

typedef struct {
    GMainLoop *loop;
    size_t timeout_count;
} data_t;

gboolean
timeout_callback (gpointer user_data)
{
    data_t *data = (data_t*)user_data;

    g_print ("timeout_count: %zu\n", data->timeout_count);
    if (data->timeout_count < TIMEOUT_COUNT_MAX) {
        ++data->timeout_count; 
        return G_SOURCE_CONTINUE;
    } else {
        g_main_loop_quit (data->loop);
        return G_SOURCE_REMOVE;
    }
}
int
main (void)
{
    GMainContext *context;
    GSource *source;
    data_t data = { 0 };

    source = g_timeout_source_new (TIMEOUT_INTERVAL);
    g_source_set_callback (source, timeout_callback, &data, NULL);
    context = g_main_context_new ();
    g_source_attach (source, context);
    g_source_unref (source);

    data.loop = g_main_loop_new (context, FALSE);
    g_main_context_unref (context);

    g_main_loop_run (data.loop);
    g_main_loop_unref (data.loop);
    return 0;
}

In ~40 LOC we have an event loop triggering a timeout on a fixed interval. After a fixed number of timeout events the program shuts itself down cleanly. Not bad for C. It’s worth noting as well that GLib has some convenience functions (in this case g_timeout_add) that would have saved us a few lines here but I’ve opted to do those bits manually to make future examples more clearly relatable to this one.

Event-driven TSS2 function calls

Now that we have the basic tools required to use a GMainLoop for asynchronous events we’ll play around with adapting them for use with asynchronous calls in the SAPI library. We’ll make this next step a small one though. We won’t go straight to creating GObjects to plug into the GMainLoop. Instead let’s take a look at *how* the TSS2 libraries expose the primitives we need to make async calls and *what* those primitives are.

Asynchronous TSS2

The TCG TSS2 APIs all work together to provide the mechanisms necessary to make asynchronous calls. Each layer in the stack has a purpose and an asynchronous mechanism for carrying out that purpose. From the perspective of an application, the lowest layer is the TCTI library. This API is extremely small and provides transmit / receive functions as an interface to the underlying IPC mechanism. This API can be used by higher layers to send TPM2 command buffers and receive the associated response without having to worry about the underlying implementation.

If the underlying IPC mechanism used by the TCTI library is capable of asynchronous operation then the TCTI may expose non-blocking behavior to the client. The TCTI API provides only one function capable of non-blocking I/O: The `receive` function can be passed a timeout value that will cause the calling thread to block for some time waiting for a response. If no response is received before the timeout then the caller will be returned a response code instructing them to “try again”. If this timeout is provided a timeout value of TSS2_TCTI_TIMEOUT_NONE then the function will not block at all. This is about as simple as non-blocking I/O can get.

Additionally, TCTI libraries provide the getPollHandle function. This function returns the TSS2_TCTI_POLL_HANDLE type which we map to an asynchronous I/O mechanism for various platforms. On Linux it defined as struct pollfd. On Windows it would make sense to map it to the HANDLE type.

But the TSS2_TCTI_POLL_HANDLE isn’t intended for use by the typical developer. Instead it’s intended for use as a mechanism to integrate the TSS2 stack into event driven frameworks. So if the TCTI mechanism works as advertised we should be able to use this type to drive events in the GLib main loop.

Asynchronous SAPI function calls

The mechanisms from the TCTI layer are necessary for asynchronous interaction with TPM2 devices but alone they’re not sufficient. The layers above the TCTI in the TSS2 architecture must participate by providing asynchronous versions of each function. Using the SAPI as an example, each TPM2 function is mapped to a SAPI function call. But the SAPI provides not only a synchronous version of the function call (let’s use Tss2_Sys_GetCapability in this example), but also an asynchronous version.

For our the TPM2 GetCapability function the SAPI header provides:

  1. Tss2_Sys_GetCapability – A synchronous function that will send a command to the TPM2 and block until a response is ready to be passed back to the caller.
  2. Tss2_Sys_GetCapability_Prepare – A function to prepare / construct the TPM2 command buffer from the function parameters before they’re transmitted to the TPM.
  3. Tss2_Sys_GetCapability_Complete – A function to convert the response received by the _ExecuteFinish from the byte stream representation to C structures.

Additionally the utility functions are required:

  1. Tss2_Sys_ExecuteAsync – A generic function to send the previously prepared TPM2 command buffer to the TPM. It will only call the underlying TCTI transmit function.
  2. Tss2_Sys_ExecuteFinish – A generic function to receive the TPM2 response buffer (the response to the command previously sent). The second parameter to this function is a timeout value that can be set to non-blocking.

These functions provide us with all we need to create the TPM2 command buffer, transmit it, receive the response and transform the response buffer into C structures that we can use in our program.
The event mechanism is able to trigger an event for us when there’s data ready on the struct pollfd from the underlying TCTI module.

Combining GSource and the TSS2

We’ve effectively enumerated the tools at our disposal. Now we put them together into a very minimal example. Our goal here isn’t to get perfect integration with the GLib object model, just to use the available tools to hopefully demonstrate the concept.

The TCTI layer and the TSS2_TCTI_POLL_HANDLE type mapping to the struct pollfd was the big hint in this whole thing. Using the pollfd structure we can extract the underlying file descriptor and use this well known IPC interface to learn of events from the fd. But before we set off to implement a GSource to drive events for the SAPI let’s see if there’s an existing one that we might use. File descriptors (fds) are pretty common on *nix platforms so it makes sense that there may already be a tool we can use here.

Sure enough the GLib UNIX specific utilities and integration provides us with just this. All we must do is provide the g_unix_fd_source_new function with the fd from the underlying TCTI while requesting the event be triggered when the G_IO_IN (input data ready) condition becomes true for the fd.

NOTE: Before we get too far though it’s important to point out that the TCTI we use for this example is libtcti-tabrmd. This TCTI requires the tpm2-abrmd user space resource management daemon. The other TCTIs for communicating with the kernel device driver and the TPM2 emulator do not support asynchronous I/O. The kernel driver does not support asynchronous I/O. We use a few utility functions for creating the TCTI and SAPI contexts.

The header:

#include <tss2-tpm2-types.h>
#include <tss2-tcti-tabrmd.h>
    
/*  
 * Allocate and initialize an instance of the TCTI module associated with the
 * tpm2-abrmd. A successful call to this function will return a TCTI context
 * allocated by the function. It must be freed by the caller.
 * A failed call to this function will return NULL.
 */ 
TSS2_TCTI_CONTEXT*
tcti_tabrmd_init ();
    
/*  
 * Allocate and initialize an instance of a SAPI context. This context will be
 * configured to use the provided TCTI context. A successful call to this
 * function will return a SAPI context allocated by the function. It must be
 * freed by the caller.
 * A failed call to this function will return NULL.
 */
TSS2_SYS_CONTEXT*
sapi_init_from_tcti_ctx (TSS2_TCTI_CONTEXT *tcti_ctx);

/*
 * Allocate and initialize an instance of a TCTI and SAPI context. The SAPI
 * context will be configured to use a newly allocated instance of the tabrmd
 * TCTI. A successful call to this function will return a TCTI and SAPI
 * context allocated by the function. Both must be freed by the caller.
 * A failed call to this function will return NULL.
 */
TSS2_SYS_CONTEXT*
sapi_init_tabrmd (void);

And the implementation:

#include <errno.h>
#include <glib.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>

#include <tss2/tss2_tpm2_types.h>
#include <tss2/tss2-tcti-tabrmd.h>
    
#include "context-util.h"

TSS2_TCTI_CONTEXT*
tcti_tabrmd_init ()
{   
    TSS2_RC rc;
    TSS2_TCTI_CONTEXT *tcti_ctx;
    size_t size;
    
    rc = tss2_tcti_tabrmd_init (NULL, &size);
    if (rc != TSS2_RC_SUCCESS) {
        g_critical ("Failed to get allocation size for tabrmd TCTI context: "
                    "0x%" PRIx32, rc);
        return NULL;
    }
    tcti_ctx = calloc (1, size);
    if (tcti_ctx == NULL) {
        g_critical ("Allocation for TCTI context failed: %s",
                    strerror (errno));
        return NULL;
    }
    rc = tss2_tcti_tabrmd_init (tcti_ctx, &size);
    if (rc != TSS2_RC_SUCCESS) {
        g_critical ("Failed to initialize tabrmd TCTI context: 0x%" PRIx32,
                    rc);
        free (tcti_ctx);
        return NULL;
    }
    return tcti_ctx;
}
TSS2_SYS_CONTEXT*
sapi_init_from_tcti_ctx (TSS2_TCTI_CONTEXT *tcti_ctx)
{
    TSS2_SYS_CONTEXT *sapi_ctx;
    TSS2_RC rc;
    size_t size;
    TSS2_ABI_VERSION abi_version = TSS2_ABI_VERSION_CURRENT;

    size = Tss2_Sys_GetContextSize (0);
    sapi_ctx = (TSS2_SYS_CONTEXT*)calloc (1, size);
    if (sapi_ctx == NULL) {
        g_critical ("Failed to allocate 0x%zx bytes for the SAPI contextn",
                    size);
        return NULL;
    }
    rc = Tss2_Sys_Initialize (sapi_ctx, size, tcti_ctx, &abi_version);
    if (rc != TSS2_RC_SUCCESS) {
        g_critical ("Failed to initialize SAPI context: 0x%xn", rc);
        free (sapi_ctx);
        return NULL;
    }
    return sapi_ctx;
}
TSS2_SYS_CONTEXT*
sapi_init_tabrmd (void) {
    TSS2_SYS_CONTEXT  *sapi_context;
    TSS2_TCTI_CONTEXT *tcti_context;

    tcti_context = tcti_tabrmd_init ();
    if (tcti_context == NULL) {
        return NULL;
    }
    sapi_context = sapi_init_from_tcti_ctx (tcti_context);
    if (sapi_context == NULL) {
        free (tcti_context);
        return NULL;
    }
    return sapi_context;
}

By combining all of this we can create a simple program that makes an asynchronous SAPI function call. At a high level our goal is to execute a TPM2 command without blocking. When the TPM2 device sends our application a response we want the GLib event loop to invoke our callback so that we can do something with the response. We accomplish this with roughly the following steps:

  1. Create the TCTI & SAPI contexts.
  2. Create a GSource for the fd associated with the TCTI using the g_unix_fd_source_new GSource constructor.
  3. Connect the GSource with the GMainContext and register a callback.
  4. Create the GMainLoop and associate it with the proper context.
  5. Make the asynchronous SAPI function call.
  6. Start the GMainLoop.

When data is ready in the TCTI our callback will be invoked and it will do the following:

  1. Finish the SAPI function call.
  2. Dump out some data returned from the SAPI function call.
  3. Quit the main loop (will terminate the program).

Here’s the code:

#include <glib.h>
#include <glib-unix.h>
#include <inttypes.h>

#include <tss2/tss2_sys.h>
    
#include "context-util.h"

typedef struct { 
    TSS2_SYS_CONTEXT *sapi_context;
    GMainLoop *loop;
} data_t;
    
gboolean
fd_callback (gint fd,
             GIOCondition condition,
             gpointer user_data)
{
    data_t *data = (data_t*)user_data;
    TSS2_RC rc;
    TPMI_YES_NO more_data;
    TPMS_CAPABILITY_DATA capability_data = { 0 };

    rc = Tss2_Sys_ExecuteFinish (data->sapi_context, 0);
    rc = Tss2_Sys_GetCapability_Complete (data->sapi_context,
                                          &more_data,
                                          &capability_data);
    g_print ("Capability: 0x%" PRIx32 "\n", capability_data.capability);
    g_print ("Capability data command count: %" PRIu32 "\n",
             capability_data.data.command.count);
    g_main_loop_quit (data->loop);
    return G_SOURCE_REMOVE;
}
int
main (void)
{
    GMainContext *context;
    GSource *source;
    TSS2_TCTI_CONTEXT *tcti_context;
    TSS2_RC rc;
    TSS2_TCTI_POLL_HANDLE poll_handles[1];
    data_t data;
    size_t poll_handle_count;

    /* setup TCTI & SAPI contexts */
    tcti_context = tcti_tabrmd_init ();
    g_assert (tcti_context != NULL);
    data.sapi_context = sapi_init_from_tcti_ctx (tcti_context);
    g_assert (data.sapi_context != NULL);
    /* get fds to poll for I/O events */
    rc = tss2_tcti_get_poll_handles (tcti_context,
                                     poll_handles,
                                     &poll_handle_count);
    g_assert (rc == TSS2_RC_SUCCESS);
    g_assert (poll_handle_count == 1);
    if (!g_unix_set_fd_nonblocking (poll_handles[0].fd, TRUE, NULL)) {
        g_error ("failed to set fd %d to non-blocking", poll_handles[0].fd);
    }
    /* setup GLib source to monitor this fd */
    source = g_unix_fd_source_new (poll_handles[0].fd, G_IO_IN);
    context = g_main_context_new ();
    g_source_attach (source, context);
    /* setup callback */
    g_source_set_callback (source, (GSourceFunc)fd_callback, &data, NULL);
    data.loop = g_main_loop_new (context, FALSE);
    g_main_context_unref (context);
    /* make initial get capability call */
    rc = Tss2_Sys_GetCapability_Prepare (data.sapi_context,
                                         TPM2_CAP_COMMANDS,
                                         TPM2_CC_FIRST,
                                         TPM2_MAX_CAP_CC);
    g_assert (rc == TSS2_RC_SUCCESS);
    rc = Tss2_Sys_ExecuteAsync (data.sapi_context);
    g_assert (rc == TSS2_RC_SUCCESS);
    /* run main loop */
    g_main_loop_run (data.loop);
    g_main_loop_unref (data.loop);
    /* cleanup TCTI / SAPI stuff */
    Tss2_Sys_Finalize (data.sapi_context);
    g_free (data.sapi_context);
    tss2_tcti_finalize (tcti_context);
    g_free (tcti_context);

    return 0;
}

Conclusion & Next Steps

Pretty straight forward all things considered. But this demonstration leaves a few things to be desired: Firstly no application should have to dig directly into the TCTI to pull out the underlying fd. Instead we should have a function to create a GSource from the SAPI context directly much like g_unix_fd_source_new`

Secondly, there is little in the program to convince us that the function call is truly asynchronous. To make our point we need additional tasks that the GMain loop can execute in the background while the TPM is handling our command. A GTK+ application with a chain of slow TPM2 functions that create a key hierarchy or perform some slow RSA key function (encrypt) while the UI remains responsive would be much more convincing. This may be the topic of a future post if I don’t get distracted.

Finally: this is a lot of machinery and it often comes under attack as adding too much complexity to the API. Instead I’d argue that complexity here is the minimum complexity necessary to enable easy integration of the TSS2 APIs into event driven programming frameworks. Most programmers will never have to interact with this machinery. Instead they should be used to create higher level APIs / Objects that integrate into these event-driven programming frameworks.

Haitus followed by wordpress update misery

I crawl under a rock for a few months and my penance is a miserable wordpress upgrade when I return. TL;DR: there’s a bug in Debian Jessie vsftpd that breaks the wordpress upgrade. Thankfully someone filed a bug and moving forward to the vsftp version (3.0.3) is the fix.

I had to do one more thing though: this newer version of vsftpd disables write access for ALL accounts, even authorized accounts. No big deal, just find the write_enable variable in the vsftpd.conf file, uncomment it, and set it to YES. That only took me an hour to figure out :/

But at least now my WP install is up to date, I’m out from under my rock and I can get back to my blogging routine.

TPM2.0-TSS “1.0” release

I had hoped to cut this 1.0 release last week but we received a few last minute contributions that fixed a number of stability issues and a buffer overflow in the resourcemgr. So better late than never, the 1.0 release of the TPM2.0-TSS code has been tagged!

As part of the release I went through and did some maintainer B.S. that was long overdue: adding an AUTHORS and MAINTAINERS file, as well as updating the CHANGELOG and porting it over to markdown (so it’s now CHANGELOG.md).

So now the work on the next release begins … I’m tempted to make a pile of resolutions outlining where I want to focus my efforts initially but given the hectic schedule I know how inaccurate and difficult to live up to they will be. So for now it’s just forward progress on all fronts!

TPM2 Response Codes

For those of you struggling to use the TPM2 SAPI (system API) I’ll put the TLDR here hopefully to save you time and suffering:

  • Decoding TPM2 response codes is a PITA
  • I wrote a tool to make it a bit easier on you. It’s called tpm2_rc_decode and you can find it in the tpm2.0-tools repo.

For those if you interested in the back-story and some general information about TPM2 response codes: read on.

A maintainers priorities

I’ve come to realize that the single most important thing (to me) when working with a new library or tool is having a simple / easy debugging cycle. In the most simple case, like a libc function, returning an integer error code that’s easy to understand is crucial. On Linux, the standard man pages and the obligatory ‘return value’ section are usually sufficient.

So when I took over maintenance of the TPM2.0-TSS project a few months back the first thing I did was try to write a simple program to use the most basic parts of the API. The program I wrote called a single function from the TSS API (Tss2_Sys_GetCapabilities) but of course it didn’t work initially. Worse yet the return value that I got from the function call wasn’t something that could be decoded with a simple look up.

This triggered a sort of revelation where I realized that we’ve made it nearly impossible for people to create meaningful bug reports. Back in June, someone reported the same issue on the tpm2.0-tools project: it’s extremely time consuming to decode TPM response codes by hand. This in turn means that neither the TPM2.0-TSS, or any of the projects consuming it will get good bug reports from their users. Generally this mean that they won’t have many users.

This is a pretty well bounded problem and seemed like an “easy win” that would solve two problems at the same time: make using the TPM2 TSS easier for users, and make for higher quality bug reports from said users.

TPM2 response code encoding

TPM2 response codes (hence forth RCs) aren’t simple integers. They’re unsigned 32bit integers with a pile of information encoded in them. The complexity has a purpose though: a single RC will tell you which part of the TPM software stack (TSS) produced the RC (from the TPM all the way up to the high level APIs), whether it’s an RC from the 1.2 or the 2.0 spec, which format the RC is in, the severity of the RC (warning vs error) and even which parameter caused the RC. Oh yeah, and what the actual error / warning code is. That’s a lot of data in my book.

The hardest part of decoding these RCs is tracking down data on the format and all of the other bits. A bit of reading and searching will turn up the following:

  • Part #1 of the spec (architecture) has a good overview and flow chart for decoding RCs in section 39.
  • Part #2 of the spec has the gory details on the bit fields and what they mean in section 6.6.
  • The TSS spec documents the response code layer indicators and the TSS specific RCs in section 6.1.2 (NOTE: this will change in the next iteration of the spec)

For the sake of keeping this post as accurate as possible for as long as possible I won’t reproduce much data from the specifications. That’s what they’re for. Instead I’ll keep things limited to discussion of the tool that I wrote, some of the major annoyances that I encountered and some of the work I’m doing upstream to fix things.

tpm2_rc_decode

The algorithm for decoding RCs in part 1 of the spec was a good starting point but it’s not sufficient. It omits the details around decoding RCs generated by software outside of the TPM. For this I had to account for the ‘layer indicator’ from the RC. The augmented algorithm is documented in the commit message for the tm2_rc_decode.

Once this algorithm was implemented and documented the tool mostly becomes an exercise in looking up strings in tables that map some integer value (the error code in bits 0 through 5 or 6, or the layer identifier in bits 16 through 23). I had hoped to be able to automate the generation of these tables from the specification but parsing a PDF is a pain in the ass. I’ll probably end up posting more on automating code generation from the TPM2 specs in the future so I won’t say much here.

The neglected TSS spec

This was really my first bit of work that required I dig into the RC portion of the TSS specification. When I started this work it was in pretty bad shape. The structure and definition of all of the TSS RCs and their meaning was well done but the language used in this part of the spec was horribly inconsistent. It interchanged terms like ‘error code’ with ‘response code’ and ‘return code’ seemingly at random. It played similar games with terms like ‘error level’ and ‘layer indicator’.

Now I’ve been called pedantic in the past. And I won’t argue with that label. Attention to detail is a hobby of mine. And I’m of the opinion that, when it comes to technical specifications, pedantry is a virtue. When you’re trying to pick up concepts like this with data densely packed into a single unsigned 32bit integer exactness is paramount.

There’s nothing worse (in my mind) than having loosely defined terms causing confusion between people new to the spec. As a maintainer it’s hard enough to figure out what noobs mean when they report a bug. If the terms they get from the specification can mean any number of things we’re increasingly the likelihood of miscommunication and frustration.

Conclusion

So my first contribution to the TSS specification was the complete rewriting of the RC section. The values of the constants have remained the same but we now use consistent language for ‘layer indicator’, ‘layer shift’ etc and we’ve removed uses of terms like ‘error level’ and ‘error code’ in favor of ‘layer’ and ‘response’.

If you’re developing software using the SAPI, or using the tpm2.0-tools I highly encourage you to use the tpm2_rc_decode utility. It should make your life a lot easier and the lives of those you may end up communicating with when you’re trying to debug your code.

Finally this tool isn’t perfect. None of the RCs that are generated by the resource manager are handled yet so there’s plenty of room for improvement. If you’ve got the cycles and you’re sufficiently motivated I’d gladly take patches to improve the tool.

TPM2 software stack maintainership

My last post left some work in progress on TPM2 event long handling hanging. Sadly the state of that work hasn’t progressed since. An now for the excuse: I’ve taken over maintainership of the TPM2.0-TSS project.

If you’ve ever taken over a large project with minimal time to plan for the “hand-off” then you’ll understand how that can derail pretty much every other technical project that you may have in progress. So I’ve had to back-burner the Grub / event log stuff for the short term. My focus these days is reverse engineering the internals of the TPM2.0-TSS and generally trying to make sense of the thousands of pages of relevant specs.

On the upside this is a great opportunity to get some much needed documentation and refactoring done for the TSS source tree. As the previous maintainer told me, the documentation for this code is the TPM specifications. This is synonymous with “no documentation” in my mind because while the TPM2 specs describe the functioning of a TPM and the associated software stack should behave it says nothing about how the source code is structured. This is the proverbial up hill battle that comes with taking over a “legacy software” project.

I’ve got a few posts in my backlog of lessons learned from trying to get my brain wrapped around this project. Up till now I’d just been making sure things built well and easily on Linux platforms and so my knowledge of the guts of the infrastructure was limited. Now that I’m neck deep in it I’ll do my best to get some lessons learned up here on the blog and unblock the writers block I’ve been suffering.

The first hurdle I had to overcome was making sense of the TPM2 response codes and their inherent complexity. After that I’m hoping to pick apart some of the unit tests that I’ve been pushing these last few weeks since those are mostly how I’m learning about the structure of the source code.

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.