Hi Jarkko,
Hi James,

first of all, sorry for hijacking a thread from 2 years ago.

On Thu, Nov 14, 2024 at 12:34:30AM +0200, Jarkko Sakkinen wrote:
> On Wed Nov 13, 2024 at 8:12 PM EET, James Bottomley wrote:
> > > I think we might have to expect the NULL name to change on actual
> > > hibernation because unlike suspend to ram it does power off the TPM.
> >
> > I checked the code: we're coming in on the correct path to renew the
> > null seed after hibernation, so it should all work.  The problem seems
> > to be that your TPM itself is doing something invalid because the name
> > we calculate for the primary key doesn't match what your TPM says it
> > should be.  Absent some form of attack or bus integrity problem, that
> > shouldn't ever happen, so I'm even more curious to know why it worked
> > in 6.11.5 and before and whether current upstream works.
> >
> > I haven't found it yet, but I think the every 10s signature is because
> > the hibernation path is trying to restart the TPM device and won't take
> > no for an answer.
> 
> My fix returned the behavior how it was before my earlier fix in this
> corner case (i.e. disable TPM). The issue has gone unnoticed before
> since it has emitted only a single klog entry.
> 
> On suspend this has not happened to me so obvious deduction is that
> hibernate resets the null seed.
> 
> Hibernate needs an addition a fix to disable bus encryption from kernel
> command-line completely, i.e. tpm.disable_integrity following the
> convention from my earlier fix [1].

I'd like to offer a way it might be resolvable with the null key after
all, without provisioning a persistent NV key -- by changing the
question from "re-derive the null primary and compare" to "inherit the
trust the resume has already established".

Resume-from-hibernation is a TPM Restart (Shutdown(STATE) ->
Startup(CLEAR)), i.e. a firmware cold-init of the (f)TPM, after which
the boot/initramfs kernel establishes a fresh, genuine null primary.
In the common configuration (FDE with the resume/swap device inside
a TPM-sealed LUKS2 container) that same TPM has, moments earlier and
*before* the hibernation image is restored, cryptographically attested
itself by unsealing the resume device. A substituted or interposed TPM
cannot produce that unseal.

So rather than letting the resumed kernel re-derive the null name,
find a mismatch and disable the chip, the boot kernel's
freshly-established and unseal-validated null primary could be
inherited by the resumed image. The existing null-seed TOFU model is
preserved; nothing new is provisioned.

The gate is the unseal, and the adversary case shows why it is the
right gate:

  Malice swaps (or interposes on) the TPM while the machine is
  hibernated, then leaves. Alice powers on. The initramfs attempts the
  TPM unseal of the resume device; with a foreign TPM it fails, so
  systemd-cryptsetup falls back to the passphrase, which Alice --
  seeing a prompt -- types. The disk opens and the system resumes.

If trust were re-established here, Alice would have personally vouched
for Malice's TPM. But the unseal *failed*, so under this scheme
nothing is inherited and the chip stays fail-closed exactly as today.
The passphrase proves a human is present; it never proves the TPM is
the genuine one.
Hence: unseal succeeded -> inherit the validated null primary;
passphrase fallback -> trust is lost, stay disabled.

This keeps the property the null-seed design wants -- an in-session
reset is not on the hibernate-restore path and is still caught --
while removing the false positive only where the platform has already
re-attested the TPM.

The hard parts, and where I'd value direction:

 - systemd-cryptsetup would need to signal "the resume device was
   unsealed by the TPM this boot" (vs. the passphrase fallback).
   This is per-resume runtime state; a static command-line parameter
   (and obviously build-time config as well) cannot represent it.

 - the validated null primary has to cross the boot -> resumed memory
   discontinuity (the initramfs kernel's state is overwritten by the
   restored image). Boot and image kernel are the same binary, so
   patching chip->null_key_name in the restored image is mechanically
   possible; a small reserved/nosave hand-off area may be cleaner.
   I don't know the hibernate path well enough to say which is right.

It is admittedly cross-subsystem (tpm + pm/hibernate +
systemd/cryptsetup), which is presumably why it hasn't been done.
Compared with the persistent NV-key route
(tpm.integrity_key=<handle>): that avoids the carry-across but needs
the key provisioned and managed, and a persistent key's name no longer
changes on a genuine reset, so the implicit reset detection has to be
reconstructed. The null-key-inherit approach keeps the existing model
and defers "is this the same TPM?" to the unseal that has already
happened.

Does this seem viable, or is there a reason the unseal-as-attestation
gate does not hold that I'm missing?

(For motivation: on a firmware TPM -- Intel PTT here -- there is no
external bus to interpose, so the protection has no benefit on this
class of hardware at all, yet the legitimate hibernation power-cycle
still trips the disable. For fTPMs specifically, not enabling the
feature is arguably the better answer; but for discrete TPMs that
hibernate, a real solution seems a good idea if doable.)


Cheers


Daniel

Reply via email to