[PATCH 2/3] tpm/kexec: Duplicate TPM measurement log in of-tree for kexec

Rob Herring robh+dt at kernel.org
Tue Jun 14 10:48:30 PDT 2022


(),On Tue, Jun 14, 2022 at 10:13 AM Stefan Berger <stefanb at linux.ibm.com> wrote:
>
> The memory area of the TPM measurement log is currently not properly
> duplicated for carrying it across kexec when an Open Firmware
> Devicetree is used. Therefore, the contents of the log get corrupted.
> Fix this for the kexec_file_load() syscall by allocating a buffer and
> copying the contents of the existing log into it. The new buffer is
> preserved across the kexec and a pointer to it is available when the new
> kernel is started. To achieve this, store the allocated buffer's address
> in the flattened device tree (fdt) under the name linux,tpm-kexec-buffer
> and search for this entry early in the kernel startup before the TPM
> subsystem starts up. Adjust the pointer in the of-tree stored under
> linux,sml-base to point to this buffer holding the preserved log. The
> TPM driver can then read the base address from this entry when making
> the log available.

This series really needs a wider Cc list of folks that worry about
TPMs and kexec.

> Signed-off-by: Stefan Berger <stefanb at linux.ibm.com>
> Cc: Rob Herring <robh+dt at kernel.org>
> Cc: Frank Rowand <frowand.list at gmail.com>
> Cc: Eric Biederman <ebiederm at xmission.com>
> ---
>  drivers/of/device.c       |  24 +++++
>  drivers/of/kexec.c        | 190 +++++++++++++++++++++++++++++++++++++-
>  include/linux/kexec.h     |   6 ++
>  include/linux/of.h        |   5 +
>  include/linux/of_device.h |   3 +
>  kernel/kexec_file.c       |   6 ++
>  6 files changed, 233 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/of/device.c b/drivers/of/device.c
> index 874f031442dc..0cbd47b1cabc 100644
> --- a/drivers/of/device.c
> +++ b/drivers/of/device.c
> @@ -382,3 +382,27 @@ int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
>         return 0;
>  }
>  EXPORT_SYMBOL_GPL(of_device_uevent_modalias);
> +
> +int of_tpm_get_sml_parameters(struct device_node *np, u64 *base, u32 *size)

of/device.c is for functions that work on a struct device. This is not
the case here.

> +{
> +       const u32 *sizep;
> +       const u64 *basep;
> +
> +       sizep = of_get_property(np, "linux,sml-size", NULL);
> +       basep = of_get_property(np, "linux,sml-base", NULL);

Any new properties need a schema. For chosen, that's located here[1].
The more common pattern for similar properties is <base size>.

Don't use of_get_property(), but the typed functions instead.

> +       if (sizep == NULL && basep == NULL)
> +               return -ENODEV;
> +       if (sizep == NULL || basep == NULL)
> +               return -EIO;

Do we really need the error distinction?

> +
> +       if (of_property_match_string(np, "compatible", "IBM,vtpm") < 0 &&
> +           of_property_match_string(np, "compatible", "IBM,vtpm20") < 0) {
> +               *size = be32_to_cpup((__force __be32 *)sizep);
> +               *base = be64_to_cpup((__force __be64 *)basep);
> +       } else {
> +               *size = *sizep;
> +               *base = *basep;

What? Some platforms aren't properly encoded? Fix the firmware.

> +       }
> +       return 0;
> +}
> +EXPORT_SYMBOL_GPL(of_tpm_get_sml_parameters);
> diff --git a/drivers/of/kexec.c b/drivers/of/kexec.c
> index eef6f3b9939c..db5441123a70 100644
> --- a/drivers/of/kexec.c
> +++ b/drivers/of/kexec.c
> @@ -14,6 +14,7 @@
>  #include <linux/memblock.h>
>  #include <linux/libfdt.h>
>  #include <linux/of.h>
> +#include <linux/of_device.h>
>  #include <linux/of_fdt.h>
>  #include <linux/random.h>
>  #include <linux/slab.h>
> @@ -221,7 +222,6 @@ static void remove_ima_buffer(void *fdt, int chosen_node)
>                 pr_debug("Removed old IMA buffer reservation.\n");
>  }
>
> -#ifdef CONFIG_IMA_KEXEC
>  static int setup_buffer(void *fdt, int chosen_node, const char *name,
>                         uint64_t addr, uint64_t size)
>  {
> @@ -243,6 +243,7 @@ static int setup_buffer(void *fdt, int chosen_node, const char *name,
>
>  }
>
> +#ifdef CONFIG_IMA_KEXEC
>  /**
>   * setup_ima_buffer - add IMA buffer information to the fdt
>   * @image:             kexec image being loaded.
> @@ -275,6 +276,190 @@ static inline int setup_ima_buffer(const struct kimage *image, void *fdt,
>  }
>  #endif /* CONFIG_IMA_KEXEC */
>
> +/**
> + * tpm_get_kexec_buffer - get TPM log buffer from the previous kernel
> + * @phyaddr:   On successful return, set to physical address of buffer
> + * @size:      On successful return, set to the buffer size.
> + *
> + * Return: 0 on success, negative errno on error.
> + */
> +static int tpm_get_kexec_buffer(void **phyaddr, size_t *size)
> +{
> +       int ret;
> +       unsigned long tmp_addr;
> +       size_t tmp_size;
> +
> +       ret = get_kexec_buffer("linux,tpm-kexec-buffer", &tmp_addr, &tmp_size);

Again, must be documented.

> +       if (ret)
> +               return ret;
> +
> +       *phyaddr = (void *)tmp_addr;
> +       *size = tmp_size;
> +
> +       return 0;
> +}
> +
> +/**
> + * tpm_free_kexec_buffer - free memory used by the IMA buffer
> + */
> +static int tpm_of_remove_kexec_buffer(void)
> +{
> +       struct property *prop;
> +
> +       prop = of_find_property(of_chosen, "linux,tpm-kexec-buffer", NULL);
> +       if (!prop)
> +               return -ENOENT;
> +
> +       return of_remove_property(of_chosen, prop);
> +}
> +
> +/**
> + * remove_tpm_buffer - remove the TPM log buffer property and reservation from @fdt
> + *
> + * @fdt: Flattened Device Tree to update
> + * @chosen_node: Offset to the chosen node in the device tree
> + *
> + * The TPM log measurement buffer is of no use to a subsequent kernel, so we always
> + * remove it from the device tree.
> + */
> +static void remove_tpm_buffer(void *fdt, int chosen_node)
> +{
> +       int ret;
> +
> +       ret = remove_buffer(fdt, chosen_node, "linux,tpm-kexec-buffer");
> +       if (!ret)
> +               pr_debug("Removed old TPM log buffer reservation.\n");

Do we really need this print? If so, perhaps in remove_buffer() rather
than having every caller do it.

> +}
> +
> +/**
> + * setup_tpm_buffer - add TPM measurement log buffer information to the fdt
> + * @image:             kexec image being loaded.
> + * @fdt:               Flattened device tree for the next kernel.
> + * @chosen_node:       Offset to the chosen node.
> + *
> + * Return: 0 on success, or negative errno on error.
> + */
> +static int setup_tpm_buffer(const struct kimage *image, void *fdt,
> +                           int chosen_node)
> +{
> +       return setup_buffer(fdt, chosen_node, "linux,tpm-kexec-buffer",
> +                           image->tpm_buffer_addr, image->tpm_buffer_size);
> +}
> +
> +void tpm_add_kexec_buffer(struct kimage *image)
> +{
> +       struct kexec_buf kbuf = { .image = image, .buf_align = 1,
> +                                 .buf_min = 0, .buf_max = ULONG_MAX,
> +                                 .top_down = true };
> +       struct device_node *np;
> +       void *buffer;
> +       u32 size;
> +       u64 base;
> +       int ret;
> +
> +       np = of_find_node_by_name(NULL, "vtpm");
> +       if (!np)
> +               return;
> +
> +       if (of_tpm_get_sml_parameters(np, &base, &size) < 0)
> +               return;
> +
> +       buffer = vmalloc(size);
> +       if (!buffer)
> +               return;
> +       memcpy(buffer, __va(base), size);
> +
> +       kbuf.buffer = buffer;
> +       kbuf.bufsz = size;
> +       kbuf.memsz = size;
> +       ret = kexec_add_buffer(&kbuf);
> +       if (ret) {
> +               pr_err("Error passing over kexec TPM measurement log buffer: %d\n",
> +                      ret);
> +               return;
> +       }
> +
> +       image->tpm_buffer = buffer;
> +       image->tpm_buffer_addr = kbuf.mem;
> +       image->tpm_buffer_size = size;
> +}
> +
> +/**
> + * tpm_post_kexec - Make stored TPM log buffer available in of-tree
> + */
> +static int __init tpm_post_kexec(void)
> +{
> +       struct property *newprop;
> +       struct device_node *np;
> +       void *phyaddr;
> +       size_t size;
> +       u32 oflogsize;
> +       u64 unused;
> +       int ret;
> +
> +       ret = tpm_get_kexec_buffer(&phyaddr, &size);
> +       if (ret)
> +               return 0;
> +
> +       /*
> +        * If any one of the following steps fails then the next kexec will
> +        * cause issues due to linux,sml-base pointing to a stale buffer.
> +        */
> +       np = of_find_node_by_name(NULL, "vtpm");

This seems pretty IBM specific.

> +       if (!np)
> +               goto err_free_memblock;
> +
> +       /* logsize must not have changed */
> +       if (of_tpm_get_sml_parameters(np, &unused, &oflogsize) < 0)
> +               goto err_free_memblock;
> +       if (oflogsize != size)
> +               goto err_free_memblock;
> +
> +       /* replace linux,sml-base with new physical address of buffer */
> +       ret = -ENOMEM;
> +       newprop = kzalloc(sizeof(*newprop), GFP_KERNEL);
> +       if (!newprop)
> +               goto err_free_memblock;
> +
> +       newprop->name = kstrdup("linux,sml-base", GFP_KERNEL);
> +       if (!newprop->name)
> +               goto err_free_newprop;
> +
> +       newprop->length = sizeof(phyaddr);
> +
> +       newprop->value = kmalloc(sizeof(u64), GFP_KERNEL);
> +       if (!newprop->value)
> +               goto err_free_newprop_struct;
> +
> +       if (of_property_match_string(np, "compatible", "IBM,vtpm") < 0 &&
> +           of_property_match_string(np, "compatible", "IBM,vtpm20") < 0) {
> +               ret = -ENODEV;
> +               goto err_free_newprop_struct;
> +       } else {
> +               *(u64 *)newprop->value = (u64)phyaddr;
> +       }
> +
> +       ret = of_update_property(np, newprop);

Just FYI for now, there's some work happening on a better API for
writing nodes and properties.

> +       if (ret) {
> +               pr_err("Could not update linux,sml-base with new address");
> +               goto err_free_newprop_struct;
> +       }
> +
> +       goto exit;
> +
> +err_free_newprop_struct:
> +       kfree(newprop->name);
> +err_free_newprop:
> +       kfree(newprop);
> +err_free_memblock:
> +       memblock_phys_free((phys_addr_t)phyaddr, size);
> +exit:
> +       tpm_of_remove_kexec_buffer();
> +
> +       return ret;
> +}
> +postcore_initcall(tpm_post_kexec);

Would be better if this is called explicitly at the right time when
needed rather than using an initcall.

> +
>  /*
>   * of_kexec_alloc_and_setup_fdt - Alloc and setup a new Flattened Device Tree
>   *
> @@ -464,6 +649,9 @@ void *of_kexec_alloc_and_setup_fdt(const struct kimage *image,
>         remove_ima_buffer(fdt, chosen_node);
>         ret = setup_ima_buffer(image, fdt, fdt_path_offset(fdt, "/chosen"));
>
> +       remove_tpm_buffer(fdt, chosen_node);
> +       ret = setup_tpm_buffer(image, fdt, fdt_path_offset(fdt, "/chosen"));
> +
>  out:
>         if (ret) {
>                 kvfree(fdt);
> diff --git a/include/linux/kexec.h b/include/linux/kexec.h
> index 58d1b58a971e..a0fd7ac0398c 100644
> --- a/include/linux/kexec.h
> +++ b/include/linux/kexec.h
> @@ -319,6 +319,12 @@ struct kimage {
>         void *elf_headers;
>         unsigned long elf_headers_sz;
>         unsigned long elf_load_addr;
> +
> +       /* Virtual address of TPM log buffer for kexec syscall */
> +       void *tpm_buffer;
> +
> +       phys_addr_t tpm_buffer_addr;
> +       size_t tpm_buffer_size;

Probably should use the same types as other buffers...

>  };

Rob

[1] https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/chosen.yaml



More information about the kexec mailing list