powerpc: ima: get the kexec buffer passed by the previous kernel
Patch series "ima: carry the measurement list across kexec", v8.
The TPM PCRs are only reset on a hard reboot. In order to validate a
TPM's quote after a soft reboot (eg. kexec -e), the IMA measurement
list of the running kernel must be saved and then restored on the
subsequent boot, possibly of a different architecture.
The existing securityfs binary_runtime_measurements file conveniently
provides a serialized format of the IMA measurement list. This patch
set serializes the measurement list in this format and restores it.
Up to now, the binary_runtime_measurements was defined as architecture
native format. The assumption being that userspace could and would
handle any architecture conversions. With the ability of carrying the
measurement list across kexec, possibly from one architecture to a
different one, the per boot architecture information is lost and with it
the ability of recalculating the template digest hash. To resolve this
problem, without breaking the existing ABI, this patch set introduces
the boot command line option "ima_canonical_fmt", which is arbitrarily
defined as little endian.
The need for this boot command line option will be limited to the
existing version 1 format of the binary_runtime_measurements.
Subsequent formats will be defined as canonical format (eg. TPM 2.0
support for larger digests).
A simplified method of Thiago Bauermann's "kexec buffer handover" patch
series for carrying the IMA measurement list across kexec is included in
this patch set. The simplified method requires all file measurements be
taken prior to executing the kexec load, as subsequent measurements will
not be carried across the kexec and restored.
This patch (of 10):
The IMA kexec buffer allows the currently running kernel to pass the
measurement list via a kexec segment to the kernel that will be kexec'd.
The second kernel can check whether the previous kernel sent the buffer
and retrieve it.
This is the architecture-specific part which enables IMA to receive the
measurement list passed by the previous kernel. It will be used in the
next patch.
The change in machine_kexec_64.c is to factor out the logic of removing
an FDT memory reservation so that it can be used by remove_ima_buffer.
Link: http://lkml.kernel.org/r/1480554346-29071-2-git-send-email-zohar@linux.vnet.ibm.com
Signed-off-by: Thiago Jung Bauermann <bauerman@linux.vnet.ibm.com>
Signed-off-by: Mimi Zohar <zohar@linux.vnet.ibm.com>
Acked-by: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: Andreas Steffen <andreas.steffen@strongswan.org>
Cc: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
Cc: Josh Sklar <sklar@linux.vnet.ibm.com>
Cc: Dave Young <dyoung@redhat.com>
Cc: Vivek Goyal <vgoyal@redhat.com>
Cc: Baoquan He <bhe@redhat.com>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stewart Smith <stewart@linux.vnet.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-12-20 07:22:32 +07:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2016 IBM Corporation
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Thiago Jung Bauermann <bauerman@linux.vnet.ibm.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/kexec.h>
|
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/memblock.h>
|
|
|
|
#include <linux/libfdt.h>
|
|
|
|
|
|
|
|
static int get_addr_size_cells(int *addr_cells, int *size_cells)
|
|
|
|
{
|
|
|
|
struct device_node *root;
|
|
|
|
|
|
|
|
root = of_find_node_by_path("/");
|
|
|
|
if (!root)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
*addr_cells = of_n_addr_cells(root);
|
|
|
|
*size_cells = of_n_size_cells(root);
|
|
|
|
|
|
|
|
of_node_put(root);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int do_get_kexec_buffer(const void *prop, int len, unsigned long *addr,
|
|
|
|
size_t *size)
|
|
|
|
{
|
|
|
|
int ret, addr_cells, size_cells;
|
|
|
|
|
|
|
|
ret = get_addr_size_cells(&addr_cells, &size_cells);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (len < 4 * (addr_cells + size_cells))
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
*addr = of_read_number(prop, addr_cells);
|
|
|
|
*size = of_read_number(prop + 4 * addr_cells, size_cells);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ima_get_kexec_buffer - get IMA buffer from the previous kernel
|
|
|
|
* @addr: On successful return, set to point to the buffer contents.
|
|
|
|
* @size: On successful return, set to the buffer size.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, negative errno on error.
|
|
|
|
*/
|
|
|
|
int ima_get_kexec_buffer(void **addr, size_t *size)
|
|
|
|
{
|
|
|
|
int ret, len;
|
|
|
|
unsigned long tmp_addr;
|
|
|
|
size_t tmp_size;
|
|
|
|
const void *prop;
|
|
|
|
|
|
|
|
prop = of_get_property(of_chosen, "linux,ima-kexec-buffer", &len);
|
|
|
|
if (!prop)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
ret = do_get_kexec_buffer(prop, len, &tmp_addr, &tmp_size);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
*addr = __va(tmp_addr);
|
|
|
|
*size = tmp_size;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ima_free_kexec_buffer - free memory used by the IMA buffer
|
|
|
|
*/
|
|
|
|
int ima_free_kexec_buffer(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
unsigned long addr;
|
|
|
|
size_t size;
|
|
|
|
struct property *prop;
|
|
|
|
|
|
|
|
prop = of_find_property(of_chosen, "linux,ima-kexec-buffer", NULL);
|
|
|
|
if (!prop)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
ret = do_get_kexec_buffer(prop->value, prop->length, &addr, &size);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = of_remove_property(of_chosen, prop);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return memblock_free(addr, size);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* remove_ima_buffer - remove the IMA buffer property and reservation from @fdt
|
|
|
|
*
|
|
|
|
* The IMA measurement buffer is of no use to a subsequent kernel, so we always
|
|
|
|
* remove it from the device tree.
|
|
|
|
*/
|
|
|
|
void remove_ima_buffer(void *fdt, int chosen_node)
|
|
|
|
{
|
|
|
|
int ret, len;
|
|
|
|
unsigned long addr;
|
|
|
|
size_t size;
|
|
|
|
const void *prop;
|
|
|
|
|
|
|
|
prop = fdt_getprop(fdt, chosen_node, "linux,ima-kexec-buffer", &len);
|
|
|
|
if (!prop)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ret = do_get_kexec_buffer(prop, len, &addr, &size);
|
|
|
|
fdt_delprop(fdt, chosen_node, "linux,ima-kexec-buffer");
|
|
|
|
if (ret)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ret = delete_fdt_mem_rsv(fdt, addr, size);
|
|
|
|
if (!ret)
|
|
|
|
pr_debug("Removed old IMA buffer reservation.\n");
|
|
|
|
}
|
2016-12-20 07:22:45 +07:00
|
|
|
|
|
|
|
#ifdef CONFIG_IMA_KEXEC
|
|
|
|
/**
|
|
|
|
* arch_ima_add_kexec_buffer - do arch-specific steps to add the IMA buffer
|
|
|
|
*
|
|
|
|
* Architectures should use this function to pass on the IMA buffer
|
|
|
|
* information to the next kernel.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, negative errno on error.
|
|
|
|
*/
|
|
|
|
int arch_ima_add_kexec_buffer(struct kimage *image, unsigned long load_addr,
|
|
|
|
size_t size)
|
|
|
|
{
|
|
|
|
image->arch.ima_buffer_addr = load_addr;
|
|
|
|
image->arch.ima_buffer_size = size;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int write_number(void *p, u64 value, int cells)
|
|
|
|
{
|
|
|
|
if (cells == 1) {
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
if (value > U32_MAX)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
tmp = cpu_to_be32(value);
|
|
|
|
memcpy(p, &tmp, sizeof(tmp));
|
|
|
|
} else if (cells == 2) {
|
|
|
|
u64 tmp;
|
|
|
|
|
|
|
|
tmp = cpu_to_be64(value);
|
|
|
|
memcpy(p, &tmp, sizeof(tmp));
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* setup_ima_buffer - add IMA 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.
|
|
|
|
*/
|
|
|
|
int setup_ima_buffer(const struct kimage *image, void *fdt, int chosen_node)
|
|
|
|
{
|
|
|
|
int ret, addr_cells, size_cells, entry_size;
|
|
|
|
u8 value[16];
|
|
|
|
|
|
|
|
remove_ima_buffer(fdt, chosen_node);
|
|
|
|
if (!image->arch.ima_buffer_size)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = get_addr_size_cells(&addr_cells, &size_cells);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
entry_size = 4 * (addr_cells + size_cells);
|
|
|
|
|
|
|
|
if (entry_size > sizeof(value))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = write_number(value, image->arch.ima_buffer_addr, addr_cells);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = write_number(value + 4 * addr_cells, image->arch.ima_buffer_size,
|
|
|
|
size_cells);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = fdt_setprop(fdt, chosen_node, "linux,ima-kexec-buffer", value,
|
|
|
|
entry_size);
|
|
|
|
if (ret < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = fdt_add_mem_rsv(fdt, image->arch.ima_buffer_addr,
|
|
|
|
image->arch.ima_buffer_size);
|
|
|
|
if (ret)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
pr_debug("IMA buffer at 0x%llx, size = 0x%zx\n",
|
|
|
|
image->arch.ima_buffer_addr, image->arch.ima_buffer_size);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_IMA_KEXEC */
|