Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

preinstall: move some extra TPM tests to only run during pre-install #340

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

chrisccoulson
Copy link
Collaborator

@chrisccoulson chrisccoulson commented Oct 6, 2024

(Draft until #339 has landed)

The RunChecks API will support being executed post-install even
though it's primarily a pre-install API. A use case for this is re-running
it as part of the post-recovery repair process. Not all tests makes sense
post-install, unless a motherboard (and TPM) change was the reason for
recovery, and so are skipped.

We were already skipping the lockout hierarchy auth value test during
post-install because this doesn't make sense, but also skip similar
tests for other hierarchies and also skip the lockout checkout and the
test for sufficient number of NV counters.

This adds some checks for PCR7.

The caller supplies a context.Context to which an EFI variable backend
is attached, a internal_efi.HostEnvironment implementation, a TCG log, a
PCR digest algorith (the optimum for this is computed earlier by
another function that does a more general check of the TCG log) and the
image of the initial boot loader for the current boot.

As we're testing the firmware, this only checks the log up to the launch
of the initial boot loader. It ignores events after this, as these are
under the control of the OS and don't rely on any special firmware
features such as EFI_TCG2_PROTOCOL + PE_COFF_IMAGE vs EFI_TCG_PROTOCOL
in the same way that OS components measuring to PCR4 do.

This ensures that secure boot is enabled, else an error is returned, as
WithSecureBootPolicyProfile only generates profiles compatible with
secure boot being enabled.

If the version of UEFI is >= 2.5, it also makes sure that the secure
boot mode is "deployed mode". If the secure boot mode is "user mode",
then the "AuditMode" and "DeployedMode" values are measured to PCR7,
something that WithSecureBootPolicyProfile doesn't support today.
Support for "user mode" will be added in the future, although the public
RunChecks API will probably require a flag to opt in to supporting user
mode, as it is the less secure mode of the 2 (see the documentation for
SecureBootMode in github.com/canonical/go-efilib).

It also reads the "OsIndicationsSupported" variable to test for features
that are not supported by WithSecureBootPolicyProfile. These are
timestamp revocation (which requires an extra signature database -
"dbt") and OS recovery (which requires an extra signature database -
"dbr", used to control access to OsRecoveryOrder and OsRecover####
variables). Of the 2, it's likely that we might need to add support for
timestamp revocation at some point.

It reads the "BootCurrent" EFI variable and matches this to the
EFI_LOAD_OPTION associated with the current boot from the TCG log - it
uses the log as "BootXXXX" EFI variables can be updated at runtime and
might be out of data when this code runs. It uses this to detect the
launch of the initial boot loader, which might not necessarily be the
first EV_EFI_BOOT_SERVICES_APPLICATION event in the OS-present
environment in PCR4 (eg, if Absolute is active).

First of all, it iterates over the secure boot configuration in the log,
making sure that the configuration is measured in the correct order,
that the event data is valid, and that the measured digest is the tagged
hash of the event data. It makes sure that the value of "SecureBoot" in
the log is consistent with the "SecureBoot" variable (which is read-only
at runtime), and it verifies that all of the signature databases are
formatted correctly. It will return an error if any of these checks
fail.

If the pre-OS environment contains events other than
EV_EFI_VARIABLE_DRIVER_CONFIG, it will return an error. This can happen
if a firmware debugger is enabled, in which case PCR7 will begin with a
EV_EFI_ACTION "UEFI Debug Mode" event. This case is detected by earlier
firmware protection checks.

If not all of the expected secure boot configuration is measured, an
error is returned.

Once the secure boot configuration has been measured, it looks for
EV_EFI_VARIABLE_AUTHORITY events in PCR7, until it detects the launch of
the initial boot loader. It verifies that each of these come from db,
and if the log is in the OS-present environment, it ensures that the
measured digest is the tagged hash of the event data. It doesn't do this
for events in the pre-OS environment because WithSecureBootPolicyProfile
just copies these to the profile. It verifies that the firmware doesn't
measure a signature more than once. For each EV_EFI_VARIABLE_AUTHORITY
event, it also matches the measured signature to a EFI_SIGNATURE_LIST
structure in db. If the matched ESL is a X.509 certificate, it records
the use of this CA in the return value. If the CA is an RSA certificate
with a public modulus of <= 1024 bits, it sets a flag in the return
value indicating a weak algorithm. If the matched ESL is a Authenticode
digest, it sets a flag in the return value indicating that pre-OS
components were verified using digests rather than signatures. This
makes PCR7 fragile wrt firmware updates, because it means db needs to
be updated to reflect the new components each time. If the digest being
matched is SHA-1, it sets the flag in the return value indicating a weak
algorithm. If any of these checks fail, an error is returned. If an
event type other than EV_EFI_VARIABLE_AUTHORITY is detected, an error is
returned.

Upon detecting the launch of the initial boot loader in PCR4, it
extracts the authenticode signatures from the supplied image, and
matches these to a previously measured CA. If no match is found, an
error is returned. If a match is found, it ensures that the signing
certificate has an RSA public key with a modulus larger than 1024 bits,
else it sets a flag in the return value indicating a weak algorithm.
Once the event for the initial boot loader is complete, the function
returns.

If the end of the log is reached without encountering the launch of the
initial boot loader, an error is returned.
This moves the code to read the current boot load option from the log
into a helper function, as the same code was used both in the PCR4 and
PCR7 checks.
Hopefully this will address any outstanding review comments.
CheckResult is returned from RunChecks on successful completion. It is
JSON serializable and intended to be supplied later on to
WithAutoTCGPCRProfile along with some user customization flags (defined
by PCRProfileOptionsFlags) in order to generate an option that can be
supplied to secboot_efi.AddPCRProfile.

Note that some user options don't work yet because of missing PCR
support, although these limitations will be addressed later. Even so,
some options may still fail depending on the CheckResult flags.
RunChecks is the first main entry point for running all of the platform
tests included in this package. It is considered to be quite a low
level and a future PR is going to add a higher level API that is easier
to use (RunChecksContext)

It can be customized with the CheckFlags argument, and the caller must
supply a list of images loaded during the current boot, in the correct
order.

On failure, it will return an error. In some cases, it fails early. In
other cases, it carries on and collects individual errors which are
then returned wrapped in RunChecksError. This can be obtained with the
errors.As API in order to obtain individual errors.

On success, it will return a CheckResult. Any errors that were
encountered but are considered non-fatal based on the supplied
CheckFlags will he added to the Warnings field of CheckResult.

The intention is that CheckResult is persisted and then used by an API
(to be added in the next PR) along with some user customization flags in
order to select the most appropriate set of TCG defined PCRs to seal
against. This can still fail if an appropriately secure set of PCRs
cannot be selected, even if RunChecks succeeded. The future
higher-level RunChecksContext API will make this case easier to handle
and will also help remove the burden of deciding which set of flags
to pass to RunChecks.
We were already skipping the lockout hierarchy auth value test during
post-install because this doesn't make sense, but also skip similar
tests for other hierarchies and also skip the lockout checkout and the
test for sufficient number of NV counters.
@chrisccoulson chrisccoulson force-pushed the preinstall-skip-some-tpm-tests-post-install branch from e3f1bc2 to 4701bb5 Compare November 13, 2024 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant