9
0
Fork 0

bootm: add initial FIT support

This implementation is inspired by U-Boot's FIT support. Instead of
using libfdt (which does not exist in barebox), configuration signatures
are verified by using a simplified DT parser based on barebox's own
code.

Currently, only signed configurations with hashed images are supported,
as the other variants are less useful for verified boot. Compatible FIT
images can be created using U-Boot's mkimage tool.

Signed-off-by: Jan Luebbe <jlu@pengutronix.de>
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
This commit is contained in:
Jan Luebbe 2016-01-06 18:01:31 +01:00 committed by Sascha Hauer
parent ebb1160cdd
commit ac55adb321
8 changed files with 726 additions and 3 deletions

View File

@ -531,6 +531,12 @@ BAREBOX_MAGICVAR(aimage_noverwrite_bootargs, "Disable overwrite of the bootargs
BAREBOX_MAGICVAR(aimage_noverwrite_tags, "Disable overwrite of the tags addr with the one present in aimage");
#endif
static struct image_handler arm_fit_handler = {
.name = "FIT image",
.bootm = do_bootm_linux,
.filetype = filetype_oftree,
};
static struct binfmt_hook binfmt_aimage_hook = {
.type = filetype_aimage,
.exec = "bootm",
@ -556,6 +562,8 @@ static int armlinux_register_image_handler(void)
register_image_handler(&aimage_handler);
binfmt_register(&binfmt_aimage_hook);
}
if (IS_BUILTIN(CONFIG_CMD_BOOTM_FITIMAGE))
register_image_handler(&arm_fit_handler);
binfmt_register(&binfmt_arm_zimage_hook);
binfmt_register(&binfmt_barebox_hook);

View File

@ -418,6 +418,31 @@ config CMD_BOOTM_AIMAGE
help
Support using Android Images.
config CMD_BOOTM_FITIMAGE
bool
prompt "FIT image support"
select FITIMAGE
depends on CMD_BOOTM && ARM
help
Support using Flattened Image Tree (FIT) Images. FIT is an image
format introduced by U-Boot. A FIT image contains one or multiple
kernels, device trees and initrds. The FIT image itself is a flattened
device tree binary. Have a look at the u-boot source tree
in the "doc/uImage.FIT" folder for more information:
http://git.denx.de/?p=u-boot.git;a=tree;f=doc/uImage.FIT
config CMD_BOOTM_FITIMAGE_SIGNATURE
bool
prompt "support verifying signed FIT images"
depends on CMD_BOOTM_FITIMAGE
select FITIMAGE_SIGNATURE
help
Support verifying signed FIT images. This requires FIT images
as described in:
http://git.denx.de/?p=u-boot.git;a=blob;f=doc/uImage.FIT/signature.txt
Additionally the barebox device tree needs a /signature node with the
public key with which the image has been signed.
config CMD_BOOTU
tristate
default y

View File

@ -66,6 +66,15 @@ config UIMAGE
select CRC32
bool
config FITIMAGE
bool
select OFTREE
select DIGEST
config FITIMAGE_SIGNATURE
select CRYPTO_RSA
bool
config LOGBUF
bool

View File

@ -46,6 +46,7 @@ obj-$(CONFIG_SHELL_HUSH) += hush.o
obj-$(CONFIG_SHELL_SIMPLE) += parser.o
obj-$(CONFIG_STATE) += state.o
obj-$(CONFIG_UIMAGE) += image.o uimage.o
obj-$(CONFIG_FITIMAGE) += image-fit.o
obj-$(CONFIG_MENUTREE) += menutree.o
obj-$(CONFIG_EFI_GUID) += efi-guid.o
obj-$(CONFIG_EFI_DEVICEPATH) += efi-devicepath.o

View File

@ -17,6 +17,7 @@
#include <malloc.h>
#include <memory.h>
#include <libfile.h>
#include <image-fit.h>
#include <globalvar.h>
#include <init.h>
#include <linux/stat.h>
@ -92,6 +93,17 @@ int bootm_load_os(struct image_data *data, unsigned long load_address)
if (load_address == UIMAGE_INVALID_ADDRESS)
return -EINVAL;
if (data->os_fit) {
data->os_res = request_sdram_region("kernel",
load_address,
data->os_fit->kernel_size);
if (!data->os_res)
return -ENOMEM;
memcpy((void *)load_address, data->os_fit->kernel,
data->os_fit->kernel_size);
return 0;
}
if (data->os) {
int num;
@ -121,6 +133,9 @@ bool bootm_has_initrd(struct image_data *data)
if (!IS_ENABLED(CONFIG_CMD_BOOTM_INITRD))
return false;
if (data->os_fit && data->os_fit->initrd)
return true;
if (data->initrd_file)
return true;
@ -178,6 +193,18 @@ int bootm_load_initrd(struct image_data *data, unsigned long load_address)
if (data->initrd_res)
return 0;
if (data->os_fit && data->os_fit->initrd) {
data->initrd_res = request_sdram_region("initrd",
load_address,
data->os_fit->initrd_size);
if (!data->initrd_res)
return -ENOMEM;
memcpy((void *)load_address, data->os_fit->initrd,
data->os_fit->initrd_size);
printf("Loaded initrd from FIT image\n");
goto done1;
}
type = file_name_detect_type(data->initrd_file);
if ((int)type < 0) {
@ -216,7 +243,7 @@ done:
if (type == filetype_uimage && data->initrd->header.ih_type == IH_TYPE_MULTI)
printf(", multifile image %s", data->initrd_part);
printf("\n");
done1:
printf("initrd is at " PRINTF_CONVERSION_RESOURCE "-" PRINTF_CONVERSION_RESOURCE "\n",
data->initrd_res->start,
data->initrd_res->end);
@ -289,7 +316,9 @@ int bootm_load_devicetree(struct image_data *data, unsigned long load_address)
if (!IS_ENABLED(CONFIG_OFTREE))
return 0;
if (data->oftree_file) {
if (data->os_fit && data->os_fit->oftree) {
data->of_root_node = of_unflatten_dtb(data->os_fit->oftree);
} else if (data->oftree_file) {
size_t size;
type = file_name_detect_type(data->oftree_file);
@ -376,6 +405,8 @@ int bootm_get_os_size(struct image_data *data)
if (data->os)
return uimage_get_size(data->os,
simple_strtoul(data->os_part, NULL, 0));
if (data->os_fit)
return data->os_fit->kernel_size;
if (data->os_file) {
struct stat s;
@ -495,10 +526,24 @@ int bootm_boot(struct bootm_data *bootm_data)
goto err_out;
}
if (IS_ENABLED(CONFIG_FITIMAGE) && os_type == filetype_oftree) {
struct fit_handle *fit;
fit = fit_open(data->os_file, data->os_part, data->verbose, data->verify);
if (IS_ERR(fit)) {
printf("Loading FIT image %s failed with: %s\n", data->os_file,
strerrorp(fit));
ret = PTR_ERR(fit);
goto err_out;
}
data->os_fit = fit;
}
if (os_type == filetype_uimage) {
ret = bootm_open_os_uimage(data);
if (ret) {
printf("Loading OS image failed with %s\n",
printf("Loading OS image failed with: %s\n",
strerror(-ret));
goto err_out;
}
@ -544,6 +589,8 @@ err_out:
uimage_close(data->initrd);
if (data->os)
uimage_close(data->os);
if (IS_ENABLED(CONFIG_FITIMAGE) && data->os_fit)
fit_close(data->os_fit);
if (data->of_root_node && data->of_root_node != of_get_root_node())
of_delete_node(data->of_root_node);

584
common/image-fit.c Normal file
View File

@ -0,0 +1,584 @@
/*
* Copyright (C) Jan Lübbe, 2014
*
* This code is inspired by the U-Boot FIT image code.
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#define pr_fmt(fmt) "FIT: " fmt
#include <common.h>
#include <init.h>
#include <boot.h>
#include <libfile.h>
#include <fdt.h>
#include <digest.h>
#include <of.h>
#include <fs.h>
#include <malloc.h>
#include <linux/ctype.h>
#include <asm/byteorder.h>
#include <errno.h>
#include <linux/err.h>
#include <stringlist.h>
#include <rsa.h>
#include <image-fit.h>
#define FDT_MAX_DEPTH 32
#define FDT_MAX_PATH_LEN 200
#define CHECK_LEVEL_NONE 0
#define CHECK_LEVEL_HASH 1
#define CHECK_LEVEL_SIG 2
#define CHECK_LEVEL_MAX 3
static uint32_t dt_struct_advance(struct fdt_header *f, uint32_t dt, int size)
{
dt += size;
dt = ALIGN(dt, 4);
if (dt > f->off_dt_struct + f->size_dt_struct)
return 0;
return dt;
}
static char *dt_string(struct fdt_header *f, char *strstart, uint32_t ofs)
{
if (ofs > f->size_dt_strings)
return NULL;
else
return strstart + ofs;
}
static int of_read_string_list(struct device_node *np, const char *name, struct string_list *sl)
{
struct property *prop;
const char *s;
of_property_for_each_string(np, name, prop, s) {
string_list_add(sl, s);
}
return prop ? 0 : -EINVAL;
}
static int fit_digest(void *fit, struct digest *digest,
struct string_list *inc_nodes, struct string_list *exc_props,
uint32_t hashed_strings_start, uint32_t hashed_strings_size)
{
struct fdt_header *fdt = fit;
uint32_t dt_struct;
void *dt_strings;
struct fdt_header f = {};
int stack[FDT_MAX_DEPTH];
char path[FDT_MAX_PATH_LEN];
char *end;
uint32_t tag;
int start = -1;
int depth = -1;
int want = 0;
f.totalsize = fdt32_to_cpu(fdt->totalsize);
f.off_dt_struct = fdt32_to_cpu(fdt->off_dt_struct);
f.size_dt_struct = fdt32_to_cpu(fdt->size_dt_struct);
f.off_dt_strings = fdt32_to_cpu(fdt->off_dt_strings);
f.size_dt_strings = fdt32_to_cpu(fdt->size_dt_strings);
if (hashed_strings_start > f.size_dt_strings ||
hashed_strings_size > f.size_dt_strings ||
hashed_strings_start + hashed_strings_size > f.size_dt_strings) {
pr_err("%s: hashed-strings too large\n", __func__);
return -EINVAL;
}
dt_struct = f.off_dt_struct;
dt_strings = (void *)fdt + f.off_dt_strings;
end = path;
*end = '\0';
do {
const struct fdt_property *fdt_prop;
const struct fdt_node_header *fnh;
const char *name;
int include = 0;
int stop_at = 0;
int offset = dt_struct;
int maxlen, len;
tag = be32_to_cpu(*(uint32_t *)(fit + dt_struct));
switch (tag) {
case FDT_BEGIN_NODE:
fnh = fit + dt_struct;
name = fnh->name;
maxlen = (unsigned long)fdt + f.off_dt_struct +
f.size_dt_struct - (unsigned long)name;
len = strnlen(name, maxlen + 1);
if (len > maxlen)
return -ESPIPE;
dt_struct = dt_struct_advance(&f, dt_struct,
sizeof(struct fdt_node_header) + len + 1);
depth++;
if (depth == FDT_MAX_DEPTH)
return -ESPIPE;
if (end - path + 2 + len >= FDT_MAX_PATH_LEN)
return -ESPIPE;
if (end != path + 1)
*end++ = '/';
strcpy(end, name);
end += len;
stack[depth] = want;
if (want == 1)
stop_at = offset;
if (string_list_contains(inc_nodes, path))
want = 2;
else if (want)
want--;
else
stop_at = offset;
include = want;
break;
case FDT_END_NODE:
dt_struct = dt_struct_advance(&f, dt_struct, FDT_TAGSIZE);
include = want;
want = stack[depth--];
while (end > path && *--end != '/')
;
*end = '\0';
break;
case FDT_PROP:
fdt_prop = fit + dt_struct;
len = fdt32_to_cpu(fdt_prop->len);
name = dt_string(&f, dt_strings, fdt32_to_cpu(fdt_prop->nameoff));
if (!name)
return -ESPIPE;
dt_struct = dt_struct_advance(&f, dt_struct,
sizeof(struct fdt_property) + len);
include = want >= 2;
stop_at = offset;
if (string_list_contains(exc_props, name))
include = 0;
break;
case FDT_NOP:
dt_struct = dt_struct_advance(&f, dt_struct, FDT_TAGSIZE);
include = want >= 2;
stop_at = offset;
break;
case FDT_END:
dt_struct = dt_struct_advance(&f, dt_struct, FDT_TAGSIZE);
include = 1;
break;
default:
pr_err("%s: Unknown tag 0x%08X\n", __func__, tag);
return -EINVAL;
}
if (!dt_struct)
return -ESPIPE;
pr_debug("%s: include %d, want %d, offset 0x%x, len 0x%x\n",
path, include, want, offset, dt_struct-offset);
if (include && start == -1)
start = offset;
if (!include && start != -1) {
pr_debug("region: 0x%p+0x%x\n", fit + start, offset - start);
digest_update(digest, fit + start, offset - start);
start = -1;
}
} while (tag != FDT_END);
pr_debug("region: 0x%p+0x%x\n", fit + start, dt_struct - start);
digest_update(digest, fit + start, dt_struct - start);
pr_debug("strings: 0x%p+0x%x\n", dt_strings+hashed_strings_start, hashed_strings_size);
digest_update(digest, dt_strings + hashed_strings_start, hashed_strings_size);
return 0;
}
/*
* The consistency of the FTD structure was already checked by of_unflatten_dtb()
*/
static int fit_verify_signature(struct device_node *sig_node, void *fit)
{
uint32_t hashed_strings_start, hashed_strings_size;
struct string_list inc_nodes, exc_props;
struct rsa_public_key key = {};
struct digest *digest;
int sig_len;
const char *algo_name, *key_name, *sig_value;
char *key_path;
struct device_node *key_node;
enum hash_algo algo;
void *hash;
int ret;
if (of_property_read_string(sig_node, "algo", &algo_name)) {
pr_err("algo not found\n");
ret = -EINVAL;
goto out;
}
if (strcmp(algo_name, "sha1,rsa2048") == 0) {
algo = HASH_ALGO_SHA1;
} else if (strcmp(algo_name, "sha256,rsa4096") == 0) {
algo = HASH_ALGO_SHA256;
} else {
pr_err("unknown algo %s\n", algo_name);
ret = -EINVAL;
goto out;
}
digest = digest_alloc_by_algo(algo);
if (!digest) {
pr_err("unsupported algo %s\n", algo_name);
ret = -EINVAL;
goto out;
}
sig_value = of_get_property(sig_node, "value", &sig_len);
if (!sig_value) {
pr_err("signature value not found in %s\n", sig_node->full_name);
ret = -EINVAL;
goto out_free_digest;
}
if (of_property_read_string(sig_node, "key-name-hint", &key_name)) {
pr_err("key name not found in %s\n", sig_node->full_name);
ret = -EINVAL;
goto out_free_digest;
}
key_path = xasprintf("/signature/key-%s", key_name);
key_node = of_find_node_by_path(key_path);
free(key_path);
if (!key_node) {
pr_info("failed to find key node\n");
ret = -ENOENT;
goto out_free_digest;
}
ret = rsa_of_read_key(key_node, &key);
if (ret) {
pr_info("failed to read key in %s\n", key_node->full_name);
ret = -ENOENT;
goto out_free_digest;
}
if (of_property_read_u32_index(sig_node, "hashed-strings", 0, &hashed_strings_start)) {
pr_err("hashed-strings start not found in %s\n", sig_node->full_name);
ret = -EINVAL;
goto out_free_digest;
}
if (of_property_read_u32_index(sig_node, "hashed-strings", 1, &hashed_strings_size)) {
pr_err("hashed-strings size not found in %s\n", sig_node->full_name);
ret = -EINVAL;
goto out_free_digest;
}
string_list_init(&inc_nodes);
string_list_init(&exc_props);
if (of_read_string_list(sig_node, "hashed-nodes", &inc_nodes)) {
pr_err("hashed-nodes property not found in %s\n", sig_node->full_name);
ret = -EINVAL;
goto out_sl;
}
string_list_add(&exc_props, "data");
digest_init(digest);
ret = fit_digest(fit, digest, &inc_nodes, &exc_props, hashed_strings_start, hashed_strings_size);
hash = xzalloc(digest_length(digest));
digest_final(digest, hash);
ret = rsa_verify(&key, sig_value, sig_len, hash, algo);
if (ret) {
pr_info("image signature BAD\n");
ret = -EBADMSG;
} else {
pr_info("image signature OK\n");
ret = 0;
}
free(hash);
out_sl:
string_list_free(&inc_nodes);
string_list_free(&exc_props);
out_free_digest:
digest_free(digest);
out:
return ret;
}
static int fit_verify_hash(struct device_node *hash, const void *data, int data_len)
{
struct digest *d;
const char *algo;
const char *value_read;
char *value_calc;
int hash_len, ret;
value_read = of_get_property(hash, "value", &hash_len);
if (!value_read) {
pr_err("%s: \"value\" property not found\n", hash->full_name);
return -EINVAL;
}
if (of_property_read_string(hash, "algo", &algo)) {
pr_err("%s: \"algo\" property not found\n", hash->full_name);
return -EINVAL;
}
d = digest_alloc(algo);
if (!d) {
pr_err("%s: unsupported algo %s\n", hash->full_name, algo);
return -EINVAL;
}
if (hash_len != digest_length(d)) {
pr_err("%s: invalid hash length %d\n", hash->full_name, hash_len);
ret = -EINVAL;
goto err_digest_free;
}
value_calc = xmalloc(hash_len);
digest_init(d);
digest_update(d, data, data_len);
digest_final(d, value_calc);
if (memcmp(value_read, value_calc, hash_len)) {
pr_info("%s: hash BAD\n", hash->full_name);
ret = -EBADMSG;
} else {
pr_info("%s: hash OK\n", hash->full_name);
ret = 0;
}
free(value_calc);
err_digest_free:
digest_free(d);
return ret;
}
static int fit_open_image(struct fit_handle *handle, const char *unit, const void **outdata,
unsigned long *outsize)
{
struct device_node *image = NULL, *hash;
const char *type = NULL, *desc= "(no description)";
const void *data;
int data_len;
int ret = 0;
image = of_get_child_by_name(handle->root, "images");
if (!image)
return -ENOENT;
image = of_get_child_by_name(image, unit);
if (!image)
return -ENOENT;
of_property_read_string(image, "description", &desc);
pr_info("image '%s': '%s'\n", unit, desc);
of_property_read_string(image, "type", &type);
if (!type) {
pr_err("No \"type\" property found in %s\n", image->full_name);
return -EINVAL;
}
data = of_get_property(image, "data", &data_len);
if (!data) {
pr_err("data not found\n");
return -EINVAL;
}
if (handle->verify > BOOTM_VERIFY_NONE) {
ret = -EINVAL;
for_each_child_of_node(image, hash) {
if (handle->verbose)
of_print_nodes(hash, 0);
ret = fit_verify_hash(hash, data, data_len);
if (ret < 0)
return ret;
}
}
if (ret < 0)
return ret;
*outdata = data;
*outsize = data_len;
return 0;
}
static int fit_open_configuration(struct fit_handle *handle, const char *name)
{
struct device_node *conf_node = NULL, *sig_node;
const char *unit, *desc = "(no description)";
int ret;
conf_node = of_get_child_by_name(handle->root, "configurations");
if (!conf_node)
return -ENOENT;
if (name) {
unit = name;
} else if (of_property_read_string(conf_node, "default", &unit)) {
unit = "conf@1";
}
conf_node = of_get_child_by_name(conf_node, unit);
if (!conf_node) {
pr_err("configuration '%s' not found\n", unit);
return -ENOENT;
}
of_property_read_string(conf_node, "description", &desc);
pr_info("configuration '%s': %s\n", unit, desc);
if (IS_ENABLED(CONFIG_FITIMAGE_SIGNATURE) &&
handle->verify == BOOTM_VERIFY_SIGNATURE) {
ret = -EINVAL;
for_each_child_of_node(conf_node, sig_node) {
if (handle->verbose)
of_print_nodes(sig_node, 0);
ret = fit_verify_signature(sig_node, handle->fit);
if (ret < 0)
return ret;
}
if (ret < 0)
return ret;
}
if (of_property_read_string(conf_node, "kernel", &unit) == 0) {
ret = fit_open_image(handle, unit, &handle->kernel, &handle->kernel_size);
if (ret)
return ret;
} else {
return -ENOENT;
}
if (of_property_read_string(conf_node, "fdt", &unit) == 0) {
ret = fit_open_image(handle, unit, &handle->oftree, &handle->oftree_size);
if (ret)
return ret;
}
if (of_property_read_string(conf_node, "ramdisk", &unit) == 0) {
ret = fit_open_image(handle, unit, &handle->initrd, &handle->initrd_size);
if (ret)
return ret;
}
return 0;
}
struct fit_handle *fit_open(const char *filename, const char *config, bool verbose,
enum bootm_verify verify)
{
struct fit_handle *handle = NULL;
const char *desc = "(no description)";
int ret;
handle = xzalloc(sizeof(struct fit_handle));
handle->verbose = verbose;
ret = read_file_2(filename, &handle->size, &handle->fit, FILESIZE_MAX);
if (ret) {
pr_err("unable to read %s: %s\n", filename, strerror(-ret));
goto err;
}
handle->root = of_unflatten_dtb(handle->fit);
if (IS_ERR(handle->root)) {
ret = PTR_ERR(handle->root);
goto err;
}
handle->verify = verify;
of_property_read_string(handle->root, "description", &desc);
pr_info("'%s': %s\n", filename, desc);
ret = fit_open_configuration(handle, config);
if (ret)
goto err;
return handle;
err:
if (handle->root)
of_delete_node(handle->root);
free(handle->fit);
free(handle);
return ERR_PTR(ret);
}
void fit_close(struct fit_handle *handle)
{
if (handle->root)
of_delete_node(handle->root);
if (handle->fit)
free(handle->fit);
free(handle);
}
#ifdef CONFIG_SANDBOX
static int do_bootm_sandbox_fit(struct image_data *data)
{
struct fit_handle *handle;
handle = fit_open(data->os_file, data->os_part, data->verbose);
if (handle)
fit_close(handle);
return 0;
}
static struct image_handler sandbox_fit_handler = {
.name = "FIT image",
.bootm = do_bootm_sandbox_fit,
.filetype = filetype_oftree,
};
static int sandbox_fit_register(void)
{
return register_image_handler(&sandbox_fit_handler);
}
late_initcall(sandbox_fit_register);
#endif

View File

@ -34,6 +34,10 @@ struct image_data {
/* if os is an uImage this will be provided */
struct uimage_handle *os;
/* if os is a FIT image this will be provided */
struct fit_handle *os_fit;
char *os_part;
/* otherwise only the filename will be provided */

45
include/image-fit.h Normal file
View File

@ -0,0 +1,45 @@
/*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) Jan Lübbe, 2014
*/
#ifndef __IMAGE_FIT_H__
#define __IMAGE_FIT_H__
#include <linux/types.h>
#include <boot.h>
struct fit_handle {
void *fit;
size_t size;
bool verbose;
enum bootm_verify verify;
struct device_node *root;
const void *kernel;
unsigned long kernel_size;
const void *oftree;
unsigned long oftree_size;
const void *initrd;
unsigned long initrd_size;
};
struct fit_handle *fit_open(const char *filename, const char *config, bool verbose,
enum bootm_verify verify);
void fit_close(struct fit_handle *handle);
#endif /* __IMAGE_FIT_H__ */