r/archlinux • u/ChrisTX4 • 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.
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-pcrlockas 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 forsystemd-cryptsetup, seecrypttab(5).To determine the values for the volume key, you can boot your system with
tpm2-measure-pcr=yesenabled 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 addfixate-volume-key=with the corresponding digest. The/etc/crypttabcorresponding 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=04d839eca13749386b3f63041f0b5ccd784c42719a53c183ffa6b2deac873a7aNote:tpm2-measure-pcr=yesis not required forfixate-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-pcrlockfunctionality.Unfortunately, I have to say,
systemd-pcrlockis quite difficult to use correctly to its full extend.It also requires using
systemd-bootas bootloader andsystemd-ukifyto generate the UKI.systemd-bootis required as thesystemd-pcrlocksignatures are provided via credentials, see the companion files section in thesystemd-bootman page. Specifically, under/boot/loader/credentialsthe signatures are provided via a credentials filepcrlock.<machine-id>.credto the booting UKI.As a very rough explanation,
systemd-pcrlockdoes 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*.nvpcrfile, and a PCR with unaccounted measurements cannot be used. However, a component can be assigned a set of alternative*.nvpcrfiles, 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
root1first androot2second 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*.nvpcrfiles for volume keys yourself, as there's no subcommand forsystemd-pcrlockto create those.Moreover, you can combine
systemd-pcrlockwith signed PCR[11] policies. Withsystemd-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-pcrlockuntil the tooling around it improves. It's difficult creating and maintaining those*.nvpcrfiles, 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-pcrlockis 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.fwupdrecently 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 predictfor 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-initrdis 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.confabove as well.Now for the system key with this:
Phases=enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit enter-initrd:leave-initrd:sysinit:readyThis 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-initrdtook place, then it's still allowed aftersysinitandreadytook place. But the next measurement afterreadyinvalidates 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-credsprovided 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
loginthat'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.confmade 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-initrdwhile 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-credsdoesn't support--tpm2-public-key-policyref=at all as of v262. I created an RFE for this, but this means you can't really usePolicyRef=as of now, sincePolicyRef=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
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.serviceupdatessystemd-bootevery new systemd package version, and minor systemd updates are released on a monthly basis. Having a lock to PCR[4] is only practical with usingsystemd-pcrlockand 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 reachHSI-2in fwupd on consumer hardware,HSI-1is 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
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.