If you haven’t read previous blog posts from TrenchBoot series, we strongly
encourage to catch up on it. Best way, is to search under
TrenchBoot tag. In this article, we
will take a deeper look into the reworked
extend_all.sh script, along with
util.sh which is sourced by it, to show how it can be used to check if the PCR
values are proper.
New event log entries
You can follow the
from the previous TrenchBoot post, up to the part where the log entries are read
cbmem tool. Remember that
cbmem requires kernel built with
CONFIG_IO_STRICT_DEVMEM disabled or with
iomem=relaxed in the command line!
The following instructions assume TPM2.0, for TPM1.2 change
in the commands. The output format of the event log is also slightly different.
If in doubt, refer to the previous post.
This is the example log:
This particular entries come from development version, the final hashes are different.
You can see that there are new entries. Lets check if those are correct.
This script was used since one of the first posts about TrenchBoot. In this release, it was rewritten to be easier to understand, and also to have option to reuse some of the functions implemented there. This is the source of that file:
Basically, it is a wrapper for functions in
util.sh. It calls function
extend_sha* which takes hashes or files as arguments. Those arguments in the
first case are, in order: initial PCR value after initialization (all zeros),
hash of measured part of LZ, hash of protected mode part of the kernel and path
to the initramfs.
The second branch accounts for the fact that it is possible to embed the initramfs image inside the kernel itself. Utility functions called in both cases will be described later.
We can run this script for our kernel and initramfs and compare the result with
the output of
No surprise here, the value of PCR 17 matches the one calculated by the script. Usually this is enough to prove that the platform is in a more or less known state and there is no reason to check the event log, it is more useful to check it when those values differ, but we will do this anyway.
This is the file where all the heavy lifting takes place. Below is a description
of that code. All
sha1* functions and variables have their
counterparts, not listed below.
Two constants connected with Landing Zone:
SLB_FILE is the file name which can
be overrode during invocation of the script. LZ (or SLB using AMD terminology)
starts with two 16b numbers. The first one is the offset to the entry point, the
second one is the length of measured part. That second number is extracted and
Two constants with initial values of PCRs - all zeroes. Included here to avoid tedious task of repeating and counting zeroes.
This function takes a path to the Linux kernel as an argument.
Historically, Linux was started in real mode (RM, 16 bits), where it could gather all necessary information required to boot using the BIOS interrupt calls. It saved all gathered data in a structure called zero page and jumped into the protected mode (PM). These two modes are two separate pieces of bzImage file.
Nowadays, the zero page is prepared by the bootloader and the kernel is started already in the protected mode. The code from the RM part is no longer used, except for the initial copy of the zero page, because it includes e.g. boot protocol version to which the bootloader must comply.
Another important field of zero page is the size of the RM part. It is specified as the additional number of disk sectors (512 bytes) that must be loaded, not counting the first sector. This size (in bytes) is obtained by the first line. The second line reads the other part of the file (the PM part) and calculates its hash.
Calculates hash of the measured part of the LZ, uses constants defined earlier.
Removes anything that comes after the hash (usually file name), checks its length and transforms the hex string into an escaped format.
Example: ‘01fe23dc45ba…’ is transformed to ‘\x01\xfe\x23\xdc\x45\xba…’
This function does the extend operation. It takes two or more arguments, which
are either hashes or file names, and performs what the PCR extension would do:
concatenates the old value of PCR (first argument) with the new hash (second
argument) and hashes the result. All of the data is expected to be in binary
form, this is why
validate_and_escape_hash transforms the string.
extend_sha* deliberately does not take the file name as the first argument.
There are no cases where the PCR would have a new value written to it, the only
possible way of changing the PCR (other than reset) is to extend it.
Debugging potential issues using DRTM TPM event log
For the sake of argument, lets assume that the current value of PCR 17 is not
what we expected it to be, i.e. not what
We will need the utility functions and variables. It is good to start with sourcing the file, so we won’t have to craft new scripts for every test:
First order of business is to take a look at the event log and check if all of the expected entries are there. If they end at some point, most likely the module that was measured most recently is broken - remember that each module is expected to measure the next one.
If all of the entries appear to be in order, we should check the result of extending the PCR using the values from the event log. Assuming we are using the log from above, we can do this with command:
This can be done in a single line, but splitting that command makes it easier to describe. Line 2 “resets” PCR to all zeroes - if we want to check what would be the PCR value after extending it with new hash, we can put there the current value instead. Lines 3-5 are hashes from the log for PCR 17 only. They must be passed in order, PCR extend operation is not commutative.
While it is good to always take a look at the log, the same can be done without copying the hashes manually:
There are few possible outcomes:
- the result is the same as
extend_all.sh- there were additional extend operations, most likely after the last one (initramfs), but without logging OR the TPM extend operation did not succeeded
- the result is the same as the current PCR value - it means that every measured component was also logged, so it should be easy to pinpoint the modified one
- none of the above - files passed to
extend_all.share different than the ones that were actually run AND not all extend operations were properly logged
For the first one, the debug process using event log ends here, we can’t get anywhere further that way. It may be helpful to check if the PCR value is the same after a reboot or another DRTM invocation. You may also try running:
If the real PCR value is the same as the result of the above script, it may indicate that the CPU was not in the proper state before DRTM. See also troubleshooting.
The other two require comparing logged values with ones calculated directly from files. For the Landing Zone this can be done with one of:
lz_header.bin in the current directory, the second
option lets the user specify another path and name.
To measure the kernel:
Initramfs is treated as a flat file, it is measured as a whole:
This hopefully helps to find out which of the components was modified.
Code for TPM support no longer consists of one big, merged file, it uses the original form of tpmlib instead. It is included as a git submodule, which should make it easier to keep up to date.
This also fixed some issues with extend operation for TPM1.2 - previously it worked only for the first invocation of the function. Code for TPM2.0 was also reworked - while it worked, some of the variables had misleading names, and the buffer management was used in kind of hacky way.
Another change is that the kernel now also extends PCRs with all available hash
algorithms, instead of using only the first one it finds (SHA256 in most cases
for TPM2.0). It can be seen in the event log as additional entries for the same
event, as well as in final PCR values. They now match the ones predicted by
extend_all.sh, both for SHA1 and SHA256.
All of the above fixes, along with the fact that the values from the event log,
extend_all.sh match, assure the correct operation of TPM TIS
interface in the Landing Zone and Linux kernel. This was the requirement we set
for ourselves for the previous month.
We also updated our CI. It should properly test all the changes that are to be merged into the upstream repositories. Test includes building the Linux kernel, GRUB2 and Landing Zone, as well as running them on test platforms and comparing the PCR 17 values with the expected ones.
This release made changes introduced in the previous one actually useful. As shown, it is now possible to check hash of each measured component separately, instead of relying on single binary output (PCR is either valid or not). This additional information makes it possible to narrow down the fault search area, we do not need to resort to shotgun debugging anymore. While this may seem trivial for this simple case where only LZ, kernel and initrd are measured, this will greatly help with more complicated cases with many more measured modules.
If you think we can help in improving the security of your firmware or you
looking for someone who can boost your product by leveraging advanced features
of used hardware platform, feel free to
book a call with us or
drop us email to
contact<at>3mdeb<dot>com. If you are interested in similar
content feel free to sign up for our newsletter