A TPM 2.0 Platform Configuration Register (PCR) is a hash register that can only be extended: its new value is the hash of the old value joined to a new digest, so nothing can set it directly. PCRs 0 to 7 record firmware, boot loader and Secure Boot state, 8 to 15 belong to the OS, and the boot event log lists every extend.
Replaying the log must reproduce the PCR values the TPM reports; that is how you know the log is honest. The rest of this guide goes one level deeper than our Secure Boot, trusted boot and measured boot explainer: what each register holds according to the specification, how Linux and Windows use the OS range, the exact commands to read PCRs and the event log on both systems, how BitLocker and systemd-cryptenroll seal disk keys to PCRs, why a signed PCR policy survives updates that break a raw one, what a remote attestation quote proves, the known weaknesses, and a runbook for firmware updates that does not end with users typing 48 digit recovery keys. Every register assignment and command comes from the specification, man page or vendor document linked under Sources.
Key takeaways
- A PCR can only be reset (at platform reset, or for a few registers by privileged localities) or extended with
PCRnew = H(PCRold || digest). The final value depends on every digest and on their order, which is why it works as a tamper evident summary. - The TCG PC Client Platform Firmware Profile (version 1.06, revision 52, December 2023) assigns PCR 0 to core firmware code, 1 to platform configuration, 2 and 3 to pluggable code and its data, 4 to boot manager code, 5 to boot manager configuration and the GPT, 6 to manufacturer specific measurements, 7 to Secure Boot policy, 8 to 15 to the static OS, 16 to debug and 23 to application support.
- The PCR value alone tells you almost nothing. The event log tells you what was measured; the PCR proves the log was not edited. You need both.
- On Linux,
tpm2_pcrreadreads the registers andtpm2_eventlogparses/sys/kernel/security/tpm0/binary_bios_measurements. On Windows,Get-Tpm,tpmtool getdeviceinformationandtpmtool gatherlogsare the documented starting points. - BitLocker on native UEFI seals to PCRs 7 and 11 when Secure Boot based validation is available, otherwise to 0, 2, 4 and 11. Firmware updates trigger recovery when a sealed PCR changes and BitLocker was not suspended first.
- On Linux,
systemd-cryptenroll --tpm2-pcrs=seals to today's literal values, which break on every update.--tpm2-public-key=seals to "any PCR 11 value signed by this key", which survives kernel updates. systemd's own docs recommend PCR 7 (and 14 with shim) over 0 and 2. - A remote attestation quote is a TPM signature over selected PCR values plus a verifier supplied nonce. It proves what was measured at boot, on a real TPM, recently. It does not prove the system is still in that state.
- Discrete TPMs talk to the CPU over an exposed bus. TPM only unlock without a PIN can leak the volume key to a physical attacker with an interposer; a PIN, encrypted TPM sessions and a firmware TPM change that picture.
What is a PCR, and how does extend work?
A PCR is a fixed size register inside the TPM. A TPM 2.0 has banks of PCRs, one bank per hash algorithm it supports (SHA-1 and SHA-256 are the most common), and each bank has the same number of registers. The UAPI Group's Linux PCR registry notes that typical standards compliant TPMs have only 24, which is why every component that wants one has to negotiate.
The TPM 2.0 Library specification (version 185, Part 1, dated 12 March 2026) puts the rule in one line: other than reset, "the only way to change a PCR value is to Extend it." The extend operation is:
PCR_new = H_alg( PCR_old || digest )
H_alg is the bank's hash function and || is concatenation. When software calls TPM2_PCR_Extend(), it passes one digest per algorithm, and the TPM extends each bank with the matching digest. If no digest is supplied for a bank, that bank does not change.
Why can a PCR never be set directly?
Because the TPM has no command that writes an arbitrary value into a PCR. There is extend, and there is reset. Reset only happens:
- At TPM Reset or TPM Restart, when every PCR returns to its default initial condition (all zeros or all ones, and for PCR 0 possibly a locality indicator).
- Through
TPM2_PCR_Reset(), only for registers whose platform specific attributes allow it, at the right locality and with authorization. - Through a dynamic root of trust sequence for the dynamic registers.
The PC Client profile says PCRs 0 to 15 are associated with locality 0 and are resettable only on host platform reset. So the OS cannot clear PCR 7 and start again. If it could, a compromised boot loader could wipe the evidence of itself and replay a clean history.
The second property comes from the hash. Given a target value, finding a digest that extends the current PCR into it means breaking preimage resistance of SHA-256. So the only practical way to reach a particular PCR value is to extend exactly the same digests, in exactly the same order, starting from the same reset value. That is what makes a PCR a commitment to a history.
Worked example 1: one extend from zero
The simplest real event in every PC boot is the separator. The PC Client profile requires firmware to extend the digest of the 4 byte value 00000000h (or FFFFFFFFh on error paths) into PCRs 0 to 7 before the first Ready to Boot call, and to log an EV_SEPARATOR event for each.
Start with SHA-256 PCR 6 at 32 bytes of zeros, assume nothing else was measured, and extend the separator:
| Step | Value (SHA-256, hex) |
|---|---|
| Initial PCR | 0000000000000000000000000000000000000000000000000000000000000000 |
| digest = SHA-256(00 00 00 00) | df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119 |
| PCR = SHA-256(initial || digest) | 3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969 |
If you run tpm2_pcrread sha256:6 on a machine whose firmware only measures the separator into PCR 6, this is the value you will see. On machines with manufacturer specific events in PCR 6 it will differ, which is the point: the value is unique to the history.
Worked example 2: order matters, and one changed byte changes everything
Take two made up components, bootloader-v1 and kernel-6.12, and extend their SHA-256 digests into a zeroed PCR:
| History | Final PCR (first 16 hex chars) |
|---|---|
| extend(bootloader-v1), then extend(kernel-6.12) | 428f4bbe2a8b1729... |
| extend(kernel-6.12), then extend(bootloader-v1) | 3822f06281419db4... |
| extend(bootloader-v2), then extend(kernel-6.12) | 1ae45d3804ff23fa... |
Same two components in a different order give an unrelated value. Changing one character of the boot loader name gives another unrelated value. This is exactly why a firmware or boot loader update breaks any secret sealed to the literal value of PCR 0 or 4: the new binary has a new digest, so the register lands somewhere new, and the TPM has no way to know the change was authorized.
You can reproduce these numbers in a few lines of Python:
import hashlib
def extend(pcr: bytes, digest: bytes) -> bytes:
return hashlib.sha256(pcr + digest).digest()
pcr = bytes(32) # reset value: 32 zero bytes
pcr = extend(pcr, hashlib.sha256(b"bootloader-v1").digest())
pcr = extend(pcr, hashlib.sha256(b"kernel-6.12").digest())
print(pcr.hex()) # 428f4bbe2a8b1729...
Forward looking and backward looking uses
The UAPI registry draws a distinction worth keeping in your head for the rest of this post. PCRs serve two purposes:
- Policy (forward looking). A secret is sealed so the TPM releases it only if certain PCRs hold certain values, or any values for which a signature by a known key exists. For this, a PCR must only contain measurements you can predict before boot.
- Reasoning about the past (backward looking). A verifier reads the event log, and the PCRs prove the log is complete and unedited. For this, it does not matter much what was measured, as long as every measurement is in the log.
Many problems in the field come from using a backward looking PCR (full of unpredictable data) for a forward looking policy.
What does each PCR measure?
PCRs 0 to 7 under the TCG PC Client profile
The authoritative assignment is Table 1 of the TCG PC Client Platform Firmware Profile Specification, version 1.06 revision 52 (published 4 December 2023). The profile says the first seven PCRs are for the pre OS environment, with PCR 4 described as transition code that is partly pre OS and partly OS resident, and that every extend gets a matching entry in the TCG event log "so a challenger can see how the final PCR digests were built."
| PCR | TCG PC Client usage | What it usually contains in practice | Changes when |
|---|---|---|---|
| 0 | SRTM, BIOS, host platform extensions, embedded option ROMs and PI drivers | Core firmware code, often an EV_S_CRTM_VERSION and EV_EFI_PLATFORM_FIRMWARE_BLOB style events | Firmware update (systemd's table: "changes on firmware updates") |
| 1 | Host platform configuration | Firmware configuration, boot variables such as BootOrder (EV_EFI_VARIABLE_BOOT), often SMBIOS data with serial and model numbers | Many BIOS setting changes; CPU, RAM or basic hardware replacement |
| 2 | UEFI driver and application code | Option ROMs on add in cards, drivers loaded from devices | Adding, removing or updating a GPU, NIC or storage controller with its own option ROM |
| 3 | UEFI driver and application configuration and data | Data for the pluggable code in PCR 2 | Same triggers as PCR 2, plus their settings |
| 4 | UEFI boot manager code (usually the MBR) and boot attempts | The boot loader binaries and PE images the boot manager or shim loads; the "Calling EFI Application from Boot Option" action | Boot loader update, kernel update when booted as a PE binary, different boot path |
| 5 | Boot manager configuration and data, and the GPT partition table | GPT header and entries (EV_EFI_GPT_EVENT); systemd-boot measures loader.conf here | Partitions added, modified or removed |
| 6 | Host platform manufacturer specific | Vendor defined; Microsoft's UEFI list calls it "Resume from S4 and S5 power state events" | Vendor specific |
| 7 | Secure Boot policy | SecureBoot, PK, KEK, db, dbx (and dbt, dbr, AuditMode, DeployedMode where applicable), plus the db entry that authorized each boot path image | Secure Boot turned on or off, or any Secure Boot database update, including dbx revocation updates |
PCR 7 deserves the detail because it is the most useful register for policy. The profile says firmware MUST measure the contents of the SecureBoot, PK and KEK variables and the db and dbx databases into PCR 7 as EV_EFI_VARIABLE_DRIVER_CONFIG events, and MUST also record, as EV_EFI_VARIABLE_AUTHORITY, the individual db entry that was used to validate each UEFI driver or boot application in the boot path. So PCR 7 does not say "this exact kernel booted". It says "Secure Boot was in this state, with these keys, and these certificates vouched for what ran." That is why it survives most OS updates: a new boot loader signed by the same certificate produces the same authority event.
Two more points from the profile matter when you read a log:
- The separator. Before the first Ready to Boot call, firmware extends the separator digest into each of PCRs 0 to 7. Everything before the separator in a PCR was measured by firmware; everything after was measured by OS loaders. systemd adds its own
os-separatorbarrier into PCRs 0 to 7, 9 and 12 to 14 when userspace first sees the TPM, so firmware and OS measurements are clearly isolated even on firmware that does not do it properly. - Errors. If a measurement fails, firmware extends the digest of
00000001hinto PCRs 0 to 7. If PCR 0 cannot be measured, the profile says none of the other SRTM PCR values can be trusted.
PCRs 16 to 23
The profile defines PCR 16 as Debug and PCR 23 as Application Support, and says PCR indexes not in its table are not allowed by that specification. PCRs 17 to 22 are, in the profile's words, "beyond the scope of this specification and the purview of the Dynamic OS". They belong to dynamic root of trust (DRTM) launches, where trusted hardware at locality 4 can reset PCR 17 and measure a late launched environment. Most desktop disk encryption setups never touch them.
What Linux uses PCRs 8 to 15 for
The profile reserves 8 to 15 for the static OS. On Linux, there is no single owner; the UAPI Group's Linux TPM PCR Registry (UAPI.7) records which component uses which register to avoid collisions. It is informational, but systemd's documentation points to it as the reference, and systemd's own table matches it.
| PCR | Linux user (per UAPI.7 and systemd-cryptenroll) | What is measured | Log |
|---|---|---|---|
| 8 | GRUB | GRUB commands and the kernel command line | UEFI TPM event log |
| 9 | GRUB; the Linux kernel; systemd | All files GRUB reads (including the kernel image); all initrds passed to the kernel through the LOAD_FILE2 protocol; NvPCR setup measurements | UEFI event log; /run/log/systemd/tpm2-measure.log |
| 10 | IMA | Protection of the Integrity Measurement Architecture runtime log | IMA event log |
| 11 | systemd-stub; systemd-pcrphase | All static sections of a unified kernel image (kernel, embedded initrd, os-release, command line in the UKI and more); boot phase strings such as enter-initrd, leave-initrd, sysinit, ready, shutdown, final | UEFI event log; systemd measure log |
| 12 | systemd-boot; systemd-stub | The kernel command line when it overrides the one in the UKI, system credentials, configuration extensions and addons; a non zero UKI profile number | UEFI event log |
| 13 | systemd-stub | System extension images passed to the initrd | UEFI event log |
| 14 | shim | MOK (Machine Owner Key) certificates and hashes | UEFI event log |
| 15 | systemd-cryptsetup; systemd-pcrmachine; systemd-pcrfs | Optionally the LUKS volume key, the machine ID, and the mount points, UUIDs and labels of the root and /var file systems | systemd measure log |
The registry also notes that shim measures most of its non MOK certificates and SBAT data into PCR 7, and that if the Linux kernel is booted as a UEFI PE binary it is measured into PCR 4 as well.
The most important line in that table is PCR 11. systemd-stub measures the static parts of the UKI there, and the registry stresses that, unlike PCR 4, "those values should be easy to pre-calculate." That predictability is what makes signed PCR policies possible, as covered below.
What Windows uses PCRs 8 to 15 for
Microsoft's BitLocker policy reference lists PCR meanings for two firmware modes. For native UEFI firmware:
| PCR | Microsoft description (native UEFI) |
|---|---|
| 0 | Core System Firmware executable code |
| 1 | Core System Firmware data |
| 2 | Extended or pluggable executable code |
| 3 | Extended or pluggable firmware data |
| 4 | Boot Manager |
| 5 | GPT/Partition Table |
| 6 | Resume from S4 and S5 Power State Events |
| 7 | Secure Boot State |
| 8, 9, 10 | Initialized to 0 with no extends (reserved for future use) |
| 11 | BitLocker access control |
| 12 | Data events and highly volatile events |
| 13 | Boot Module Details |
| 14 | Boot Authorities |
| 15 to 23 | Reserved for future use |
For legacy BIOS or UEFI with the Compatibility Support Module enabled, the same page lists a different, older map: PCR 4 for MBR code, 5 for the MBR partition table, 8 for the NTFS boot sector, 9 for the NTFS boot block, 10 for the boot manager and 11 for BitLocker access control. If you manage a mixed fleet, that difference is the reason Group Policy has two separate "platform validation profile" settings.
Note the overlap with Linux: both operating systems use PCR 14, for different things. That is fine, because only one OS boots at a time, but it is a reminder that "PCR 14" means nothing until you know what booted.
How do you read PCR values and the event log on Linux?
Step 1: confirm there is a TPM 2.0 and find the device
# Kernel TPM devices: tpm0 is the raw device, tpmrm0 the resource managed one
ls -l /dev/tpm*
# systemd can enumerate suitable TPM2 devices
systemd-cryptenroll --tpm2-device=list
Use /dev/tpmrm0 (the in kernel resource manager) for tools unless you have a reason not to. The systemd-cryptenroll man page uses it as its example device.
Step 2: read the PCRs
The tpm2-tools tpm2_pcrread command prints all banks and registers in YAML when run without arguments. You can narrow it with a selection list:
# All banks, all PCRs
sudo tpm2_pcrread
# Just the SHA-256 bank, firmware and policy relevant registers
sudo tpm2_pcrread sha256:0,1,2,3,4,5,7,11,14
# Save the binary values for later comparison
sudo tpm2_pcrread -o pcrs-before-update.bin sha256:0,2,4,7,11
The docs note that on most TPMs the tool can dump up to 24 PCRs at once, the size of a bank. Two quick things to check in the output:
- A bank that is all zeros or all ones in PCRs 0 to 7. If the SHA-256 bank of PCR 0 is all zeros after boot, firmware is not measuring into that bank (some older firmware only measured SHA-1). Do not seal anything to a bank firmware does not populate.
- PCR 11 on a non UKI system. If you do not boot a UKI through systemd-stub, PCR 11 will usually stay at zeros (or contain only phase strings), and a policy on it means nothing.
Step 3: read the firmware event log
The kernel exposes the firmware's log in binary form. systemd-pcrlock's man page names the path: /sys/kernel/security/tpm0/binary_bios_measurements. tpm2_eventlog parses it; its man page says the format is the one documented in the TCG PC Client Platform Firmware Profile.
sudo tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements > eventlog.yaml
# Newer output format
sudo tpm2_eventlog --eventlog-version=2 /sys/kernel/security/tpm0/binary_bios_measurements > eventlog-v2.yaml
# Look at only the Secure Boot related events
grep -n -A6 "EV_EFI_VARIABLE_DRIVER_CONFIG\|EV_EFI_VARIABLE_AUTHORITY" eventlog.yaml | less
The kernel's TPM event log documentation explains a quirk you may notice: the Linux EFI stub copies the log before ExitBootServices(), and events generated after that are mirrored in a separate "final events" table that the kernel concatenates. If your log looks short by a few events near the end of firmware boot, that is where they live.
Step 4: read the log like a reviewer
Each event in a TCG 2.0 log carries a PCR index, an event type, a digest per enabled bank and event data. Walk it in this order:
- The first event is an
EV_NO_ACTION"Spec ID" event that says which hash algorithms the log carries. It does not extend any PCR. Next you may see a Startup Locality event, which also does not extend anything but tells you PCR 0's starting value (locality 3 or 4 changes it, per the profile). - PCR 0 events: firmware version and firmware blobs. A firmware update should change these and nothing else in this PCR.
- PCR 1 events: configuration, boot order variables (
BootOrder,Boot0000and so on), SMBIOS. These are the reason PCR 1 is a poor policy choice: changing boot order changes it. - PCR 2 events: one per option ROM or driver loaded from a device. A new network card shows up here.
- PCR 7 events: the
SecureBoot,PK,KEK,db,dbxvariable measurements, then oneEV_EFI_VARIABLE_AUTHORITYper distinct certificate that authorized something in the boot path. - Separators for PCRs 0 to 7.
- PCR 4 events:
EV_EFI_BOOT_SERVICES_APPLICATIONfor shim, the boot loader, and possibly the kernel. - PCRs 8, 9, 12, 13, 14 events from GRUB, shim or systemd-stub, depending on your boot chain.
Step 5: check that the log matches the PCRs
The kernel documentation quotes the TCG profile on why this matters: the log is "not trusted on their own", and the PCR contents are what validate it. Replaying means: start each PCR at its reset value and apply extend for every event in that PCR, in log order, using the digest for the bank you are checking. If the replayed value equals what tpm2_pcrread reports, the log is complete for that PCR.
On systemd systems, systemd-pcrlock log does the work. Its man page says it reads the combined firmware and userspace logs, validates them, matches them against the current PCR values and prints both in a table; cel writes the combined log as TCG Canonical Event Log JSON. Note the man page marks systemd-pcrlock as experimental.
sudo /usr/lib/systemd/systemd-pcrlock log
sudo /usr/lib/systemd/systemd-pcrlock cel > boot-log.cel.json
# PCR 11 on a UKI system: what is there now, as systemd-measure sees it
sudo /usr/lib/systemd/systemd-measure status
If you want to do it yourself, the logic is the Python extend function above in a loop over (pcr_index, sha256_digest) pairs from the parsed log. Skip EV_NO_ACTION events, which are logged but never extended, and start PCR 0 from the locality value if the log has a Startup Locality event of 3 or 4.
Step 6: the runtime logs
Two more logs exist beyond the firmware one:
- systemd's userspace log at
/run/log/systemd/tpm2-measure.log, which records what systemd-pcrphase, systemd-pcrmachine, systemd-pcrfs and cryptsetup extended into PCRs 9, 11 and 15. - The IMA log, if IMA is enabled. IMA extends PCR 10 for each measured file, and its default template is
ima-ng. The PCR 10 value validates that runtime log the same way PCRs 0 to 7 validate the firmware log.
How do you read PCRs and the event log on Windows?
Windows exposes less of this directly, and Microsoft's docs are thinner on the log format. The documented tools are:
# Is there a TPM, is it ready, what firmware version is it?
Get-Tpm
# Basic TPM information (run from an elevated prompt)
tpmtool getdeviceinformation
# Collect TPM logs into a folder
tpmtool gatherlogs C:\Temp\tpmlogs
Get-Tpm returns fields such as TpmPresent, TpmReady and ManufacturerVersion. tpmtool gatherlogs writes, according to Microsoft's reference page, TpmEvents.evtx, TpmInformation.txt and the measured boot logs SRTMBoot.dat, SRTMResume.dat, DRTMBoot.dat and DRTMResume.dat. SRTM is the static root of trust boot you get on every PC; the DRTM files only appear when a dynamic launch was used. The .evtx file opens in Event Viewer and is where TPM provisioning, lockout and clear events show up; that is what people usually mean by "TBS logs" (TBS is the Windows TPM Base Services layer).
To see which BitLocker protectors exist on a volume:
manage-bde -protectors -get C:
Get-BitLockerVolume -MountPoint C: | Select-Object -ExpandProperty KeyProtector
The output lists the TPM protector and any PIN, startup key or recovery password protectors. On many builds the TPM protector's details also show which PCRs it is sealed to; if yours does not, the policy settings in the next section tell you which profile applies.
Microsoft does not document the binary format of SRTMBoot.dat on the tpmtool page. These are TCG measured boot logs, and an evaluator such as an attestation service parses them, but if you try a Linux parser on them treat the result as informative, not authoritative.
How does BitLocker choose its PCR profile?
BitLocker seals its volume master key to a set of PCRs, called the platform validation profile. Microsoft's BitLocker configuration reference describes how the default is chosen:
- Native UEFI, Secure Boot validation available: the default profile is PCR 7 (Secure Boot state) and PCR 11 (BitLocker access control). Microsoft calls this using Secure Boot for integrity validation.
- Native UEFI without PCR 7 support: the default is PCRs 0, 2, 4 and 11.
- Legacy BIOS or UEFI with CSM: the default is PCRs 0, 2, 4, 8, 9, 10 and 11.
The Group Policy setting "Allow Secure Boot for integrity validation" controls the first choice. If it is enabled or not configured, BitLocker uses Secure Boot when the platform supports it. If you configure a custom UEFI profile and leave PCR 7 out, BitLocker cannot use Secure Boot for platform or Boot Configuration Data validation.
The BitLocker FAQ lists what prevents binding to PCR 7: a non Windows OS booted before Windows, or Secure Boot not being available because it is disabled or unsupported. Microsoft also notes that PCR 7 measurements are a mandatory logo requirement for Modern Standby systems.
What PCR 11 means to BitLocker
Microsoft's tables describe Windows PCR 11 only as "BitLocker access control", and it is in every default profile. Microsoft does not spell out on these pages exactly what Windows extends into it, so treat it as BitLocker's own register and leave it in the profile. The idea of a register owned by the component that needs the secret is common: on Linux, systemd-pcrphase extends phase strings such as leave-initrd into PCR 11, so a slot bound to the initrd phase stops working once boot moves past it.
Why do firmware updates trigger BitLocker recovery?
Because a firmware update changes PCR 0 (core firmware code) and often PCR 2 or 7. If BitLocker is sealed to a PCR that changes, the TPM refuses to unseal and the device boots to the recovery screen. Microsoft's recovery overview lists "upgrading critical early startup components, such as a BIOS or UEFI firmware upgrade" and "modifying the PCRs used by the TPM validation profile" among the causes, alongside changes to the boot manager, the NTFS partition table, clearing the TPM and moving the drive to another computer.
With the PCR 7 and 11 profile, a firmware update by itself often does not change PCR 7. What does change it is a Secure Boot database update: the FAQ says users must suspend BitLocker for "manual or non-Microsoft updates to secure boot databases (only if BitLocker uses Secure Boot for integrity validation)". The same FAQ says no user action is needed for updates from Microsoft, and that suspension is needed for some TPM firmware updates that clear the TPM outside the Windows API and for non-Microsoft applications that modify UEFI or BIOS configuration.
The fix is to suspend before the change:
# Suspend for one reboot (the default when -RebootCount is omitted)
Suspend-BitLocker -MountPoint "C:" -RebootCount 1
# Suspend until you resume it by hand (for multi reboot firmware flows)
Suspend-BitLocker -MountPoint "C:" -RebootCount 0
# ... apply the firmware update, reboot as many times as the vendor tool needs ...
Resume-BitLocker -MountPoint "C:"
Microsoft's FAQ explains what suspension does: the drive stays encrypted, but the volume master key is protected with a clear key stored unprotected on disk. On resume, BitLocker reseals the key to the new PCR values. That is a real exposure window, so keep it short and keep the device in your physical control while suspended.
How does systemd-cryptenroll bind LUKS to PCRs?
systemd-cryptenroll (TPM2 support since systemd version 248) adds a TPM2 key slot to a LUKS2 volume. The TPM seals a secret that unlocks the slot, under a policy. You choose the policy with flags, and the choice decides whether your next kernel or firmware update is a non event or a lockout.
Before any enrollment: check the device and back up the header
Warning. The commands in this section modify the LUKS2 header of an encrypted disk. Enrolling or wiping a slot on the wrong device can make data unrecoverable. Before each block, confirm the device path, keep a working passphrase or recovery key slot, and back up the header to a different disk.
# 1. Identify the right partition: look for TYPE crypto_LUKS and the size you expect
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
# 2. Confirm it is LUKS2 and see the existing key slots and tokens
sudo cryptsetup luksDump /dev/nvme0n1p3
# 3. Back up the header to removable media, not the same disk
sudo cryptsetup luksHeaderBackup /dev/nvme0n1p3 \
--header-backup-file /media/usb/nvme0n1p3-luks-header.img
# 4. Add a recovery key slot if you do not have one (prints a key: store it offline)
sudo systemd-cryptenroll --recovery-key /dev/nvme0n1p3
Replace /dev/nvme0n1p3 everywhere with the path you confirmed in step 1.
Option A: literal PCR values
# Re-check the device path before running
lsblk -o NAME,SIZE,FSTYPE /dev/nvme0n1p3
sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/nvme0n1p3
--tpm2-pcrs= takes names or indexes from 0 to 23, optionally with a bank (7:sha256) and even a literal digest. The default, if you omit the option, is no PCRs at all, which means any boot of this machine with this TPM can unseal. The man page's examples include boot-loader-code+platform-config+boot-loader-config for PCRs 4, 1 and 5.
Then add the volume to /etc/crypttab, using the man page's form:
myvolume /dev/nvme0n1p3 none tpm2-device=auto
Which PCRs should you pick?
systemd-cryptenroll's own guidance is unusually direct: encrypted volumes would generally be bound to some combination of PCRs 7, 11 and 14 (if shim and MOK are used), and it is "typically not advisable" to use PCRs 0 and 2, because the code they cover is already covered indirectly through the certificates in PCR 7. Validation through certificates is less brittle, since "the measurements will change on every update, but signatures should remain unchanged."
Option B: signed PCR policy on PCR 11 with unified kernel images
A raw policy on PCR 11 would break on every kernel update, because the kernel is measured into PCR 11. The signed policy (systemd 252 and later) fixes that. Instead of "PCR 11 must equal X", the TPM policy says "PCR 11 must equal any value for which the holder of this private key has produced a signature". The TPM enforces this with the TPM2_PolicyAuthorize assertion.
The flow:
- Generate a PCR signing key pair once, and keep the private key with your build or release process, not on the laptop.
- When building each UKI,
ukifycallssystemd-measureto precompute the PCR 11 values that systemd-stub will produce when the UKI boots (systemd-measure starts from a zeroed PCR 11 and replays exactly what systemd-stub does), signs them, and embeds the signatures in the UKI's.pcrsigsection and the public key in.pcrpkey. systemd-stub does not measure.pcrsiginto PCR 11, because it holds signatures over the result. - Enroll the LUKS slot against the public key with
--tpm2-public-key=. With no--tpm2-public-key-pcrs=, it defaults to PCR 11. - At boot, systemd-stub passes the signatures to the OS, and systemd-cryptsetup presents the matching signature to the TPM. If the booted UKI was signed by your key, the policy is satisfied, whatever the exact kernel version.
# Generate keys (the output files must not exist yet)
ukify genkey \
--pcr-private-key=tpm2-pcr-private-key.pem \
--pcr-public-key=tpm2-pcr-public-key.pem
# Build a UKI with signed PCR 11 predictions
ukify build \
--linux=/boot/vmlinuz-6.12.0 \
--initrd=/boot/initrd.img-6.12.0 \
--pcr-private-key=tpm2-pcr-private-key.pem \
--pcr-public-key=tpm2-pcr-public-key.pem \
--output=/efi/EFI/Linux/linux-6.12.0.efi
# Inspect what went into the UKI
ukify inspect /efi/EFI/Linux/linux-6.12.0.efi
# Re-check the device path before enrolling
lsblk -o NAME,SIZE,FSTYPE /dev/nvme0n1p3
# Bind to "any PCR 11 signed by this key" plus Secure Boot state in PCR 7
sudo cp tpm2-pcr-public-key.pem /etc/systemd/tpm2-pcr-public-key.pem
sudo systemd-cryptenroll --tpm2-device=auto \
--tpm2-public-key=/etc/systemd/tpm2-pcr-public-key.pem \
--tpm2-public-key-pcrs=11 \
--tpm2-pcrs=7 \
/dev/nvme0n1p3
The man page notes a safety net: if a signature file (tpm2-pcr-signature.json) is supplied or found, systemd-cryptenroll checks that the current PCR state can actually be unlocked with it before writing the slot, and refuses to enroll if it cannot.
Two refinements are worth knowing:
- Phases. ukify can sign separate predictions per boot phase. Because systemd-pcrphase extends phase strings into PCR 11 (
enter-initrd,leave-initrd,sysinit,ready), you can sign only theenter-initrdphase, and the key becomes unusable once the system leaves the initrd. systemd 262 adds--tpm2-public-key-policyref=so a slot is bound only to signatures made with a matching policy reference, for example one used only for the initrd phase. - A PIN.
--tpm2-with-pin=yesadds a PIN to the TPM policy. That is the single most effective mitigation against the bus attacks discussed below.
Option C: systemd-pcrlock predicted policies
systemd-pcrlock (systemd 255 and later, marked experimental) takes another route. It reads the firmware and userspace event logs, matches records against component definition files (*.pcrlock), predicts the PCR values for future boots including alternative variants (two kernel versions, two boot loader versions), and stores the resulting policy in a TPM NV index. LUKS is then bound with systemd-cryptenroll --tpm2-pcrlock=. Because the policy lives in NV memory, it can be updated without re-enrolling the disk. It needs a TPM implementing PolicyAuthorizeNV, which means TPM 2.0 specification version 1.38 or newer.
For firmware updates it offers unlock-firmware-code and unlock-firmware-config: run them before an update so the next policy stops pinning PCRs 0 and 2 (or 1 and 3), then lock again after the update has booted.
Why is a signed policy better than raw PCR values?
| Approach | Policy says | Survives kernel update | Survives firmware update | Survives Secure Boot db or dbx update | Who must act on update |
|---|---|---|---|---|---|
--tpm2-pcrs=0+2+4+7 | These four exact values | No | No | No | Every user, every time |
--tpm2-pcrs=7 | This exact Secure Boot state | Yes, if same signing certificate | Usually yes | No | Re-enroll after db or dbx changes |
--tpm2-pcrs=7+14 with shim | Secure Boot state and MOK list | Yes, if same signing certificate | Usually yes | No | Re-enroll after db, dbx or MOK changes |
--tpm2-public-key-pcrs=11 (signed) | Any PCR 11 signed by your key | Yes | Yes (PCR 11 does not cover firmware) | Yes, unless also bound to PCR 7 | Release process signs each UKI |
| Signed PCR 11 plus raw PCR 7 | Signed kernel and this Secure Boot state | Yes | Usually yes | No | Re-enroll after db or dbx changes |
--tpm2-pcrlock= | Any predicted combination of variants | Yes, if the new variant is registered | Yes, if unlocked before update | Yes, if re-predicted | Admin runs predict or make-policy |
| No PCRs | Any boot of this TPM | Yes | Yes | Yes | Nobody, but offers little protection |
The core reason is that a raw value describes an outcome, while a signature describes an authority. You know in advance who is allowed to ship your kernels; you cannot know in advance the hash of a kernel you have not built yet. The same reasoning is behind BitLocker's preference for PCR 7: Secure Boot certificates change rarely, binaries change constantly.
What is remote attestation, and what does a quote prove?
Remote attestation is a verifier asking a machine to prove its boot state. Microsoft's description of Measured Boot puts it plainly: using an attestation identity key, "the TPM can generate and cryptographically sign a statement (or quote) of the current measurements." The open source Keylime project, which started in MIT Lincoln Laboratory, describes itself as a TPM based remote boot attestation and runtime integrity measurement solution built on the same primitives.
The exchange, using tpm2-tools to show the pieces:
# On the device: create an endorsement key and an attestation key
tpm2_createek -c 0x81010001 -G rsa -u ekpub.pem -f pem
tpm2_createak -C 0x81010001 -c ak.ctx -G rsa -s rsassa -g sha256 \
-u akpub.pem -f pem -n ak.name
# The verifier sends a fresh random nonce; the device quotes PCRs with it
tpm2_quote -c ak.ctx -l sha256:0,2,4,7 -q "$NONCE_HEX" \
-m quote.msg -s quote.sig -o quote.pcrs -g sha256
# On the verifier: check the signature, nonce and PCR values
tpm2_checkquote -u akpub.pem -m quote.msg -s quote.sig -f quote.pcrs \
-g sha256 -q "$NONCE_HEX"
(This follows the example in the tpm2_checkquote man page with a different PCR selection.)
What a quote proves
- The values are from a TPM. The signature is by an attestation key that lives in the TPM. Proving the AK is in a genuine TPM is a separate step: the verifier checks the endorsement key certificate from the manufacturer and the AK's binding to it (Microsoft describes an identity CA doing exactly this for Windows Hello for Business).
- The values are fresh. The nonce (the
-qqualification data, which the tpm2-tools docs say is "typically used to add a nonce against replay attacks") is inside the signed structure, so an old quote cannot be replayed. - The selected PCRs had those values at that moment. The TPM 2.0 Library spec notes that
TPM2_Quote()signs a digest of the selected PCRs, and the values must be retrieved separately, which is whytpm2_quote -owrites them out.
What a quote does not prove
- What the values mean. For that, the device sends its event log, the verifier replays it to reproduce the quoted digest, and then evaluates each event against policy: expected firmware versions, Secure Boot on, expected db, no unexpected option ROMs.
- That the system is still in that state. A quote describes boot state. Something that happens after boot and is not measured (most of runtime, unless IMA or similar measures it into PCR 10) is invisible.
- That nothing malicious ran. It proves what was measured. If the first code to run is compromised in a way that measures a fake value, the log will be consistent and wrong. That is why the profile cares about the core root of trust being immutable or measured by hardware.
Remote attestation vs sealing
| Sealing (BitLocker, systemd-cryptenroll) | Remote attestation (quote) | |
|---|---|---|
| Who decides | The TPM itself, at unseal time | A remote verifier |
| Needs the event log | No (policy is on PCR values or signatures) | Yes, to interpret values |
| Output | A released key, or a refusal | A signed statement plus a log |
| Typical use | Disk unlock without a password | Network access, cloud workload admission, fleet health |
| Failure mode | Recovery prompt | Device quarantined or denied |
What are the known weaknesses?
A PCR protects integrity of the measurement record. It does not protect a secret on its way out of the TPM, and it is only as good as the code doing the measuring. The issues below are described conceptually, with the fix and the lesson.
Timeline
| Date | Event | Source |
|---|---|---|
| December 2011 | NIST publishes the SP 800-155 initial public draft, BIOS Integrity Measurement Guidelines, on measuring and reporting firmware integrity | NIST CSRC |
| 20 January 2012 | Public comment period on the SP 800-155 draft closes | NIST CSRC |
| August 2018 | "A Bad Dream" at USENIX Security 18 shows PCR reset and forgery attacks around sleep states: one a TPM 2.0 static root of trust design flaw, one an implementation flaw in tboot | USENIX |
| 7 May 2021 | TCG PC Client Platform Firmware Profile 1.05 revision 23 | TCG |
| 4 December 2023 | TCG PC Client Platform Firmware Profile 1.06 revision 52, the current PCR assignment reference | TCG |
| 25 October 2024 | NIST ceases development of SP 800-155; its technical content had gone into TCG's firmware integrity measurement work, and NIST SP 1800-34 covers related guidance | NIST CSRC |
| 12 March 2026 | TPM 2.0 Library specification version 185 | TCG |
The Linux tooling evolved by release rather than by date. The systemd man pages record when each piece arrived:
| systemd version | What it added for PCR policies |
|---|---|
| 248 | systemd-cryptenroll --tpm2-device= and --tpm2-pcrs= |
| 252 | Signed PCR policies (--tpm2-public-key=) and systemd-measure |
| 255 | systemd-pcrlock (experimental) and --tpm2-device-key= |
| 262 | --tpm2-public-key-policyref= for phase specific signed policies |
Comparison of weaknesses
| Weakness | Mechanism (conceptual) | What it breaks | Mitigation |
|---|---|---|---|
| Passive bus sniffing on a discrete TPM | The TPM sits on a low bandwidth bus on the board. With TPM only unlock, the unsealed key crosses that bus in the clear, and a probe or interposer can record it | Disk encryption confidentiality against a physical attacker | TPM plus PIN; encrypted TPM sessions so secrets cross the bus encrypted; firmware TPMs with no external bus; chassis and port hardening |
| Active interposer | A device on the bus substitutes PCR extend commands so the TPM sees a clean history while a different OS boots | Sealing and attestation | HMAC protected extends and reads, so substitution produces a detectable failure (the Linux kernel's TPM security approach) |
| TPM reset and replay | Reset the TPM (via the bus or a power trick), then replay good measurements | Sealing and attestation | Detect resets: the kernel salts sessions with a key derived from the null seed, which changes on every TPM reset; PIN based policies |
| Sleep state attacks | On resume, a TPM that lost or improperly restored state lets software rebuild PCRs | Sealing and attestation | Firmware and TPM updates for the specific issue; the 2018 paper's fixes; keep firmware current |
| Memory reset and cold boot | Reboot into another environment and read keys left in RAM | Keys already released into memory | TCG Reset Attack Mitigation (the MOR bit), which BitLocker uses before keys reach memory; PIN; DMA protections |
| Unmeasured or wrongly measured early code | The first code is the measurer; if it lies, the log is consistent but false | Attestation trust | Hardware rooted CRTM, firmware with H-CRTM, keep firmware patched |
| Over trusting the log | Treat log entries as true without replaying them against PCRs | Attestation | Always replay; the PCR validates the log, not the other way round |
Discrete TPM sniffing, in more detail
The Linux kernel's TPM security document is the clearest primary description. It says the TPM is usually a discrete chip attached "via some type of low bandwidth bus", that a simple hardware interposer can be installed in a couple of seconds, and that attacks have been demonstrated against both BitLocker and TPM based Linux disk encryption. Its stated goal is to protect TPM secrets as far as possible in that environment and to detect what cannot be prevented.
The kernel's answer has two parts. HMAC protection on PCR extend and read commands, so values cannot be substituted without detection. And request and response encryption for commands that move secrets, such as unsealing, so a passive listener sees ciphertext. Microsoft's countermeasures page gives the Windows view: preboot authentication with a PIN keeps keys out of memory until the user supplies the second factor, and it specifically mitigates an exposed eDrive bus capturing the key at startup.
The lesson: TPM only unlock gives integrity (the key is only released to the expected boot chain), not confidentiality against someone holding the board. If the threat model includes a stolen laptop in skilled hands, add a PIN. Our post on whether you need a TPM for BitLocker covers the protector choices from the Windows side.
Reset attacks, in more detail
The kernel document separates two active attack classes: substituting extends (stopped by HMAC), and resetting the TPM and then sending fake measurements, which "can only really be detected" by something that changes across a TPM reset. The kernel uses the null seed for this: a primary key derived from it changes on every TPM reset, so sessions salted with it fail afterwards.
The 2018 USENIX paper by Han, Shin, Park and Kim showed a related problem in power management: during certain sleep transitions, PCRs could be reset and then forged. One attack exploited a design issue in the TPM 2.0 specification for the static root of trust; the other exploited an implementation flaw in tboot. The fix is in firmware and in the affected software, and the lesson is broader: integrity mechanisms have to survive every power state, not just cold boot.
For the broader picture of why fixes like these take so long to reach machines, see our post on why firmware below the OS goes unpatched.
How do you plan firmware updates without locking users out?
The principle is simple: before anything changes a PCR in the policy, either remove that PCR from the policy temporarily or make sure the new value is already authorized. The practice depends on the platform.
A runbook that works
| Step | Windows (BitLocker) | Linux (systemd-cryptenroll) |
|---|---|---|
| 1. Inventory | Record each device's protector set with manage-bde -protectors -get C: and confirm the recovery password is escrowed | Record each volume's tokens with cryptsetup luksDump and confirm a recovery key slot exists |
| 2. Know the profile | Is the device on PCR 7 and 11, or 0, 2, 4 and 11? Check the policy and protector details | Which --tpm2-pcrs and public key PCRs were used? luksDump shows the systemd-tpm2 token |
| 3. Capture a baseline | tpmtool gatherlogs before the update | tpm2_pcrread -o before.bin sha256:0,1,2,4,7,11,14 and save the event log |
| 4. Open the window | Suspend-BitLocker -MountPoint "C:" -RebootCount 1 (or 0 for multi reboot flows) | For pcrlock: systemd-pcrlock unlock-firmware-code. For raw PCR 0 or 2 policies: plan to re-enroll |
| 5. Update | Apply firmware through the vendor tool or Windows Update | Apply via your firmware update tool |
| 6. First boot | BitLocker reseals to new values on resume | Unlock with the recovery key or passphrase if needed |
| 7. Close the window | Resume-BitLocker -MountPoint "C:" if you used -RebootCount 0 | Re-enroll with --wipe-slot=tpm2 --tpm2-device=auto ..., or systemd-pcrlock lock-firmware-code and make-policy |
| 8. Verify | Check the protector is enabled again; BitLocker status is On | Reboot once and confirm the TPM unlocks without a prompt |
| 9. Compare | Compare new logs with the baseline: only expected events should differ | Diff the event logs: PCR 0 firmware events should differ, nothing unexpected in 2, 4 or 7 |
Warning.
systemd-cryptenroll --wipe-slot=removes key slots. Before running it, confirm the device path withlsblk, confirm withcryptsetup luksDumpthat a passphrase or recovery key slot remains, and have the header backup from earlier on separate media.
# Re-check the device and that a non TPM slot exists
lsblk -o NAME,SIZE,FSTYPE /dev/nvme0n1p3
sudo cryptsetup luksDump /dev/nvme0n1p3 | grep -A3 -i "keyslots\|tokens"
# Replace the TPM2 slot in one step against the new PCR state
sudo systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=7 /dev/nvme0n1p3
Decisions that remove most of the pain
- Prefer certificate based PCRs. On Windows that means keeping PCR 7 in the profile so BitLocker uses Secure Boot validation; on Linux, PCR 7 (plus 14 with shim) rather than 0, 2 and 4.
- Sign kernel predictions. UKIs with signed PCR 11 policies make kernel updates silent.
- Treat Secure Boot database updates as firmware updates. A dbx revocation update changes PCR 7 by design. Stage it like a BIOS update.
- Do it in rings. Update a pilot group first, watch for recovery prompts, and only then widen. Recovery prompts are the clearest signal you will get that a profile is wrong.
- Escrow before you need it. Microsoft's FAQ notes that on Entra ID joined devices, Windows needs network access to back up a recovery password when policy requires it before resuming. Confirm escrow before step 4, not after.
Our motherboard evolution post explains where the TPM, the firmware flash and the BMC sit on the board, which helps when reading which PCR a given component update will move.
Common mistakes
- Sealing to PCR 0, 1 or 2 on laptops. They change on firmware updates, some BIOS settings, docking and RAM changes. systemd's docs advise against 0 and 2 for exactly this reason.
- Sealing to a bank the firmware does not fill. If the SHA-256 PCR 0 is zeros after boot, a policy on it protects nothing.
- Binding PCR 11 on a system that does not boot a UKI. PCR 11 then holds nothing predictable (or nothing at all), and the policy is either useless or fragile.
- Reading PCR values without the event log. A hash proves the log is intact; it does not tell you what booted.
- Trusting the event log without replaying it. The kernel documentation's quote from the profile is the rule: the log is not trusted alone.
- Forgetting PCR 7 moves on dbx updates. Revocation updates are security updates and should ship; they just need the same suspend or re-enroll step as firmware.
- Clearing the TPM to "fix" an error. Clearing destroys every key sealed in it. On Windows, Microsoft lists clearing the TPM as a recovery trigger. Only clear with recovery keys in hand.
- Relying on TPM only unlock against physical attackers. It ties the key to the boot chain; it does not stop a probe on a discrete TPM bus. Add a PIN.
- Leaving BitLocker suspended.
-RebootCount 0means suspended until someone resumes it. Put the resume step in the same script. - Enrolling LUKS without a recovery slot or header backup. One wrong flag and the only way back is the backup you did not take.
FAQ
What is a PCR in a TPM?
A Platform Configuration Register is a hash register in the TPM that records boot measurements. It can only be extended, meaning its new value is the hash of its old value joined to a new digest, or reset at platform reset. The result is a compact, tamper evident summary of everything measured into it, in order.
What does PCR 7 measure?
PCR 7 records Secure Boot policy: the SecureBoot variable, PK, KEK, db and dbx, plus the specific db entry that authorized each driver or boot application in the boot path. It changes when Secure Boot is enabled or disabled or any of those databases are updated.
Why does my BitLocker recovery screen appear after a BIOS update?
The update changed a PCR in BitLocker's validation profile, usually PCR 0, 2 or 7, so the TPM would not release the key. Suspend BitLocker with Suspend-BitLocker -MountPoint "C:" -RebootCount 1 before the update; BitLocker reseals to the new values when protection resumes.
Which PCRs should I use for LUKS with systemd-cryptenroll?
systemd's man page recommends some combination of PCR 7, PCR 11 and PCR 14 (if shim and MOK are used), and advises against PCRs 0 and 2. For PCR 11, use a signed policy with --tpm2-public-key= rather than literal values so kernel updates do not break unlock.
How do I see the TPM event log on Linux?
Run sudo tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements. On systemd systems, systemd-pcrlock log combines the firmware and userspace logs and checks them against the current PCR values.
How do I check TPM status and get measured boot logs on Windows?
Run Get-Tpm in PowerShell for presence and readiness, and tpmtool gatherlogs <folder> to collect TpmEvents.evtx, TpmInformation.txt and the measured boot logs such as SRTMBoot.dat.
What is the difference between PCR 4 and PCR 11 on Linux?
Firmware measures the boot loader and PE images it loads into PCR 4, which changes with every boot loader or kernel update. systemd-stub measures the static sections of a unified kernel image into PCR 11, which can be precomputed and signed at build time, so it is suited to signed policies.
Can malware reset or change PCR values?
Not from the OS on a correctly working TPM: PCRs 0 to 15 only reset at platform reset, and an extend can only add history. Physical attackers with access to a discrete TPM's bus, or flaws in sleep state handling, have been shown to reset or forge PCRs, which is why PINs, encrypted sessions and firmware updates matter.
What does a TPM quote prove in remote attestation?
That a key inside a TPM signed the selected PCR values together with the verifier's nonce, so the values are genuine and fresh. The verifier still needs the event log to know what those values mean, and the quote says nothing about what happened after boot unless it was measured.
Do PCRs 8 to 15 mean the same thing on Windows and Linux?
No. The TCG profile reserves them for the operating system, and each OS assigns them differently. Windows uses 11 for BitLocker access control and 12 to 14 for data events, boot modules and boot authorities; Linux uses 8 and 9 for GRUB, 10 for IMA, 11 to 13 for systemd-stub, 14 for shim's MOK list and 15 for system identity.
Where Safeguard fits, honestly
Measured boot is only as trustworthy as the software in the chain, and most of that chain on Linux is open source: shim, GRUB, systemd, the tpm2 software stack, attestation agents, plus the libraries they link against. A rebuilt image or a fresh install pulls all of them again, so the same version hygiene you apply to application dependencies applies here, even though Safeguard does not inspect firmware or TPMs themselves.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
To be plain about scope: Safeguard does not read PCRs, verify quotes, scan firmware, manage BitLocker or generate a hardware bill of materials. Those jobs belong to the TPM, the OS and your device management tooling. What Safeguard does is scan the software you build and ship, which for this topic means the attestation agents, boot tooling packages and container images in your build pipeline, with safeguard scan --dir ., --image <name> or --sbom <file>, and show which projects contain a given component and version.
This first demo is a real CLI session. You install the CLI, run safeguard to open the interactive terminal and ask it to fix a lodash CVE; it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. The same ask before every command flow applies if you point it at the repository that builds your attestation service or your UKI build scripts.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine: you approve it with its device code, then scan from the terminal. That suits build hosts and lab machines where you assemble images and boot artifacts and would rather the scan run where the files already are.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. If your team already uses Claude to work through boot and attestation tooling, this is how to let it ask Safeguard which of your projects carry a given package version, with the tool list you approve.
Click through it at your own pace, or open it full screen.
You can download Safeguard or read the CLI documentation to try it on your own build.
Quick check
Why does a signed PCR 11 policy survive a kernel update when a literal PCR 11 policy does not?
A literal policy requires PCR 11 to equal one exact hash, and a new kernel produces a new hash. A signed policy, enforced with TPM2_PolicyAuthorize, accepts any PCR 11 value for which your build key produced a signature, and ukify signs the prediction for each new UKI at build time.
A verifier receives a valid quote over PCRs 0 to 7 with its nonce. What else does it need before trusting the device?
It needs the event log, replayed to reproduce the quoted values, and a policy to judge each event (firmware version, Secure Boot state, db entries, option ROMs). It also needs to know the attestation key is in a genuine TPM, usually through the endorsement key certificate. Even then, the quote only covers what was measured during boot.
Sources
- TCG PC Client Platform Firmware Profile Specification, version 1.06 revision 52 (resource page)
- TCG PC Client Platform Firmware Profile Specification, version 1.06 revision 52 (PDF)
- TCG TPM 2.0 Library Specification (resource page)
- TPM 2.0 Library Part 1: Architecture, version 185 (PDF)
- UAPI Group: UAPI.7 Linux TPM PCR Registry
- systemd-cryptenroll man page
- systemd-pcrlock man page
- systemd-measure man page
- systemd-stub man page
- ukify man page
- systemd-pcrphase.service man page
- tpm2-tools: tpm2_pcrread
- tpm2-tools: tpm2_eventlog
- tpm2-tools: tpm2_quote
- tpm2-tools: tpm2_checkquote
- Linux kernel documentation: TPM Security
- Linux kernel documentation: TPM Event Log
- Linux kernel documentation: IMA Template Management Mechanism
- Microsoft Learn: BitLocker policy settings (configure)
- Microsoft Learn: BitLocker FAQ
- Microsoft Learn: BitLocker countermeasures
- Microsoft Learn: BitLocker recovery overview
- Microsoft Learn: BitLocker operations guide
- Microsoft Learn: How Windows uses the TPM
- Microsoft Learn: TPM fundamentals
- Microsoft Learn: Suspend-BitLocker
- Microsoft Learn: Get-Tpm
- Microsoft Learn: manage-bde protectors
- Microsoft Learn: tpmtool
- NIST SP 800-155 (initial public draft): BIOS Integrity Measurement Guidelines
- USENIX Security 2018: A Bad Dream: Subverting Trusted Platform Module While You Are Sleeping
- Keylime documentation
Last reviewed: 23 October 2026. TPM, BitLocker and systemd behaviour changes between releases; check the linked specifications and man pages for your versions.