Hi Cedric,
> [...] Here are the measurements I collected from the
> TPM event log
>
> /* efibootguard measured in PCR[4] */
> PCR 4 -- Event 80000003 <TpmEventType.EFI_BOOT_SERVICES_APPLICATION>
> Extend (SHA1): 3877b5f89f59bc5d934ba4b26672fea0ffb0806c
> Path vector:
> * ACPIDevice ACPI b'\xd0A\x03\n\x00\x00\x00\x00'
> * HardwareDevice PCI b'\x00\x10'
> * MessagingDevice SCSI b'\x00\x00\x00\x00'
> * MediaDevice HardDrive
> b'\x01\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\\&\x0b\xa9\xa7\xee\xf1C\xb4".\xd3M\xf6\xe6Y\x02\x02'
> * MediaDevice FilePath \EFI\BOOT\BOOTX64.EFI
> * End 255 b''
>
> /* our measurement of the specified kernel image in PCR[8]
> * note: this is a raw measurement (i.e. SHA1 of the entire file without
> interpreting e.g PE/COFF sections)
> */
> PCR 8 -- Event 0000000D <TpmEventType.IPL>
This event type is deprecated, see, e.g., "TCG PC Client Platform
Firmware Profile Specification", Family "2.0", Level 00 Revision 00.49
from 2017 or any later revision.
> Extend (SHA1): e42ee38fd3cf1f7d310f00e4331628b0a188ef3a
> 0x00000000: 4D 5A 90 00 03 00 00 |MZ..... |
> -- not touching non-systemd IPL event --
> --> after this event, PCR 8 contains value
> 58add887d84dd4be97a32a6ba6cb5535f84a7663
> --> after reboot, PCR 8 will contain value
> 58add887d84dd4be97a32a6ba6cb5535f84a7663
>
>
> /* LoadImage() still measures our kernel image in PCR[4]
> * note: EFI knows it is a PE/COFF binary and only relevant sections are
> hashed
> */
> PCR 4 -- Event 80000003 <TpmEventType.EFI_BOOT_SERVICES_APPLICATION>
> Extend (SHA1): a9823f0033e9cb2c30019b4bab7a9ee31071debf
> Path vector:
> * HardwareDevice MemoryMapped
> b'\x02\x00\x00\x00\x18\xc0\x10\x0b\x00\x00\x00\x00\x10\xcd\xbd\r\x00\x00\x00\x00'
> * End 255 b''
>
> /* our measurement of the kernel command line into PCR[9] */
Why did you chose PCR #9 here? According to my shared list of who
measures to what register(s), there's at least systemd-boot and
rhboot/grub2 measuring Strings to PCR #8. Any reason to deviate?
> PCR 9 -- Event 0000000D <TpmEventType.IPL>
> Extend (SHA1): 1985ccaadef02e21d2f161311bd35ea4ce89676e
> 0x00000000: 72 00 6F 00 6F 00 74 00 77 00 61 00 69 00 74 00 |r.o.o.t.w.a.i.t.|
> 0x00000010: 20 00 71 00 75 00 69 00 65 00 74 00 20 00 6E 00 |..q.u.i.e.t...n.|
> 0x00000020: 6F 00 68 00 7A 00 3D 00 6F 00 66 00 66 00 20 00 |o.h.z.=.o.f.f...|
> 0x00000030: 73 00 65 00 63 00 75 00 72 00 69 00 74 00 79 00 |s.e.c.u.r.i.t.y.|
> (125 more bytes)
>
> So as noted before, the kernel image gets measured twice...
>
> > > > Probably, PCRs #8 - #15 "Defined for use by the Static OS" (according to
> > > > the spec) are a better match for measuring the OS kernel, additionally.
> > > > You're measuring the kernel command line to this range already.
> > > This would have been my preference as well but again LoadImage() isn't
> > > giving an option to select a different PCR (unless I missed it).
> > No, that's not foreseen, but see above.
> >
> > > What we could do is side-load the UEFI image and HashLogExtend it
> > > ourselves to PCR[8] but LoadImage would still be needed and would also
> > > measure it and extend PCR[4].
> > Yes, you can measure the binary to PCR #8 as well, then however mashing
> > up the kernel binary and the kernel's cmdline into one PCR. Whether
> > that's sensible or not is the question I asked for referring to the
> > overarching concept you'll want to follow here, i.e., what information /
> > deduction does it give you and what is it you're after (as just
> > measuring is not an end in itself)?
>
> Our concept is twofold: (1) use SecureBoot to make sure we are booted from a
> trusted boot-loader + trusted Linux kernel (both are signed with keys loaded
> into the trust store) (2) extend the root of trust by mounting a LUKS
> encrypted volume with the key in the TPM and sealed with selected PCRs. In an
> A/B scheme, we may have the same encryption key placed both in slot[A] and
> slot[B] but sealed as follows:
>
> * for A: PCR[4] == HashOf(efibootguard.efi) + PCR[8] == HashOf(kernelA) +
> PCR[9] == HashOf(optionsA) + other relevant PCRs (e.g. BIOS, EFI variables,
> etc.)
> * for B: PCR[4] == HashOf(efibootguard.efi) + PCR[8] == HashOf(kernelB) +
> PCR[9] == HashOf(optionsB) + other relevant PCRs (e.g. BIOS, EFI variables,
> etc.)
>
> Note that for our purpose (and I understand we may now want restrict ourselves
> to that specific use-case), the following sealing would have worked:
>
> * for A: PCR[4] == Extend(HashOf(efibootguard.efi), HashOf(kernelA)) + PCR[9]
> == HashOf(optionsA) + other relevant PCRs (e.g. BIOS, EFI variables, etc.)
> * for B: PCR[4] == Extend(HashOf(efibootguard.efi), HashOf(kernelB)) + PCR[9]
> == HashOf(optionsB) + other relevant PCRs (e.g. BIOS, EFI variables, etc.)
>
> since we don't really need/want to know which binary was tampered but simply
> to maintain the root of trust ("yes the booted kernel is the one I expected, I
> may mount my root file-system and run my user-land stack)
If the (unified) kernel (image) has booted and you're in initramfs, then
you've booted an eligible kernel ― otherwise you have more severe
problems such as compromised keys. What you're doing here is tying the
kernel to the firmware/hardware so that only this specific kernel (out
of a set of binary-different but properly signed and eligible kernels)
can be booted. Is this by intention?
Next is to find out whether the root filesystem is eligible as well,
i.e., assert the root filesystem integrity. This you infer from it being
unlocked. You could've used dm-verity with a kernel image embedded root
hash for this as well.
So, your use case is entirely solvable without involving measurement or
the TPM at all.
> > > > That said, what's the overarching concept behind this? Just measuring
> > > > "something" is not an end in itself. How is that supposed to be sensibly
> > > > used (e.g., by the OS) to actually leverage the measurements? According
> > > > to this answer, the measurement(s) should be performed by EFI Boot Guard
> > > > to support the intended idea/concept.
> > > We are wanting to get boot artifacts measured so we can locally attest
> > > that we
> > > have booted into a well-known system and may safely unseal a secret sealed
> > > into the TPM. Our secret would be sealed with at least PCR[4] and PCR[8].
> > > In
> > > our case, we don't really need to say which element was tampered, we just
> > > want
> > > to prevent the bad guy from reading our secret.
> > OK, but that ties you to this particular kernel binary (PCR#4) and this
> > particular cmdline (PCR#8). If you now change something, think firmware
> > update, you have to re-seal the secret which is a delicate operation to
> > perform in the context of a firmware update transaction...
> or seal the same encryption key using a different slot in your TPM so you
> don't change anything in your current configuration. If something in your new
> system wasn't done right, all pieces to revert to the old system should still
> be in place
This would require logics to detect which one to actually use to unseal
the secret and at least two slots in the TPM, or just trial and error.
Still, you have to seal the new secret while a firmware update transaction.
This way demands that you can under all circumstances rule out any side
effects to the current boot path in case something goes wrong.
> > That said, if you have Secure Boot enabled and use unified kernel
> > images, what does measuring the cmdline to PCR#8 give you in addition to
> > just sealing the secret with ― probably among others ― PCR#4 and thereby
> > bind it to (at least) the unified kernel image ― which includes the
> > cmdline as PE/COFF section?
>
> in my experiments, I was able to override the kernel command line with
> bg_setenv -a. this is probably a "feature" of systemd's stub (maybe this would
> not happen when directly building the kernel as an EFI binary but I am
> guessing that efibootguard should not assume a specific unified kernel image
> implementation?)
It doesn't. EFI Boot Guard supplies the command line from its
environment to the callee, unconditionally. The callee can then decide
whether to honor it or not. EFI Boot Guard is also not concerned with
the callee's format or shape as long as it's a valid PE/COFF UEFI binary
that passes Secure Boot checks if that's enabled. After all, it just
uses standard UEFI mechanisms to load the callee and start it. So, you
can also load other OSs or the EFI Shell or whatever as EFI Boot Guard's
callee. It just has to be a valid PE/COFF UEFI binary and there is
nothing else imposed on it.
The Linux kernel itself has an EFI stub loader ― enabled if compiled for
UEFI as target ― that makes it a valid PE/COFF UEFI binary. Unified
Kernel Images are just needed to pack everything in one fat binary to
allow Secure Boot checks for the stuff typically not included in the
Kernel binary itself, read: command line, splash, initramfs, ... You can
bake those into the Kernel itself at build-time but that would demand
a rebuild of, e.g., a distro's kernel to do so and that is precisely
why systemd has stub.c: to allow for simple repackaging instead of
rebuilding.
Unified Kernel Images built with systemd's stub loader only accept
external, read command line options *not* from its PE's built-in
.cmdline section, if Secure Boot is not enabled and there are actually
external while not internal command line options, see:
https://github.com/systemd/systemd/blob/main/src/boot/efi/stub.c#L58
So you may double-check whether you've done your experiments with
Secure Boot enabled or not and whether you have a baked in .cmdline
section or not...
Kind regards,
Christian
--
Dr. Christian Storm
Siemens AG, Technology, T RDA IOT SES-DE
Otto-Hahn-Ring 6, 81739 München, Germany
--
You received this message because you are subscribed to the Google Groups "EFI
Boot Guard" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/efibootguard-dev/20210719135948.jzfowk74uezeqwqo%40MD1ZFJVC.ad001.siemens.net.