Repository navigation
Conversation
|
LGTM. A couple of small questions while reading the sync and the signed first-write path: startNvPCRSetup() runs after udevListener is already going. On a machine where /dev/tpmrm0 is already there (built-in driver), could a TPM unlock call awaitNvPCRSetup() before the WaitGroup has been Added, see a zero counter, and continue while setup is still about to start? Would it be safer to Add(1) before starting udev (or use a latch that starts “not ready”)? For LUKS signed unseal you still try RSA exponents 0x10001 and then 0, but NvPCR init seems to use only key.E. Is that enough on every TPM you care about, or is the second exponent only needed for the unseal path? |
|
You're absolutely right. I missed it and looking into it I found a similar but unrelated bug I'll address in another PR. The fix starts setup before udev. On the exponent: the 0 retry on unseal is for tokens from systemd versions that stored the default RSA exponent as 0 (systemd issue 30546); systemd's own unseal does the same retry. NvPCR indexes are new in 262, and systemd encodes 0x10001 when it builds them, so key.E matches. I checked that booster and systemd 262 produce byte-identical index Names and .auth from the same key. If a Name ever differed, booster would redefine the index rather than fail. |
|
Two things I am not sure you have already seen:
Also, |
|
|
LGTM |
tpmAwaitReady waits for the uevent that announces /dev/tpmrm0. When the TPM driver is built into the kernel, the device is created before booster starts listening for uevents, so the event never comes and every TPM2 unlock waits out the full 3 second timeout. Return right away when /dev/tpmrm0 already exists.
Before unsealing a signed TPM2 token bound to PCR 11, booster extends PCR 11 with "enter-initrd". That step opened the TPM without waiting for it; only the unseal waited. When the TPM driver is a module, the disk can appear before the module is loaded, so the extend fails with "stat /dev/tpmrm0: no such file or directory" and booster falls back to the passphrase prompt. Move the wait from the unseal to the start of systemd-tpm2 token handling, so it covers the extend, the PIN prompt and both unseal paths. If no TPM appears, fail the token there instead of asking for a PIN.
The Void Linux test kernel builds tpm_tis as a module and NVMe into the kernel. With the disk on NVMe, booster finds the LUKS device before the TPM exists, which is the case the previous commit fixes. The kernel the other tests use has the TPM driver built in, so it cannot hit this case.
systemd-measure --policyref signs the policy digest followed by SHA256 of the reference, and ukify's SignInitrdPCRs=yes adds such an "initrd" entry next to the plain one. Booster ignored the ref, so it could pick the "initrd" entry for a plain token and fail to verify it. Select entries by ref as systemd does, include the ref in the signed data and PolicyAuthorize, and honour tpm2_pubkey_ref on tokens enrolled with --tpm2-public-key-policyref.
systemd 262 only initializes NvPCRs in the initrd, so init needs the definitions systemd would load on the host. Copy the winning copy of each /usr/lib/nvpcr, /usr/local/lib/nvpcr and /etc/nvpcr definition into /usr/lib/nvpcr, skipping masked names. Do this only for systemd 262 and later. Versions 259 to 261 initialize NvPCRs on the host with an anchor secret and reject an index defined the 262 way.
systemd 262 only allows the first write to an NvPCR in the initrd, so on a booster boot the host's NvPCR units fail. On a measured UKI boot, define each bundled NvPCR, do its first write under the UKI's "initrd" signature or else bound to the current PCR 11 as systemd 263 does, write the .auth file the host extends with, and record it in PCR 9. go-tpm has no NV_Extend command type and keeps its executor private, so the command is marshaled by hand. TPMs that hang on NV_Extend are skipped like systemd's hwdb quirk, and systemd.tpm2_measured_os= is honoured.
An extend of PCR 0 to 15 between a PolicyPCR and the command that uses the session fails that command with TPM_RC_PCR_CHANGED. The PCR 15 latch, the leave-initrd extend and the TPM2 and clevis unlocks now wait for the NvPCR setup, as systemd orders it before cryptsetup. Resume waits too, since the resumed kernel uses the NvPCRs this boot sets up. The wait gives up after 30 seconds, so a TPM that hangs in NV_Extend delays the boot instead of stopping it.
The generator bundles the host's NvPCR definitions, so on systemd 262 this boots with systemd.tpm2_measured_os=1 and expects the NvPCRs and the PCR 9 separator to be set up before each TPM user runs: a systemd-tpm2 unlock, a clevis TPM2 unlock and a resume. The hibernate test now keeps one swtpm state across both boots, as hibernating powers the TPM off, and checks that the resumed system can still extend its NvPCRs after the resume boot sets them up again.
Document systemd.tpm2_measured_os= and note that a measured UKI boot initializes systemd's NvPCRs, with SignInitrdPCRs=yes keeping their names stable across kernel updates.
Every NvPCR's first write loaded the PCR key and had the TPM verify the "initrd" signature again, about 500 ms per index on a Nuvoton dTPM. All writes in one setup approve the same PCR 11 digest, so the first ticket now satisfies PolicyAuthorize for the rest. The ticket only attests that the key signed the digest. Each session still runs PolicyPCR against the live PCR 11, and the TPM rejects the ticket once PCR 11 moves, which a test pins.
The setup registered itself only after the udev listener started. A disk that appeared while force-loaded modules were loading could reach a TPM unlock, the PCR 15 latch or resume before that, find no setup to wait for, and run ahead of it. Starting the setup first closes the window. The signed unseal retries RSA exponent 0 for tokens from systemd versions that stored the default exponent that way, as systemd's own unseal does. The NvPCR policy needs no such retry: systemd builds it from the PEM exponent unchanged and NvPCRs are new in 262, so key.E matches what systemd writes.
tpmReadyWg was armed inside udevListener, which starts after the NvPCR setup, so on a modular TPM the setup could wait on a zero counter, fail to open /dev/tpmrm0 and give up. Arm it in boost before either starts. Even armed, the 3 second wait started at boot, while the driver is only requested after plymouth has started, which can take up to 5 seconds. Start the wait once plymouth is up, and warn when no TPM is found.
bfe94af to
09fb903
Compare
|
@pilotstew @enihcam is it reviewed/settled? are there any objections to merge it? |
|
It's ready but based on top of #434. Which should also be good to go. |
systemd 262 moved the first write of each NvPCR into the initrd, so on a booster boot the host's NvPCR units fail and the
hardware,cryptsetup,loginandverityNvPCRs stay unusable. This does in booster what systemd-tpm2-setup --early and systemd-pcrnvdone do in a systemd initrd./usr/lib/nvpcr, on systemd 262 and later only. Versions 259 to 261 initialize NvPCRs on the host with an anchor secret instead..authfile the host extends with, and records each one plus a separator in PCR 9.systemd.tpm2_measured_os=overrides the default.SignInitrdPCRs=yesneeds.Testing:
hardwareandlogin.This is stacked on the open TPM wait PR; its three commits come first.
let me know what you think @enihcam