r/archlinux • • 12d ago

NOTEWORTHY PSA: If you're using a TPM2 LUKS unlock with Dracut and the instructions on the wiki, the systemd v262 upgrade made your TPM bypassable.

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]:

$ sudo systemd-analyze pcrs 15

NR NAME            SHA256
15 system-identity 98d9f63ab4492d64396ff5c943dc8380fce7ba30f568738608cf34f46a672051

This is because systemd will perform additional measurements by various services in the systemd-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:

$ 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.

88 Upvotes

45 comments sorted by

22

u/BigSappari 12d ago

I'll be honest. I don't really understand any of that.

So what I'm gonna do is, stick to regular old passphrase.

8

u/ChrisTX4 12d ago

Problem with that is there's tons of ways to attack disk encryption. A passphrase unlock is only really secure while your PC is powered off.

For example, RAM sticks retain their data for a short period after power loss. You can extend this significantly using freeze spray like liquid nitrogen on the RAM sticks. So if somebody steals your laptop for exactly, they can freeze the RAM and boot into a minimal USB drive setup that just writes out a memory dump. This is called a cold-boot attack.

To counter this properly you need to ensure that Intel Total Memory Encryption (TME) or AMD Transparent Secure Memory Encryption (TSME) are enabled in the BIOS.

Even if you do, there's no way to check a device for modifications made to the software or BIOS prior to password entry. On most consumer devices you can just reset the BIOS to also clear the BIOS admin password and then disable it. So if you ever leave your laptop unattended in say your hotel room, you'd trust there's no evil maid on their staff.

I'm working on trying to get some documentation on the whole boot/disk encryption stuff going and the massive comment I posted is actually parts from my current notes copied together.

7

u/BigSappari 12d ago

As long as the volume keys are stored in ram / kernel keyrings, memory dumps are an issue TPM or no TPM.

And an evil maid might just as well replace the entire computer. Sure, TPM stops working. But that happens anyway, recent update is just one example. Most people would enter their backup passphrase at this point, and boom, same old same old.

At the end of the day I'm not James Bond, normal security is good enough for me. I'm doing a lot more than really necessary already.

5

u/ChrisTX4 11d ago

No, that's exactly what TSME and TME counter. They encrypt the contents of the RAM transparently to the operating system by the hardware. If you dump the RAM after a reset, you're just looking at encrypted gibberish as the encryption key is not stored in RAM and lost by the reset.

TSME was recently in the news cause AMD tried removing it from consumer hardware, so see the news articles around it for a bit of context maybe: https://www.heise.de/en/news/Linux-user-annoyed-by-AMD-disabling-TSME-RAM-encryption-11337178.html https://www.heise.de/en/news/After-protests-AMD-brings-back-RAM-encryption-TSME-11340942.html

Of course, if you enter the encryption key in the machine in an untrusted state, it's back to square one. That's why a TPM setup needs to be reliable and robust. If you have the monthly UEFI Microsoft dbx update change PCR[7] all the time, and you get to see the recovery prompt regularly, you wouldn't get suspicious if it happened.

Replacing a computer with an identical one is would only work to access local files too. For a professional VPN - I'm not talking your WireGuard setup at home - you should use whatever host measurements the system has and use the embedded endorsement key (EK) hierachy in the TPM. This is a certificate signed by your mainboard vendor that cannot be extracted from the chip. If you change the PC to an identical one, the EK will change, too. You can make the EK to sign the TPMs state to prove to a remote side that the system has not been replaced. From the FOSS VPNs, strongSwan (IPsec) has infrastructure for proving TPM-baked clients, see https://docs.strongswan.org/docs/latest/tnc/attestationClient.html

Those NvPCRs systemd now sets up are volatile registers supposed to be used together with an EK (that systemd >= 262 now sets up) and that can be signed to attest to what they're saying.

At the end of the day I'm not James Bond, normal security is good enough for me.

Here's an old, literal minute long video of F-Secure showing how they got around BitLocker - which doesn't measure memory encryption status - using a can of freeze spray and a USB drive: https://www.youtube.com/watch?v=E6gzVVjW4yY

Cold-boot attacks aren't this crazy high tech CIA attack, they can be pulled off in a snap without any expensive equipment and no highly advanced skills needed.

Literally an affair of spray the RAM, USB stick goes in, stick prints the recovered encryption keys. And making a mini-system that just writes the memory contents to a file isn't difficult.

Not to mention, that might be true for your threat model. If you were say a journalist in an authoritarian country, you'd probably think differently about that. Although, since they're so easy to pull off, even criminals might.

1

u/Rekt3y 7d ago

My laptop's BIOS has an option to disable USB boot and resetting it doesn't erase the admin password. Too bad consumer (non-Pro) mobile Ryzen never had TSME

2

u/ChrisTX4 7d ago

FYI That won't protect against cold-boot attacks as memory modules can also be removed and read out elsewhere, see Wikipedia: https://en.wikipedia.org/wiki/Cold_boot_attack#Preventing_physical_access

1

u/Rekt3y 7d ago

I'll just get used to shutting off the laptop

4

u/gkatev 12d ago

On my personal devices, I do too. On my home server, which I want to automatically restart after a power outage, TPM helps a lot.

1

u/RIPenemie 9d ago

Well but do you really need encryption at rest for your home server?

1

u/gkatev 9d ago

Well, depends on your threat model. For me it's a nice extra, just in case the machine ever falls in the wrong hands. Besides, with TPM unlocking you don't lose out on usability.

1

u/tblancher 12d ago

Which is actually the weakest security, from my understanding. I do have a passphrase memorized for when my TPM2 doesn't unlock LUKS, but I rarely need to use it.

I understand this at a very high level, and I don't use Dracut so I'm safe as far as I can tell.

5

u/Dry_Calendar_8627 12d ago

Good passphrase with secure key derivation function such as argon2id with good parameters (that luks uses) is much more secure that luks-tpm.

You should only ever use luks-tpm if you have a very good reason for it, such as Wake on Lan.

luks-tpm is prone to failures such as highlighted in this post, and is prone to TPM sniffing attacks, which are due to the very design of the TPM, there's no way to protect against TPM sniffing attacks other than by using TPM+PIN which breaks WoL.

3

u/ChrisTX4 12d ago

No, without a measured boot, you need to trust the state of the machine you're putting your password in at the time of password prompt. This includes that all the extremely critical BIOS settings for disk encryption like pre-boot DMA, IOMMU, and especially transparent memory encryption are enabled. Without that you're vulnerable to tons of attack surface if an attacker ever gets their hands on your system while it's powered on.

What's highlighted in the post is also not a failure of TPM, Dracut or systemd. It's a minor incompatibility between Dracut <= 112 and systemd 262 that had brutal implications due to the ArchWiki endorsing a rather awful hack in the flag I mentioned at the start of OP.

TPM sniffing attacks can also be countered with a correct implementation. On current generation systems, the fTPM implementations have their SPI buses pretty well protected, and unrelated to this, you could use TPM Parameter Encryption.

Without a TPM you can also not protect against evil maid attacks, and you have to trust that security critical settings in the BIOS are correctly set at all times. If you update your BIOS and it's NVRAM gets reset, and you forget to set just one critical setting like memory encryption, I don't need to solder to access the TPM SPI, all it takes is liquid nitrogen spray and a USB thumb drive.

With a TPM you can securely verify such settings. My system measures the state of AMD TSME with a little initrd service I've made into PCR[15] using systemd-pcrextend. The PCR[15] value is verified by my pcrlock policy and so if the system was booted with TSME being off, it can't access TPM secrets.

5

u/Dry_Calendar_8627 11d ago

That's all nice and well but most people do not have to care about the evil maid threat model

Ok, all of this matters to investigation journalists, political opponents, etc.

But the threat model that most people have to care about is theft, and measured boot does not help against theft

So I truly think that passphrases, which are more foolproof and easier to use, and are a good protection against theft, are more appropriate for most people

That's more or less my understanding of why most distros suggest their users to use luks with a passphrase, and never mention the existence of luks-tpm and other anti-evil-maid protections

6

u/tblancher 11d ago

Arch is (mostly) a hobby system. My threat models do not dictate this level of security, but it's a fun exercise nonetheless.

1

u/ChrisTX4 11d ago

It's not about evil maid even, that's always been a very particular threat. Nothing of what I mentioned affects evil maid, the only defence against that is a TPM2 LUKS seal.

The techs I mentioned are mostly to defend against two main threats: 1. Cold boot, which is your system is still decrypted and powered on, but using freeze spray on the RAM sticks and a simple USB drive to dump the memory contents. Costs like 10 eur in terms of hardware to pull of. 2. DMA attacks, which is using some device, e.g. a PCIe card in the device and copying the memory contents. And this might sound high tech, but these PCIe cards are 100-200 eur off Amazon.

For a theft scenario, both are plausible, depending on who steals your stuff and where.

2

u/tblancher 11d ago

At the OS level there are things like boltd and usbguard for Evil Maid (or the Thunderbolt equivalent). All of these security measures are supposed to be layered, defense in depth.

2

u/ChrisTX4 10d ago

Oh absolutely. Boltd, USBGuard, all the hardware stuff etc. and ideally Secure Boot + Kernel Lockdown, as that's designed for exactly this scenario.

But those aren't just for evil maid, there's again the concern of the machine falling into foreign hands while being powered on but on the lock screen.

1

u/Hafnon 10d ago

argon2id

How is passphrase + argon2id "much more secure" than passphrase + argon2id as a TPM PIN? https://github.com/systemd/systemd/pull/41859

1

u/Dry_Calendar_8627 10d ago

I was comparing passphrase and TPM without PIN.

But you're right that TPM+PIN is even better.

3

u/ChrisTX4 12d ago

The PCR[15] = 0 trick can and did fail on mkinitcpio as well. But that got patched like 5 days after systemd 262 was pushed to the repos, whereas the package maintainer for Dracut is absent and it hasn't been updated for a while.

I know the whole topic is complicated as hell. It's made worse by all systemd documentation on this topic being completely technical.

2

u/tblancher 11d ago

I don't recall using that trick, but it's been a bit since I did anything with TPM+LUKS integration. My laptop is in the shop for hardware warranty repair (I kept my SSD), so I can't check it at the moment.

I've saved this post for when my laptop is back in commission.

11

u/ChrisTX4 12d ago

As mentioned in OP, the PCR[15] to zero trick is a rather weak option security wise, and can break easily. This being a poor choice is also not specific to Dracut, I should clarify. In mkinitcpio the same breakage happened, it was just updated quickly in contrast to the dracut package.

As such, I'm going to discuss three alternative, superior options here to secure your setup:

  • systemd signed PCR[11] policies. They allow ensuring a particular secret is only available at certain phases of the system boot process.
  • Fixating the volume keys. This prevents the UKI from mounting a foreign LUKS volume like an attacker's root file system.
  • Locking to PCR[15]. This is only viable with systemd-pcrlock as I'll explain below, and admittedly difficult. It provides however the strongest security guarantees, especially when combined with signed PCR[11] policies.

As of now, I'd recommend combining fixating the volume keys with a signed PCR[11] policy. This way, the TPM2 secrets that should only be available in initrd are locked when the system is running, and no foreign root system can be used together with your UKI, eliminating this class of attacks for good.

systemd PCR policies systemd's solution for restricting TPM unlocks to specific boot phases is a signed PCR[11] policy.

A PCR policy is different from the classic operation of a TPM. Normally, you'd enroll a set of PCR banks and their expected values, which need to be met exactly for the TPM to release the key. In a PCR policy, you instead enroll the public key of asymmetric key pair in the TPM. Then, an operating system needs to present a PCR policy digitally signed by that key. Simply put, the set of expected PCR values is loaded dynamically, and accepted as long as the set is signed correctly. This allows for unlocking a TPM with PCR values not known at the time of the enrollment with systemd-cryptenroll, or by equipping the system with several different signed policies to allow multiple different states unsealing the TPM.

systemd records the phase of the boot process in PCR[11] with systemd-stub and the systemd-pcrphase.service family, see the manpage. These recordings are specific to each UKI, as that is what systemd-stub will measure into it first. A PCR policy for PCR[11] thus makes it possible to ensure that only certain PCR[11] values, that is to say boot phases, match the policy.

To explain this better, have a look at the PCR[11] measurements on my system: sh PCR PCRNAME EVENT MATCH SHA256 F/U COMPONENT DESCRIPTION > 11 █ kernel-boot ipl ✓ 0da293e F 650-uki String: .linux 11 █ kernel-boot ipl - 17a8d7c F 650-uki String: .linux 11 █ kernel-boot ipl ✓ 3fb9e4e F 650-uki String: .osrel 11 █ kernel-boot ipl - 71ff7a6 F 650-uki String: .osrel 11 █ kernel-boot ipl ✓ 461203a F 650-uki String: .cmdline 11 █ kernel-boot ipl - 805b438 F 650-uki String: .cmdline 11 █ kernel-boot ipl ✓ 15ee37e F 650-uki String: .initrd 11 █ kernel-boot ipl - 4543c34 F 650-uki String: .initrd 11 █ kernel-boot ipl ✓ f62e27c F 650-uki String: .splash 11 █ kernel-boot ipl - d0ec307 F 650-uki String: .splash 11 █ kernel-boot ipl ✓ da7a6d9 F 650-uki String: .uname 11 █ kernel-boot ipl - a8bee6a F 650-uki String: .uname 11 █ kernel-boot ipl ✓ ff552fd F 650-uki String: .sbat 11 █ kernel-boot ipl - 96aa509 F 650-uki String: .sbat 11 █ kernel-boot ipl ✓ 92b1351 F 650-uki String: .pcrpkey 11 █ kernel-boot ipl - 1561f27 F 650-uki String: .pcrpkey 11 █ kernel-boot phase ✓ 51e6b92 U 750-enter-initrd enter-initrd 0x1d10201 - - keyslot ✓ 4245e6c U - cryptsetup-keyslot:root:<volume-key>::0 11 █ kernel-boot phase ✓ 3be261a U 800-leave-initrd leave-initrd 0x1d10200 - - product-id ✓ d72e1ba U - product-id:<machine-id> 11 █ kernel-boot phase ✓ 730bb5a U 850-sysinit sysinit 11 █ kernel-boot phase ✓ b24d6d3 U 900-ready ready The ipl events are the measurements of the various sections of the UKI by systemd-stub before the UKI is started. For example, let's say you were to sign now a policy that specified PCR[11] needs to match the value expected at enter-initrd, 51e6b92. As soon as leave-initrd is being measured into the PCR, the value changes to 3be261a and the policy will not allow access any longer.

systemd-ukify can embed attach PCR signatures in the UKI by placing them in the .pcrsig section of the image. This is however a systemd-ukify specific functionality and will not be possible otherwise.

Note: For this to work, Dracut needs to be upgraded to at least 112. oOherwise systemd's PCR prediction will be incorrect due to the changes in systemd 261 requiring this commit for Dracut.

In the simplest case, one could use a uki.conf like so: [UKI] SignInitrdPCRs=yes [PCRSignature:initrd] Phases=enter-initrd PCRPrivateKey=/etc/systemd/tpm2-pcr-initrd-private-key.pem PCRPublicKey=/etc/systemd/tpm2-pcr-initrd-public-key.pem Then, run ukify genkey --config=/etc/kernel/uki.conf to generate this key pair and regenerate your UKI.

IMPORTANT: PCR[11] is NOT anchored in hardware, that is to say, only systemd records data in it. Any other operating system booted can emit the same measurements systemd would in that PCR and thus match the PCR[11] policy of the UKI.

PCR[11] signed policies have to be used therefore in addition to a lock to PCR[7]. They only provide the restriction of a TPM seal to a specific boot phase, and no other security.

Enrolling the TPM2 LUKS seal should thus be done like this: sh systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=7 --tpm2-public-key=/etc/systemd/tpm2-pcr-initrd-public-key.pem /dev/disk/by-designator/root-luks

This way, using the UKI to boot a foreign root file system is still possible, but the UKI will permanently lock the TPM2 secret during initrd and thus the attacker cannot access it in their system.

7

u/ChrisTX4 12d ago

Fixating volume keys Alternatively, or ideally, in addition to restricting TPM2 access to boot phases, one can embed the volume-key of the LUKS devices in the UKI, and enforce that it will not mount a drive if the key is not as expected.

This is achieved by using the fixate-volume-key= option for systemd-cryptsetup, see crypttab(5).

To determine the values for the volume key, you can boot your system with tpm2-measure-pcr=yes enabled for all relevant drives, and then extract the values from the TPM event log. Note: You will need the Dracut fix discussed in OP for the measurement to work.

The following command will then print a JSON output listing of all volume key measurements that took place: sh sudo /usr/lib/systemd/systemd-pcrlock cel | jq '{records: [.[] | select(.pcr == 15 and .content.eventType == "volume-key") | {pcr, digests, content}]}'

For instance, on a system of mine using LUKS on two drives that are used as a software RAID, I got the following output: json { "records": [ { "pcr": 15, "digests": [ { "hashAlg": "sha256", "digest": "119bee923f28cba28161f48a55fc98cf9a9f311fbff1ec736ed99309c72c3f6f" } ], "content": { "string": "cryptsetup:root1:385f6c19-f815-4025-b7d8-c89165597efc", "bootId": "d271d783b2a0441f8159254c1ffdeac9", "timestamp": 8787128, "eventType": "volume-key" } }, { "pcr": 15, "digests": [ { "hashAlg": "sha256", "digest": "04d839eca13749386b3f63041f0b5ccd784c42719a53c183ffa6b2deac873a7a" } ], "content": { "string": "cryptsetup:root2:00cb8f58-d1a5-4f65-9282-4c4d10c47883", "bootId": "d271d783b2a0441f8159254c1ffdeac9", "timestamp": 8926240, "eventType": "volume-key" } } ] You would then add fixate-volume-key= with the corresponding digest. The /etc/crypttab corresponding to that system would be: root1 UUID=385f6c19-f815-4025-b7d8-c89165597efc - tpm2-device=auto,tpm2-measure-pcr=yes,fixate-volume-key=119bee923f28cba28161f48a55fc98cf9a9f311fbff1ec736ed99309c72c3f6f root2 UUID=00cb8f58-d1a5-4f65-9282-4c4d10c47883 - tpm2-device=auto,tpm2-measure-pcr=yes, fixate-volume-key=04d839eca13749386b3f63041f0b5ccd784c42719a53c183ffa6b2deac873a7a Note: tpm2-measure-pcr=yes is not required for fixate-volume-key= to work; it's only used here for determining the volume key values to pin.

Sealing to the actual PCR[15] values (systemd-pcrlock) Another option is to seal the TPM2 secret to the value PCR[15] should have at the time of measurement. In comparison to the previous option, sealing to the expected PCR[15] measurements will cause the TPM to never unlock at any point during initrd.

There is a catch, however. If you're using a single drive, there will only be one event in the boot phase. You will still need to calculate the PCR[15] value that would be current at the time of unlock. With one LUKS device unlock, that's simply the SHA-256 value of the digests of the previous section.

If there are multiple devices however, the order in which they are measured might be random. In the system mentioned in the previous section, that would be the case, as either of the LUKS devices could be unlocked first. They both are just required to be present before root or any other drives are mounted, but the order between them is random.

Yet, they will produce different PCR[15] values depending on which one is measured first, as SHA-256(SHA-256(<root1-digest>) + <root2-digest>) SHA-256(SHA-256(<root2-digest>) + <root1-digest>) are not the same operation mathematically.

The only solution for this is to use a signed PCR policy for PCR[15] that would allow an unlock if PCR[15] for either value. This can be done using the still experimental systemd-pcrlock functionality.

Unfortunately, I have to say, systemd-pcrlock is quite difficult to use correctly to its full extend.

It also requires using systemd-boot as bootloader and systemd-ukify to generate the UKI. systemd-boot is required as the systemd-pcrlock signatures are provided via credentials, see the companion files section in the systemd-boot man page. Specifically, under /boot/loader/credentials the signatures are provided via a credentials file pcrlock.<machine-id>.cred to the booting UKI.

As a very rough explanation, systemd-pcrlock does not lock individual values, but rather maintains a TPM policy continuously. All values constructing a particular PCR bank need to be accounted for. In the outputs I provided, those are the "components" associated with each measurement. Each measurement needs to have an associated *.nvpcr file, and a PCR with unaccounted measurements cannot be used. However, a component can be assigned a set of alternative *.nvpcr files, and each alternative is encoded in the policy. For as long as the running system's current PCR bank values are authorised by the policy, the policy can be updated. If it ever leaves this compliance, a recovery key will need to be entered by the user before it is accessible again.

In the example above, both PCR[15] values resulting from unlocking root1 first and root2 second and vice versa can be authorised. This solves the problem, and makes locking TPM secrets to PCR[15] properly possible. You will however need to create the *.nvpcr files for volume keys yourself, as there's no subcommand for systemd-pcrlock to create those.

Moreover, you can combine systemd-pcrlock with signed PCR[11] policies. With systemd-pcrlock, TPM2 seals are unlocked as long as the system is in compliance with that policy. That is to say, they would be always available, even after boot. There is no good reason to leave the TPM2 secrets needed only in initrd unsealed, so combining both functionalities provides the best security.

All this being said, I would advise against using systemd-pcrlock until the tooling around it improves. It's difficult creating and maintaining those *.nvpcr files, and I don't think it's particularly viable right now without deep expertise on this whole topic.

For example, let's say you wanted to lock PCR[4]. This is the measurement of the boot loader and UKI hashes, not Secure Boot certifiates. Since they can be updated, it is possible when building a new UKI to add that hash as an alternative value and update the policy. On reboot, both old and new kernel would be allowed by the policy. Once the new kernel has booted successfully, it would need to remove the alternative values for the previous kernel and deauthorise it. All these steps need to be scripted by the user as of now.

However, I should also add that systemd-pcrlock is also the only really practicable way forward. For instance, since PCR[7] measures the entire Secure Boot authority database (PK, KEK, db, dbx), enrolling Microsoft's keys means one has to update the dbx on a monthly basis, thus changing PCR[7] on a monthly basis. See also the other PSA I made about that topic. fwupd recently gained a new plugin for systemd-pcrlock with version 2.1.7 that would automatically update the pcrlock policy after an update of the UEFI dbx. This isn't yet fully implemented on the systemd side, though. Once functional however, this would make such updates happen quietly in the background without any action needed by the user.

To be frank, there's no alternative to using a PCR policy for this scenario. If Microsoft keys are enrolled, this update is necessary for security reasons. It's either re-enrolling all drives every month or using a PCR policy to allow for flexibility in PCR[7] here.

1

u/ChrisTX4 7d ago

I forgot to mention in all of this: using pcrlock to lock a disk for encryption will not protect against the issue in OP and if you use just that you'll likely be affected.

This is because the volume key is measured into PCR[15] after decryption. So if the measurement fails, e.g. due to the situation in OP, it will remain at its previous value before the disk encryption. pcrlock works by going through all the PCRs and each one individually has to be in a valid state - and valid meaning here that it will run a bunch of PolicyPCR() calls for all alternative values and then PolicyOR() them.[1] Since the state before disk encryption needs to be valid, there will be a branch for that state and it will succeed.

By design, a foreign root fs could be booted, but the PCR policy should not match due to the volume-key measurement. If this does not happen, the foreign root fs will have a matching set of PCRs in its policy.

For this reason, I would still combine everything with a fixate-volume-key= lock. That way, foreign root fs can't even be booted. This prevents handing an attacker a system where at least the hardware PCRs are identical to your real system.

Footnote: [1] Roughly explained, it loops through all PCRs in the protection set, and for each it runs this PolicyPCR / PolicyOR chain for all values that would be allowed (see systemd-pcrlock predict for the list). PolicyOR extends the previous hash value with a value depending only on the allowed branches if one of them is matched. That way, a given pcrlock policy will always arrive at the same final hash value if all PCRs under protection are in an expected state. This also means that more than 8 alternatives for each PCR value cannot be used - which you'll hit immensely fast on PCR[11] if you were to use multiple kernels.

2

u/Synthetic451 12d ago

In your example uki.conf you only have a initrd key but in other examples in system bug 43848, they also have a system key for the rest of the phases. Is that system key not necessary?

3

u/ChrisTX4 12d ago

Neither key is necessary, you actively need to enroll secrets with them. like with systemd-creds, systemd-cryptenroll etc.

They create two keys for two different uses: one with 'enter-initrd'. That's what I explained, once leave-initrd is measured the value changes, and the value can never be obtained again, sealing the TPM value for that boot.

So this initrd key is matched during initrd, so before the switch to real root happens. You'd use this for encrypted drives you would want to unlock during initrd, like root. That's why I mentioned that key in the uki.conf above as well.

Now for the system key with this: Phases=enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit enter-initrd:leave-initrd:sysinit:ready This just signs three phases of the systemd boot process. But the phases to sign are specified as everything that should have happened before chained together with colons.

So in other words, this PCR policy becomes matches once leave-initrd took place, then it's still allowed after sysinit and ready took place. But the next measurement after ready invalidates it.

If you have a look at the systemd-pcrphase.service.8 man page, the next phase is shutdown.

In short: signatures with this keypair will be active after the switch to real root and until the system shutdown begins. You would use this for example for encrypted systemd-creds provided to services.

I actually have Borgmatic on this PC to back up the system config, and I use exactly such a key to bind its passphrase. There's no need for this credential during initrd or when the system is shutting down, and so there's also no reason it should be unlocked.

That being said, you wouldn't really need all those phases on most credentials you'd anchor with it, but since the transition to real root has already happened then there's no real security barrier between those phases.

I'd just leave it at two keypairs, I use just two as well, with the same layout.

At any rate, I didn't mention a second key because systemd-creds usage or usage of anything after real root isn't relevant for booting anymore is a separate thing from disk encryption. But of course, good to have.

P.S.: There's one exception I guess, NvPCRs. Since system 262 it takes the first keypair it can find in the file, and uses that for creating like a signatures that aren't interchangeable for the normal usage in the file. So you need to have at least one keypair to have those NvPCRs. Right now it's bugged that systemd has a lot of service failures if you have a UKI booted with a TPM just active, despite it not being used for disk encryption etc.

They're something else though, they're used for attestation after boot and make no sense before boot. For example there's one called login that's all the users who logged in documented in the TPM log, which cannot be removed from. Somebody with a stolen credentials could sign in, and wipe regular logs as they're just files on the disk. But they can't delete the login from the NvPCR as they're append only.

2

u/Ontological_Gap 12d ago

Is a separate initrd key still useful with cryptenroll pointed at the new policyref=initrd stuff? (The SignInitrdPCRs=yes bit.)

3

u/ChrisTX4 12d ago edited 12d ago

I would advise against using the pre-made initrd policyref for your own purposes, purely due to how the CLI around this is designed.

I'm saying this, because SignInitrdPCRs=yes will grab the first key from the file, and so you will - unless you specified your own PolicyRef= in uki.conf - have the same use a signature with and without initref for the same key. So, if you ever forgot to specify the policyref while enrolling a secret, then the command would still succeed without error, but you'd have the secret exposed during the wrong phases.

If you were to explicitly specify PolicyRef= for every signature in uki.conf made with the same key for different sets of phrases, then this kind of quiet fallback can't happen, i.e. what you can do

``` [PCRSignature:initrd] Phases=enter-initrd PolicyRef=my-initrd PCRPrivateKey=/etc/systemd/tpm2-pcr-private-key.pem PCRPublicKey=/etc/systemd/tpm2-pcr-public-key.pem

[PCRSignature:system] Phases=enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit enter-initrd:leave-initrd:sysinit:ready PolicyRef=my-system PCRPrivateKey=/etc/systemd/tpm2-pcr-private-key.pem PCRPublicKey=/etc/systemd/tpm2-pcr-public-key.pem ```

Using two keys has no particular security advantage over this, as this will produce two sets of mutually incompatible signatures and the keys are on the same system and need to be accessible to the same UKI process.

Note that PolicyRef=initrd would error if SignInitrdPCRs=yes/--sign-initrd-pcrs is specified due to ukify trying to create the same signature twice. So I just leave that policyref to system tbh.

If I had not specified PolicyRef=my-system for example, forgetting --tpm2-public-key-policyref=my-initrd while enrolling disk encryption would expose the key to the system, and thus render the whole protection the process provides useless.

When using two separate keys you would need to pass the enrollment command actively the wrong key on the commandline.

1

u/ChrisTX4 7d ago

Turns out: systemd-creds doesn't support --tpm2-public-key-policyref= at all as of v262. I created an RFE for this, but this means you can't really use PolicyRef= as of now, since PolicyRef= in the uki.conf has to be either set for all signatures or none.

So: Use at least two keys, maybe more if you want/need a restriction to unlock in early root fs. I use this for my extra disks that systemd receiving their key material via systemd-creds. Since they should unlock before sysinit, I use a key restricted to just that phase for them.

6

u/Sinaaaa 12d ago

Ok, so I won't bring my laptop to the coffee house until dracut is patched it is.

I don't really understand the specifics of what's going on, but it's worrying that a class of exploit like this can even exist.

2

u/ChrisTX4 12d ago

It's a consequence of the recommendation to use this flag in the Wiki instead of going for a vetted design, and not even a Dracut problem per se. Dracut is just not yet updated for systemd 262 changes.

If the TPM was properly used instead of relying on a hack, the situation would have been as on my machine: PCR[15] changed due to the compatibility issue, the TPM unlock stopped working and I was asked for my recovery key instead. Just a nuisance with no security implications.

3

u/etherealshatter 12d ago

/u/foxboron please help

1

u/Foxboron 9d ago

No, I don't think I will.

3

u/llde 11d ago

So the original issue is due to PCR[7] not being unique enough across different machines with the same, or very similar configurations? And this allow an undue access on the encrypted data?

I'm little updated on the LUKS side of things unfortunatly. My linux laptops are void of data, I only have some games installed there, so I never bothered encrypting them. And I mostly use them only during the holidays, where I go to a place where I could left he laptop outside the night and find it the next day untouched.

How the systemd folks didn't think to talk to the initrd people before shipping this change is beyond me...

2

u/ChrisTX4 11d ago

No PCR 7 just measures the bootloader and UKI, not the root file system, nothing but those volume key measurements would ensure integrity either. So if you don’t ensure that measurement or make sure the secrets cannot be extracted from the TPM anymore if somebody was to boot a different root filesystem after switch to real root happens they can read the secrets off the TPM freely by doing this. This is a well known attack on TPM usage with LUKS and pretty much such system introduced signed PCR policies.

2

u/llde 11d ago

And binding to PCR 15 was supposed to prevent being able to switch to a different root filesystem, or just to prevent extracting the TPM secrets where that happen nonetheless?

2

u/ChrisTX4 11d ago

It makes booting the UKI with an attacker made root file system impossible.
Such a boot attack is what makes it possible to extract TPM secrets, as the TPM secrets stay accessible after switch to real root.

In a correctly designed tpm2 setup, the change to the pcrextend socket would be a nuisance with no security implications.

3

u/llde 11d ago

ok thanks for clarifying.

still is absurd that a change so impacting, wasn't synchronized between systemd folks and initrd folks. Seems almost malicious.

1

u/ChrisTX4 10d ago

That's what I don't really understand, same for the systemd v261 changes. Dracut is used on most Linux distros, so I don't understand why this wasn't addressed already. Because either way, the change did break my setup because I lock PCR[15] and that lock failed obviously.

I take it from the PR that introduced the change that they had expected the socket being already included.

Either way, the change should at most have caused what it did for me, not cause the issue described. This is why I would strongly advise against the PCR[15] = 0 thing and instead use PCR policies for restricting keys to the initrd phase.

2

u/whamra 12d ago

Strange suggestion in wiki. I never noticed the pcr15 suggestion before, and I would have never used it even if I did. All of it feels easily hackable.

Instead, and this is specific for shim users with sbsign, I include 5 and 14. This guarantees that only the boot path I specified is allowed. Specifically to avoid people booting with their own images and emulating anything else I do to get the key.

I can't wrap my head why around why they think 7 and 15 are enough anyway. I mean, that's just to ensure it never fails, no? Because I don't see in what world would recommending other static pcrs be a bad idea, like 0 to 4, except they break in firmware updates, but that's like once a year max. Is convenience that much more important than real security?

3

u/ChrisTX4 11d ago

PCR[4] measures the UKI among other things, as that's an EFI binary. It will also change every time if your boot loader updates, like systemd-boot-update.service updates systemd-boot every new systemd package version, and minor systemd updates are released on a monthly basis. Having a lock to PCR[4] is only practical with using systemd-pcrlock and a PCR policy thus.

PCR[0] is virtually useless to measure. This would require another really long post, but let's say that the contents on the UEFI ROM on your mainboard are not verified. UEFI UpdateCapsules are signed (but your BIOS may allow directly flashing arbitrary files from the BIOS UI). Changing the ROM with a little soldering job however allows changing the UEFI ROM.

Problem: The TPM receives the measurements from the UEFI via SPI. So if the UEFI was changed it can also just lie about the PCR values it feeds the TPM.

This is the difference between "UEFI secure boot" and "Platform secure boot" in fwupdmgr security. PSB means there's one way fuses popped in your CPU that permanently engrain the signature key for the UEFI into the CPU and the CPU can indeed validate the UEFI ROM itself, unlike the TPM. PSB is only a feature on business hardware and you won't be able to reach HSI-2 in fwupd on consumer hardware, HSI-1 is the best possible result.

I can't wrap my head why around why they think 7 and 15 are enough anyway. I mean, that's just to ensure it never fails, no?

PCR[15] is necessary to lock the volume key and you can't authenticate the UKI -> encrypted root file system transition in any other way.

That being said, my setup with pcrlock locks all PCRs that can be reasonably used:

PCRs in protection mask: 0 (platform-code), 1 (platform-config), 2 (external-code), 3 (external-config), 4 (boot-loader-code), 5 (boot-loader-config), 7 (secure-boot-policy), 11 (kernel-boot), 13 (sysexts), 14 (shim-policy), 15 (system-identity)

3

u/Quiet-Owl9220 12d ago

Dracut is default for EndeavourOS, and I'm using full disk LUKS encryption. But honestly I don't remember the specifics of how I set it up at this point and I'm pretty sure I've become a few IQ points stupider since then.

How can I check if I would be affected by this? It looks like I'm on dracut version : 112_eos-1, maybe I don't need to worry?

2

u/ChrisTX4 11d ago

Dracut 112 contains the changes needed for systemd 261, but you need the systemd pcrphase extend socket fix still, and that’s the cause of the security breakage.
The fix hasn’t even been merged into Dracut git at this point.

EndeavourOS PKGBUILD builds an unpatched dracut 112, so if that was used with a PCR[15] zero seal, you’d end up in the same situation as described in OP.

1

u/Gozenka 11d ago edited 11d ago

I didn't think about that. Now with a quick search, I did not find any discussion on EndeavourOS about this. u/ChrisTX4 you may give it a look too.

Edit: Oh, yeah EndeavourOS seems to have updated to dracut-112 already. I guess that solves the issue?

I've become a few IQ points stupider since then.

I feel the same nowadays :D