@@ -19,6 +19,8 @@
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/types.h>
+#include <linux/tpm.h>
+#include <linux/vmalloc.h>
#define RNG_SEED_SIZE 128
@@ -116,7 +118,6 @@ static int do_get_kexec_buffer(const void *prop, int len, unsigned long *addr,
return 0;
}
-#ifdef CONFIG_HAVE_IMA_KEXEC
static int __init get_kexec_buffer(const char *name, unsigned long *addr,
size_t *size)
{
@@ -151,6 +152,7 @@ static int __init get_kexec_buffer(const char *name, unsigned long *addr,
return 0;
}
+#ifdef CONFIG_HAVE_IMA_KEXEC
/**
* ima_get_kexec_buffer - get IMA buffer from the previous kernel
* @addr: On successful return, set to point to the buffer contents.
@@ -239,7 +241,6 @@ static void remove_ima_buffer(void *fdt, int chosen_node)
remove_buffer(fdt, chosen_node, "linux,ima-kexec-buffer");
}
-#ifdef CONFIG_IMA_KEXEC
static int setup_buffer(void *fdt, int chosen_node, const char *name,
phys_addr_t addr, size_t size)
{
@@ -263,6 +264,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.
@@ -285,6 +287,213 @@ 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 __init tpm_get_kexec_buffer(phys_addr_t *phyaddr, size_t *size)
+{
+ unsigned long tmp_addr;
+ size_t tmp_size;
+ int ret;
+
+ ret = get_kexec_buffer("linux,tpm-kexec-buffer", &tmp_addr, &tmp_size);
+ if (ret)
+ return ret;
+
+ *phyaddr = (phys_addr_t)tmp_addr;
+ *size = tmp_size;
+
+ return 0;
+}
+
+/**
+ * tpm_of_remove_kexec_buffer - remove the linux,tpm-kexec-buffer node
+ */
+static int __init 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)
+{
+ if (!IS_ENABLED(CONFIG_PPC64))
+ return;
+
+ remove_buffer(fdt, chosen_node, "linux,tpm-kexec-buffer");
+}
+
+/**
+ * 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)
+{
+ if (!IS_ENABLED(CONFIG_PPC64))
+ return 0;
+
+ 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;
+
+ if (!IS_ENABLED(CONFIG_PPC64))
+ return;
+
+ 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, *p;
+ struct device_node *np;
+ phys_addr_t phyaddr;
+ u32 oflogsize;
+ size_t size;
+ u64 unused;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_PPC64))
+ return 0;
+
+ np = of_find_node_by_name(NULL, "vtpm");
+ if (!np)
+ return 0;
+
+ if (!of_get_property(of_chosen, "linux,tpm-kexec-buffer", NULL)) {
+ /*
+ * linux,tpm-kexec-buffer may be missing on initial boot
+ * or if previous kernel didn't pass a buffer.
+ */
+ if (of_get_property(of_chosen, "linux,booted-from-kexec", NULL)) {
+ /* no buffer but kexec'd: remove 'linux,sml-base' */
+ ret = -EINVAL;
+ goto err_remove_sml_base;
+ }
+ return 0;
+ }
+
+ /*
+ * If any one of the following steps fails we remove linux,sml-base
+ * to invalidate the TPM log.
+ */
+ ret = tpm_get_kexec_buffer(&phyaddr, &size);
+ if (ret)
+ goto err_remove_kexec_buffer;
+
+ /* logsize must not have changed */
+ ret = of_tpm_get_sml_parameters(np, &unused, &oflogsize);
+ if (ret < 0)
+ goto err_free_memblock;
+ ret = -EINVAL;
+ 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);
+ newprop->length = sizeof(phyaddr);
+ newprop->value = kmalloc(sizeof(phyaddr), GFP_KERNEL);
+ if (!newprop->name || !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 {
+ *(phys_addr_t *)newprop->value = phyaddr;
+ }
+
+ ret = of_update_property(np, newprop);
+ if (ret) {
+ pr_err("Could not update linux,sml-base with new address");
+ goto err_free_newprop_struct;
+ }
+
+ return 0;
+
+err_free_newprop_struct:
+ kfree(newprop->value);
+ kfree(newprop->name);
+ kfree(newprop);
+err_free_memblock:
+ memblock_phys_free((phys_addr_t)phyaddr, size);
+err_remove_kexec_buffer:
+ tpm_of_remove_kexec_buffer();
+err_remove_sml_base:
+ p = of_find_property(np, "linux,sml-base", NULL);
+ if (p)
+ of_remove_property(np, p);
+
+ return ret;
+}
+subsys_initcall(tpm_post_kexec);
+
/*
* of_kexec_alloc_and_setup_fdt - Alloc and setup a new Flattened Device Tree
*
@@ -483,6 +692,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);
@@ -383,6 +383,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;
};
/* kexec interface functions */
@@ -100,6 +100,8 @@ struct of_reconfig_data {
struct property *old_prop;
};
+struct kimage;
+
/* initialize a node */
extern struct kobj_type of_node_ktype;
extern const struct fwnode_operations of_fwnode_ops;
@@ -436,7 +438,6 @@ int of_map_id(struct device_node *np, u32 id,
phys_addr_t of_dma_get_max_cpu_address(struct device_node *np);
-struct kimage;
void *of_kexec_alloc_and_setup_fdt(const struct kimage *image,
unsigned long initrd_load_addr,
unsigned long initrd_len,
@@ -1607,4 +1608,10 @@ static inline int of_overlay_notifier_unregister(struct notifier_block *nb)
#endif
+#if defined(CONFIG_KEXEC_FILE) && defined(CONFIG_OF_FLATTREE)
+void tpm_add_kexec_buffer(struct kimage *image);
+#else
+static inline void tpm_add_kexec_buffer(struct kimage *image) { }
+#endif
+
#endif /* _LINUX_OF_H */
@@ -27,6 +27,7 @@
#include <linux/kernel_read_file.h>
#include <linux/syscalls.h>
#include <linux/vmalloc.h>
+#include <linux/of.h>
#include "kexec_internal.h"
#ifdef CONFIG_KEXEC_SIG
@@ -113,6 +114,9 @@ void kimage_file_post_load_cleanup(struct kimage *image)
image->ima_buffer = NULL;
#endif /* CONFIG_IMA_KEXEC */
+ vfree(image->tpm_buffer);
+ image->tpm_buffer = NULL;
+
/* See if architecture has anything to cleanup post load */
arch_kimage_file_post_load_cleanup(image);
@@ -248,6 +252,8 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd,
/* IMA needs to pass the measurement list to the next kernel. */
ima_add_kexec_buffer(image);
+ /* Pass the TPM measurement log to next kernel */
+ tpm_add_kexec_buffer(image);
/* Call arch image load handlers */
ldata = arch_kexec_kernel_image_load(image);