The majority of the stuff currently in include/boot.h is about bootm code implemented common/bootm.c. To be more consistent move it to a new file include/bootm.h. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
617 lines
14 KiB
C
617 lines
14 KiB
C
/*
|
|
* 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 <bootm.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) {
|
|
if (handle->verify == BOOTM_VERIFY_AVAILABLE)
|
|
ret = 0;
|
|
else
|
|
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) {
|
|
pr_err("image '%s': '%s' does not have hashes\n", unit, desc);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
*outdata = data;
|
|
*outsize = data_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fit_config_verify_signature(struct fit_handle *handle, struct device_node *conf_node)
|
|
{
|
|
struct device_node *sig_node;
|
|
int ret = -EINVAL;
|
|
|
|
if (!IS_ENABLED(CONFIG_FITIMAGE_SIGNATURE))
|
|
return 0;
|
|
|
|
switch (handle->verify) {
|
|
case BOOTM_VERIFY_NONE:
|
|
case BOOTM_VERIFY_HASH:
|
|
return 0;
|
|
case BOOTM_VERIFY_SIGNATURE:
|
|
ret = -EINVAL;
|
|
break;
|
|
case BOOTM_VERIFY_AVAILABLE:
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
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) {
|
|
pr_err("configuration '%s' does not have a signature\n",
|
|
conf_node->full_name);
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fit_open_configuration(struct fit_handle *handle, const char *name)
|
|
{
|
|
struct device_node *conf_node = NULL;
|
|
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);
|
|
|
|
ret = fit_config_verify_signature(handle, conf_node);
|
|
if (ret)
|
|
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
|