Build a TPM2 Boot Policy with systemd-pcrlock

systemd-pcrlock writes a TPM2 access policy that can survive a firmware or kernel update, instead of a fragile seal to fixed PCR values. This guide inspects the measurements and component definitions it can use, tests a prediction, then walks through the guarded command that actually writes the policy. The examples target systemd 255, including Ubuntu's 255.4-1ubuntu8.17 package.

Allow 15 to 30 minutes for inspection. Creating a policy needs more care than the commands themselves: it changes TPM state and can affect access to secrets such as an encrypted disk key. You need a UEFI system with a working TPM2 device, current-boot event logs, root privileges for commands that write under /var, and a recovery plan for firmware or bootloader changes.

1. Confirm the executable and TPM inputs

The installed executable is normally addressed by its absolute path, /usr/lib/systemd/systemd-pcrlock. This version is experimental, so its interface and behaviour may change. Check the version and both event-log paths before doing anything that writes state:

/usr/lib/systemd/systemd-pcrlock --version
test -r /sys/kernel/security/tpm0/binary_bios_measurements && echo "UEFI event log: readable"
test -r /run/log/systemd/tpm2-measure.log && echo "userspace event log: readable"

On the reference installation, the first command reports systemd 255 with TPM2 support in its feature set. A missing event log or TPM device is a stopping point, not an invitation to guess.

Tip: Predictions are deliberately conservative. Unrecognised measurements, mismatches with current PCR values, or components absent from the event log simply keep those PCRs out of the result, rather than guessing.

Checkpoint: Continue only when the machine has a TPM2 device and the logs belong to the boot you are analysing.

2. Inspect the current evidence

Start with a read-only component listing. Definitions are .pcrlock files found in the default directories, including /etc/pcrlock.d/, /run/pcrlock.d/, /var/lib/pcrlock.d/, /usr/local/pcrlock.d/ and /usr/lib/pcrlock.d/.

/usr/lib/systemd/systemd-pcrlock list-components --no-pager

The default location range is 760-:940-, covering the normal userspace runtime window from the initrd-side components towards the main system runtime. Narrow the analysis to a particular phase when needed:

/usr/lib/systemd/systemd-pcrlock list-components \
  --location=760-:940- --no-pager

Next, inspect the combined event log and its PCR matching. JSON is useful for saving output to review later; it does not create a policy:

/usr/lib/systemd/systemd-pcrlock log --json=pretty --no-pager

For a machine-readable event log in TCG Common Event Log Format, use cel instead. A successful command returns exit status 0. Treat a failure here as something to resolve before predicting.

3. Predict without changing TPM state

Run predict before make-policy, never the other way round. By default it considers PCRs 0 to 5, 7, and 11 to 15; select a narrower set explicitly when testing one measured boundary:

/usr/lib/systemd/systemd-pcrlock predict \
  --pcr=7 --pcr=11 --json=pretty --no-pager

The result is a prediction for every combination of recognised component variants, which lets one policy cover several trusted kernel or bootloader versions at once. A PCR is omitted when the current evidence cannot support a complete prediction: a safety feature, since a policy claiming to cover an unknown measurement would be misleading.

Checkpoint: The prediction must include every PCR you intend to bind to a secret. If it does not, fix the event-log or component-definition problem first; adding --force does not make an incomplete prediction safe.

4. Add only the protections you can maintain

Protection commands generate or remove component definitions under /var/lib/pcrlock.d/. They are root operations, and they change what future predictions accept.

For example, this read-and-write pair is intentionally explicit:

sudo /usr/lib/systemd/systemd-pcrlock lock-machine-id
sudo /usr/lib/systemd/systemd-pcrlock lock-gpt
sudo /usr/lib/systemd/systemd-pcrlock list-components --no-pager

Warning: Do not lock firmware configuration casually. Before an approved firmware or Secure Boot change, remove the relevant generated definition with its matching unlock- command (each lock- command has a paired unlock- command), make the change, then regenerate it and run a fresh prediction. That command deletes files, so record it and review the component list before continuing.

5. Write the TPM2 policy

Warning: This is the state-changing step. make-policy allocates a TPM2 NV index on first use and updates it later. The metadata is written to /var/lib/systemd/pcrlock.json. The TPM must implement PolicyAuthorizeNV, specified by this systemd version as TPM 2.0 version 1.38 or newer.

sudo /usr/lib/systemd/systemd-pcrlock make-policy \
  --recovery-pin=yes --json=pretty --no-pager

The recovery PIN is requested interactively and stored in encrypted form in the policy metadata; keep it in an approved password store. Without --recovery-pin=yes, systemd-pcrlock generates a PIN automatically and stores it encrypted too, but that is less convenient when you need to recover a policy after a boot change.

Verify the metadata and the resulting policy without forcing another write:

sudo test -s /var/lib/systemd/pcrlock.json
sudo /usr/lib/systemd/systemd-pcrlock predict --json=pretty --no-pager

Repeated make-policy calls stop quickly when the prediction is unchanged. --force overrides that optimisation and writes the policy again; reserve it for a specific operational reason.

6. Recover from an intentional change

If a policy is no longer usable, use the recovery PIN through a deliberate maintenance path, then refresh the component definitions and run make-policy again.

Recovery: If the policy should be abandoned entirely, the irreversible-looking command is:

sudo /usr/lib/systemd/systemd-pcrlock remove-policy

This deletes /var/lib/systemd/pcrlock.json and deallocates the NV index. Confirm no disk-encryption or other TPM-protected consumer still depends on it before running the command. A backup of the JSON file alone does not restore an NV index or undo changes made inside the TPM.

Done means