Page MenuHomeFreeBSD

loader.efi: Measure boot policy and loaded contents with a TPM
Needs ReviewPublic

Authored by kbowling on Mon, Aug 24, 7:07 AM.
Tags
None
Referenced Files
F170661187: D59141.diff
Sat, Sep 5, 9:17 PM
F170600993: D59141.id.diff
Sat, Sep 5, 2:28 PM
F170563501: D59141.id.diff
Sat, Sep 5, 10:33 AM
Unknown Object (File)
Sat, Sep 5, 12:20 AM
Unknown Object (File)
Fri, Sep 4, 11:22 PM
Unknown Object (File)
Fri, Sep 4, 7:08 AM
Unknown Object (File)
Thu, Sep 3, 10:13 PM
Unknown Object (File)
Thu, Sep 3, 10:08 PM

Details

Summary
Use the EFI TCG2 protocol, or the legacy TCG protocol on TPM 1.2
firmware, to extend the loader PCRs and record EV_IPL events in the
firmware event log.

Extend PCR 8 with the final kernel environment and the type, name, and
arguments of each loaded boot input.  Prefix both record types with
versioned, NUL-terminated domain strings.  These make the records
self-identifying and define an explicit measured policy encoding.  Store
the complete measured pre-images in the event data so attestation and
diagnostic tools can audit the values without an out of band copy.

Extend PCR 9 with the initialized contents of kernels, modules, and raw
boot inputs, but record only descriptive labels rather than copying file
contents into the event log.

The Lua module table has no defined traversal order.  Retain content
ranges as loader-private metadata, then sort the final file list before
extending either PCR.  This produces repeatable values and excludes
failed or subsequently unloaded files.

Measure initialized loaded memory rather than source files.  This covers
zero initialized ELF data while excluding undefined gaps and debugging
information.  Do not measure entropy, timing data, or GELI key files.
Exclude loader passwords and the GELI passphrase from the kernel
environment measurement so low entropy secrets do not acquire a public
event-log digest or appear in the event data.

This is complementary to loader veriexec.  When enabled, its software
pseudo-PCR and hashed-file list are part of the final kernel environment
and are therefore committed to PCR 8.

Measurement remains best effort.  Warn and stop measuring after the
first firmware or allocation error rather than preventing the system
from booting.

Validated with the EFI TCG protocol on a ThinkPad T430 TPM 1.2 using
coreboot with a measured EDK2 payload.  Replaying the loader events
reproduced PCRs 8 and 9, and both PCRs repeated across clean boots.  TPM
2.0 testing on a commercial AMI EFI firmware likewise produced stable
PCRs across clean boots.  A raw firmware-log dump decoded by
tpm2_eventlog using tpm(4) changes that will come later confirmed the
versioned PCR 8 pre-images and the descriptive PCR 9 event data.

Obtained from:  edk2-stable202502 (TcgService.h)
MFC after:      2 weeks
Relnotes:       yes
Test Plan

On a tpm2 system you can pkg install tpm2-tools and use tpm2_readpcr 8 and tpm2_readpcr 9

On a tpm1.2 system here is a minimal util to do the same:

/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Minimal TPM 1.2 TPM_PCRRead test program.
 */

#include <sys/endian.h>
#include <sys/types.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define TPM_DEVICE              "/dev/tpm"

#define TPM_TAG_RQU_COMMAND     0x00c1
#define TPM_TAG_RSP_COMMAND     0x00c4
#define TPM_ORD_PCRREAD         0x00000015
#define TPM_SUCCESS             0x00000000
#define TPM_SHA1_DIGEST_SIZE    20

/* TPM 1.2 wire integers are big-endian. */
typedef uint8_t tpm_be16[2];
typedef uint8_t tpm_be32[4];

struct tpm_header {
        tpm_be16 tag;
        tpm_be32 size;
        tpm_be32 code;  /* Command ordinal or response return code. */
};

struct tpm_pcrread_command {
        struct tpm_header header;
        tpm_be32 pcr_index;
};

struct tpm_pcrread_response {
        struct tpm_header header;
        uint8_t digest[TPM_SHA1_DIGEST_SIZE];
};

_Static_assert(sizeof(struct tpm_header) == 10,
    "unexpected TPM header size");
_Static_assert(sizeof(struct tpm_pcrread_command) == 14,
    "unexpected TPM_PCRRead command size");
_Static_assert(sizeof(struct tpm_pcrread_response) == 30,
    "unexpected TPM_PCRRead response size");

static void
usage(void)
{

        fprintf(stderr, "usage: tpm12-pcrread pcr-index\n");
        exit(2);
}

int
main(int argc, char **argv)
{
        struct tpm_pcrread_command command;
        struct tpm_pcrread_response response;
        char *end;
        unsigned long value;
        uint32_t response_code, response_size;
        uint16_t response_tag;
        ssize_t n;
        int fd, i;

        if (argc != 2)
                usage();
        errno = 0;
        value = strtoul(argv[1], &end, 0);
        if (errno != 0 || *argv[1] == '\0' || *end != '\0' ||
            value > UINT32_MAX)
                errx(2, "invalid PCR index: %s", argv[1]);

        be16enc(command.header.tag, TPM_TAG_RQU_COMMAND);
        be32enc(command.header.size, sizeof(command));
        be32enc(command.header.code, TPM_ORD_PCRREAD);
        be32enc(command.pcr_index, (uint32_t)value);

        fd = open(TPM_DEVICE, O_RDWR);
        if (fd == -1)
                err(1, "%s", TPM_DEVICE);
        n = write(fd, &command, sizeof(command));
        if (n == -1)
                err(1, "write %s", TPM_DEVICE);
        if ((size_t)n != sizeof(command))
                errx(1, "short TPM command write: %zd of %zu bytes", n,
                    sizeof(command));

        n = read(fd, &response, sizeof(response));
        if (n == -1)
                err(1, "read %s", TPM_DEVICE);
        if ((size_t)n < sizeof(response.header))
                errx(1, "short TPM response: %zd bytes", n);

        response_tag = be16dec(response.header.tag);
        response_size = be32dec(response.header.size);
        response_code = be32dec(response.header.code);
        if (response_tag != TPM_TAG_RSP_COMMAND)
                errx(1, "unexpected TPM response tag 0x%04x", response_tag);
        if (response_size != (uint32_t)n)
                errx(1, "invalid TPM response size: header says %u, read %zd",
                    response_size, n);
        if (response_code != TPM_SUCCESS)
                errx(1, "TPM_PCRRead failed: return code 0x%08x",
                    response_code);
        if ((size_t)n != sizeof(response))
                errx(1, "invalid successful TPM_PCRRead response size: %zd", n);

        for (i = 0; i < TPM_SHA1_DIGEST_SIZE; i++)
                printf("%02x", response.digest[i]);
        putchar('\n');

        if (close(fd) == -1)
                err(1, "close %s", TPM_DEVICE);
        return (0);
}

Diff Detail

Lint
Lint Skipped
Unit
Tests Skipped

Event Timeline

This is deliberately limited to the measurement plumbing. Policy decisions are exciting. For instance, this could be used to drive a full TCB with veriexec(9), sealing keys like GELI or ZFS to boot state, remote attestation, signed containers, a Chain of Trust to externally controlled bhyve VMs, etc but that will come with a lot more weight. When I did this in industry at a financial custodian, we had extremely stringent controls centered around immutable build and release pipelines as otherwise this can become a big pain when you do something trivial like change a loader setting or upgrade etc. So that is left for later experimentation, and decisions for what make sense to ship as an open source general purpose OS.

One immediate followup once this is done will be to the tpm driver to leave a handle somewhere so we can use tpm2_eventlog. In FreeBSD right now we only have access to PCRs after bootup with no stable handle that I can see to access the binary logs.

stand/efi/loader/tpm.c
1

Shouldn't this file be in libefi?

Wow this is really cool and I've been hoping for this for a long time. Should we put how to use it in the EXAMPLES section?

sys/contrib/edk2/Include/Protocol/TcgService.h
5

Do you mean to have this <BR> here?

also shouldn't the copyright and spdx be above the commentary?

I have a very similar branch to this that I've been trying to get finalized for a while. I'll forward it to the right people.

sys/contrib/edk2/Include/Protocol/TcgService.h
5

This is a verbatim copy of the EDK2 file, so no change is needed here.

Here's the work we've done internally that's somewhat similar.

https://github.com/bsdimp/freebsd/tree/tpm-measured-boot

I've not had a chance to review this and compare the two.

sys/contrib/edk2/Include/Protocol/TcgService.h
5

Also, this likely should go through a vendor import since we try to do everything relative to EDK2 headers and we still need to merge that work to 14 (15 too?).

In D59141#1356926, @imp wrote:

Here's the work we've done internally that's somewhat similar.

https://github.com/bsdimp/freebsd/tree/tpm-measured-boot

Interesting, it looks like you solved the followup problem I was talking about with an event log character device. There's no additional measurement in that branch right now so these are complimentary.

In D59141#1356926, @imp wrote:

Here's the work we've done internally that's somewhat similar.

https://github.com/bsdimp/freebsd/tree/tpm-measured-boot

Interesting, it looks like you solved the followup problem I was talking about with an event log character device. There's no additional measurement in that branch right now so these are complimentary.

Would you rather we commit our changes first and you rebase on top of them, or vice versa since they are complimentary.

In D59141#1356940, @imp wrote:

Would you rather we commit our changes first and you rebase on top of them, or vice versa since they are complimentary.

There is no strict ordering requirement, either work could land first and are independently useful. I would submit for review a small followup cleanup pass and add TPM 1.2 support if that lands as is.

stand/man/loader.efi.8
90

I was way too lazy to implement actually talking to the TPM, so in libsecureboot I implemneted a pseudo PCR, The only real difference is that I measure everything the loader reads including loader.conf rc and 4th files.

I onlly mention it FYI I'll be happy to ditch my pseudo PCR once this goes in.