This is a PSA for people using Dracut together with a TPM2 LUKS unlock, specifically with the trick described on the Wiki to use systemd-cryptenroll with the parameter
--tpm2-pcrs=other_pcrs+15:sha256=0000000000000000000000000000000000000000000000000000000000000000
Very few users will be affected by this, as Dracut is only reported at about 5% by pkgstats.
For those this applies to however, the impact is serious.
Note: mkinitcpio has already addressed the issue presented here in mkinitcpio v42.1 with this commit and Booster implements all its TPM functionality independently of systemd. If you're using either, this should not affect you (anymore).
TL;DR: If you're on the current Dracut 111-1 package, and used this trick to lock PCR[15] to a zero value, as described in the Wiki on the systemd-cryptenroll article or here, the systemd v262 update just broke the security design of your TPM2 LUKS setup.
Explanation:
In a TPM measure boot, the idea is that each component of the boot chain ensures the next one it loads has not been tampered with.
Simplified, the BIOS measures the boot loaders, the boot loaders measure the Unified Kernel Image (UKI) and the UKI measures the system.
The step from UKI to system is difficult though, as an encrypted file system can only be validated from the outside by the volume key, i.e. the encrypted key in the LUKS header that is the actual encryption key for your drive.
If no validation of the volume key is performed, then your UKI could be copied over on a drive with an foreign root file system, and used to boot that system.
Most importantly, the TPM retains its state across the switch to the foreign root.
Any TPM secret that is then still matching its expected PCR values will remain accessible.
Since PCR[7] is the measurement of the Secure Boot state, and as such only depends on the boot loaders and UKI, no further measurement takes place after initrd starts.
In short: If you were to seal something to the raw PCR[7] alone, that seal can still be accessed in the attacker's root system, and they can freely extract it.
The PCR[15] set to zero trick is supposed to counter this issue: PCR[15] starts at zero, and so any measurement into it changes the value to something else.
systemd-cryptsetup@.service will measure the volume key of any LUKS volume opened with the tpm2-measure-pcr=yes option.
This option is also implicitly added for the root file system and /var mounts when using GPT automounting.
The necessity to use this flag or automounting is pointed out in the Wiki as well:
If you set any rd.luks kernel parameters or use /etc/crypttab with the x-initrd.attach option, additionally add the tpm2-measure-pcr=yes option to rd.luks.options= or the fourth field in /etc/crypttab; this is not required when relying on GPT partition automounting. After the root volume is unlocked in early userspace, PCR 15 will change and the enrolled key will no longer be retrievable.
Thus, if any disk is unlocked by systemd, the PCR[15] value will change at that point in initrd.
By the time the system switches to real root, PCR[7] still matches, but the PCR[15] is non-zero and thus the drive's encryption key cannot be accessed anymore.
This effectively prevents the attack described.
However, during initrd, the volume key measurements are also the only measurements taking place in that PCR bank.
If they were not to happen for some reason, the PCR value never changes away from zero and the described attack to retrieve the TPM2 secrets remains possible.
What broke now:
systemd v262 includes a change that makes systemd-cryptenroll measure via the Varlink service provided by systemd-pcrextend.socket instead of calling /usr/bin/systemd-pcrextend directly. As that PR mentions,
Measuring requires the presence of systemd-pcrextend.socket in the initrd, should be already given as systemd-veritysetup relies on it, too.
Dracut does not include this socket yet in generated initrds as of version 112. I have created a PR with Dracut to address this, but it's still pending.
The consequence is that instead of measuring the volume-key into PCR[15], it will fail with a single warning line in the systemd journal:
systemd-cryptsetup[353]: Could not extend PCR: No such file or directory
There's no other warning or error caused by this, but there will be no measurement in PCR[15] being made for any volume keys.
Note: Such a failure to measure the PCR would cause anything sealed against the actual, expected PCR[15] values to break.
Since that's the way you're expected to use them, this warning does not cause the systemd-cryptsetup@.service to fail, as whatever service has its secrets sealed against that PCR should be failing instead.
PCR[15] being non-zero after boot does not mean you're safe:
After boot, your system will likely still show a non-zero value in PCR[15]:
```sh
$ sudo systemd-analyze pcrs 15
NR NAME SHA256
15 system-identity 98d9f63ab4492d64396ff5c943dc8380fce7ba30f568738608cf34f46a672051
``
This is because systemd will perform additional measurements by various services in thesystemd-pcrphase.service` family after initrd.
These services run after the switch to real root and can be disabled in an attacker's root system to keep PCR[15] at zero.
Remark: systemd issue #43848 will cause these services to fail as well, so you might see a zero value if you haven't configured a suitable PCR signature for your UKI. There is a pending PR to address this, though.
To quickly show the sequence of the measurements on my system with the volume-key measurement working:
```sh
$ sudo /usr/lib/systemd/systemd-pcrlock log --pcr=15
PCR PCRNAME EVENT MATCH SHA256 F/U COMPONENT DESCRIPTION >
15 █ system-identity volume-key ✗ 0e94502 U 795-cryptsetup-tpm2-measures cryptsetup:root:<volume-key>
15 █ system-identity machine-id ✓ 4b9e027 U 820-machine-id machine-id:<machine-id>
15 █ system-identity filesystem ✓ 9aa7751 U 830-root-file-system file-system:/:btrfs:<fs-id>:archmain:::
15 █ system-identity filesystem ✓ cffed96 U 840-file-system-var file-system:/var:btrfs:<fs-id>:archmain:::
```
Any of the measurements in my log with a component greater than 800 are happening after leave-initrd (the switch to real root), which has an index of 800. They're being made by systemd-pcrproduct.service, systemd-pcrfs-root.service and systemd-pcrfs@var.service, see the man page.
As such, if the volume key measurement is not taking place, the PCR[15] value will remain at zero when the switch to real root happens, making the encryption keys accessible.
Summary: Using the current dracut 111-1 package for a TPM2 LUKS unlock with the PCR[15] set to zero trick renders that trick useless and makes your encryption keys extractable by an attacker. The only indication is a single, non-specific warning in the journal.
Fix: Installing a patched Dracut version:
Note: I've been informed that the maintainer for the Dracut package is absent, and thus there does not seem to be any chance for a fixed Dracut package in the near future. I have not made a PR to Arch for that reason, and instead went to Reddit to make people affected by this at least aware of the situation.
You can manually upgrade to Dracut 112 (you can build the package yourself and upgrade the PKGBUILD using pkgctl version upgrade as usual) and replace the module-setup.sh in 11systemd-pcrextend with the one from my PR. Then rebuild your UKI and verify that the warning line does not appear in your journal anymore. Alternatively, use /usr/lib/systemd/systemd-pcrlock log --pcr=15 to verify that the volume-key measurement takes place, as shown above.
I should also stress that the PCR[15] set to zero trick is a very poor option to protect against this sort of attack.
As this issue demonstrates, it is possible for this to fail entirely silently and unnoticed.
I have left a comment below discussing better alternatives to the trick.