mirror of
https://github.com/Fishwaldo/u-boot.git
synced 2025-03-20 22:21:41 +00:00
Vboot vulnerability fix
-----BEGIN PGP SIGNATURE----- iQFFBAABCgAvFiEEslwAIq+Gp8wWVbYnfxc6PpAIreYFAl6EnJkRHHNqZ0BjaHJv bWl1bS5vcmcACgkQfxc6PpAIreZCcgf+KYFvKNwoLHmve7h1z+OiPkFzDqbNi12i kvTlnHzmq0RZXK39RmHh0fAoSsdyXzDMBJbdCZyf8SPTIRGPnGlHhFTnONg88cZu RDzpwZvXWB99vHYq5398zW/qSlmWmHp9lVKdTkZcmATMl7ehb5MM6LkgW9AhzRH+ 0Ybl+wUgc/JmmrE6giIOrGNgsUTVG5uo+skoHUFAlcE0itssIKx5p8vJH1Mo/29f dbr4LpSLmD1zeW8+TMocdF8o2Mx1Nn76ai+q8Nmj/acR8VaypaqTtgTnlyGwegl4 U7L0XOfj3bGZTOlUFtwnv8UmBzpRcWq9u1oTtaHT9zzvtbA+8VAZeQ== =ACdK -----END PGP SIGNATURE----- Merge tag 'dm-pull-1apr20' of git://git.denx.de/u-boot-dm Vboot vulnerability fix
This commit is contained in:
commit
e0718b3ab7
11 changed files with 602 additions and 119 deletions
|
@ -819,7 +819,8 @@ void __weak switch_to_non_secure_mode(void)
|
||||||
#else /* USE_HOSTCC */
|
#else /* USE_HOSTCC */
|
||||||
|
|
||||||
#if defined(CONFIG_FIT_SIGNATURE)
|
#if defined(CONFIG_FIT_SIGNATURE)
|
||||||
static int bootm_host_load_image(const void *fit, int req_image_type)
|
static int bootm_host_load_image(const void *fit, int req_image_type,
|
||||||
|
int cfg_noffset)
|
||||||
{
|
{
|
||||||
const char *fit_uname_config = NULL;
|
const char *fit_uname_config = NULL;
|
||||||
ulong data, len;
|
ulong data, len;
|
||||||
|
@ -831,6 +832,7 @@ static int bootm_host_load_image(const void *fit, int req_image_type)
|
||||||
void *load_buf;
|
void *load_buf;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
fit_uname_config = fdt_get_name(fit, cfg_noffset, NULL);
|
||||||
memset(&images, '\0', sizeof(images));
|
memset(&images, '\0', sizeof(images));
|
||||||
images.verify = 1;
|
images.verify = 1;
|
||||||
noffset = fit_image_load(&images, (ulong)fit,
|
noffset = fit_image_load(&images, (ulong)fit,
|
||||||
|
@ -878,7 +880,7 @@ int bootm_host_load_images(const void *fit, int cfg_noffset)
|
||||||
for (i = 0; i < ARRAY_SIZE(image_types); i++) {
|
for (i = 0; i < ARRAY_SIZE(image_types); i++) {
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = bootm_host_load_image(fit, image_types[i]);
|
ret = bootm_host_load_image(fit, image_types[i], cfg_noffset);
|
||||||
if (!err && ret && ret != -ENOENT)
|
if (!err && ret && ret != -ENOENT)
|
||||||
err = ret;
|
err = ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ static int fit_image_setup_decrypt(struct image_cipher_info *info,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
info->keyname = fdt_getprop(fit, cipher_noffset, "key-name-hint", NULL);
|
info->keyname = fdt_getprop(fit, cipher_noffset, FIT_KEY_HINT, NULL);
|
||||||
if (!info->keyname) {
|
if (!info->keyname) {
|
||||||
printf("Can't get key name\n");
|
printf("Can't get key name\n");
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -168,7 +168,7 @@ static void fit_image_print_data(const void *fit, int noffset, const char *p,
|
||||||
int value_len;
|
int value_len;
|
||||||
char *algo;
|
char *algo;
|
||||||
const char *padding;
|
const char *padding;
|
||||||
int required;
|
bool required;
|
||||||
int ret, i;
|
int ret, i;
|
||||||
|
|
||||||
debug("%s %s node: '%s'\n", p, type,
|
debug("%s %s node: '%s'\n", p, type,
|
||||||
|
@ -179,8 +179,8 @@ static void fit_image_print_data(const void *fit, int noffset, const char *p,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
printf("%s", algo);
|
printf("%s", algo);
|
||||||
keyname = fdt_getprop(fit, noffset, "key-name-hint", NULL);
|
keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL);
|
||||||
required = fdt_getprop(fit, noffset, "required", NULL) != NULL;
|
required = fdt_getprop(fit, noffset, FIT_KEY_REQUIRED, NULL) != NULL;
|
||||||
if (keyname)
|
if (keyname)
|
||||||
printf(":%s", keyname);
|
printf(":%s", keyname);
|
||||||
if (required)
|
if (required)
|
||||||
|
@ -1712,24 +1712,6 @@ int fit_conf_find_compat(const void *fit, const void *fdt)
|
||||||
return best_match_offset;
|
return best_match_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* fit_conf_get_node - get node offset for configuration of a given unit name
|
|
||||||
* @fit: pointer to the FIT format image header
|
|
||||||
* @conf_uname: configuration node unit name
|
|
||||||
*
|
|
||||||
* fit_conf_get_node() finds a configuration (within the '/configurations'
|
|
||||||
* parent node) of a provided unit name. If configuration is found its node
|
|
||||||
* offset is returned to the caller.
|
|
||||||
*
|
|
||||||
* When NULL is provided in second argument fit_conf_get_node() will search
|
|
||||||
* for a default configuration node instead. Default configuration node unit
|
|
||||||
* name is retrieved from FIT_DEFAULT_PROP property of the '/configurations'
|
|
||||||
* node.
|
|
||||||
*
|
|
||||||
* returns:
|
|
||||||
* configuration node offset when found (>=0)
|
|
||||||
* negative number on failure (FDT_ERR_* code)
|
|
||||||
*/
|
|
||||||
int fit_conf_get_node(const void *fit, const char *conf_uname)
|
int fit_conf_get_node(const void *fit, const char *conf_uname)
|
||||||
{
|
{
|
||||||
int noffset, confs_noffset;
|
int noffset, confs_noffset;
|
||||||
|
@ -1969,7 +1951,7 @@ int fit_image_load(bootm_headers_t *images, ulong addr,
|
||||||
fit_uname = fit_get_name(fit, noffset, NULL);
|
fit_uname = fit_get_name(fit, noffset, NULL);
|
||||||
}
|
}
|
||||||
if (noffset < 0) {
|
if (noffset < 0) {
|
||||||
puts("Could not find subimage node\n");
|
printf("Could not find subimage node type '%s'\n", prop_name);
|
||||||
bootstage_error(bootstage_id + BOOTSTAGE_SUB_SUBNODE);
|
bootstage_error(bootstage_id + BOOTSTAGE_SUB_SUBNODE);
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,7 +229,7 @@ static int fit_image_setup_verify(struct image_sign_info *info,
|
||||||
padding_name = RSA_DEFAULT_PADDING_NAME;
|
padding_name = RSA_DEFAULT_PADDING_NAME;
|
||||||
|
|
||||||
memset(info, '\0', sizeof(*info));
|
memset(info, '\0', sizeof(*info));
|
||||||
info->keyname = fdt_getprop(fit, noffset, "key-name-hint", NULL);
|
info->keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL);
|
||||||
info->fit = (void *)fit;
|
info->fit = (void *)fit;
|
||||||
info->node_offset = noffset;
|
info->node_offset = noffset;
|
||||||
info->name = algo_name;
|
info->name = algo_name;
|
||||||
|
@ -340,7 +340,8 @@ int fit_image_verify_required_sigs(const void *fit, int image_noffset,
|
||||||
const char *required;
|
const char *required;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
required = fdt_getprop(sig_blob, noffset, "required", NULL);
|
required = fdt_getprop(sig_blob, noffset, FIT_KEY_REQUIRED,
|
||||||
|
NULL);
|
||||||
if (!required || strcmp(required, "image"))
|
if (!required || strcmp(required, "image"))
|
||||||
continue;
|
continue;
|
||||||
ret = fit_image_verify_sig(fit, image_noffset, data, size,
|
ret = fit_image_verify_sig(fit, image_noffset, data, size,
|
||||||
|
@ -359,20 +360,39 @@ int fit_image_verify_required_sigs(const void *fit, int image_noffset,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fit_config_check_sig(const void *fit, int noffset, int required_keynode,
|
/**
|
||||||
char **err_msgp)
|
* fit_config_check_sig() - Check the signature of a config
|
||||||
|
*
|
||||||
|
* @fit: FIT to check
|
||||||
|
* @noffset: Offset of configuration node (e.g. /configurations/conf-1)
|
||||||
|
* @required_keynode: Offset in the control FDT of the required key node,
|
||||||
|
* if any. If this is given, then the configuration wil not
|
||||||
|
* pass verification unless that key is used. If this is
|
||||||
|
* -1 then any signature will do.
|
||||||
|
* @conf_noffset: Offset of the configuration subnode being checked (e.g.
|
||||||
|
* /configurations/conf-1/kernel)
|
||||||
|
* @err_msgp: In the event of an error, this will be pointed to a
|
||||||
|
* help error string to display to the user.
|
||||||
|
* @return 0 if all verified ok, <0 on error
|
||||||
|
*/
|
||||||
|
static int fit_config_check_sig(const void *fit, int noffset,
|
||||||
|
int required_keynode, int conf_noffset,
|
||||||
|
char **err_msgp)
|
||||||
{
|
{
|
||||||
char * const exc_prop[] = {"data"};
|
char * const exc_prop[] = {"data"};
|
||||||
const char *prop, *end, *name;
|
const char *prop, *end, *name;
|
||||||
struct image_sign_info info;
|
struct image_sign_info info;
|
||||||
const uint32_t *strings;
|
const uint32_t *strings;
|
||||||
|
const char *config_name;
|
||||||
uint8_t *fit_value;
|
uint8_t *fit_value;
|
||||||
int fit_value_len;
|
int fit_value_len;
|
||||||
|
bool found_config;
|
||||||
int max_regions;
|
int max_regions;
|
||||||
int i, prop_len;
|
int i, prop_len;
|
||||||
char path[200];
|
char path[200];
|
||||||
int count;
|
int count;
|
||||||
|
|
||||||
|
config_name = fit_get_name(fit, conf_noffset, NULL);
|
||||||
debug("%s: fdt=%p, conf='%s', sig='%s'\n", __func__, gd_fdt_blob(),
|
debug("%s: fdt=%p, conf='%s', sig='%s'\n", __func__, gd_fdt_blob(),
|
||||||
fit_get_name(fit, noffset, NULL),
|
fit_get_name(fit, noffset, NULL),
|
||||||
fit_get_name(gd_fdt_blob(), required_keynode, NULL));
|
fit_get_name(gd_fdt_blob(), required_keynode, NULL));
|
||||||
|
@ -413,9 +433,20 @@ int fit_config_check_sig(const void *fit, int noffset, int required_keynode,
|
||||||
char *node_inc[count];
|
char *node_inc[count];
|
||||||
|
|
||||||
debug("Hash nodes (%d):\n", count);
|
debug("Hash nodes (%d):\n", count);
|
||||||
|
found_config = false;
|
||||||
for (name = prop, i = 0; name < end; name += strlen(name) + 1, i++) {
|
for (name = prop, i = 0; name < end; name += strlen(name) + 1, i++) {
|
||||||
debug(" '%s'\n", name);
|
debug(" '%s'\n", name);
|
||||||
node_inc[i] = (char *)name;
|
node_inc[i] = (char *)name;
|
||||||
|
if (!strncmp(FIT_CONFS_PATH, name, strlen(FIT_CONFS_PATH)) &&
|
||||||
|
name[sizeof(FIT_CONFS_PATH) - 1] == '/' &&
|
||||||
|
!strcmp(name + sizeof(FIT_CONFS_PATH), config_name)) {
|
||||||
|
debug(" (found config node %s)", config_name);
|
||||||
|
found_config = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found_config) {
|
||||||
|
*err_msgp = "Selected config not in hashed nodes";
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -483,7 +514,7 @@ static int fit_config_verify_sig(const void *fit, int conf_noffset,
|
||||||
if (!strncmp(name, FIT_SIG_NODENAME,
|
if (!strncmp(name, FIT_SIG_NODENAME,
|
||||||
strlen(FIT_SIG_NODENAME))) {
|
strlen(FIT_SIG_NODENAME))) {
|
||||||
ret = fit_config_check_sig(fit, noffset, sig_offset,
|
ret = fit_config_check_sig(fit, noffset, sig_offset,
|
||||||
&err_msg);
|
conf_noffset, &err_msg);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
puts("- ");
|
puts("- ");
|
||||||
} else {
|
} else {
|
||||||
|
@ -499,13 +530,14 @@ static int fit_config_verify_sig(const void *fit, int conf_noffset,
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return verified ? 0 : -EPERM;
|
if (verified)
|
||||||
|
return 0;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
printf(" error!\n%s for '%s' hash node in '%s' config node\n",
|
printf(" error!\n%s for '%s' hash node in '%s' config node\n",
|
||||||
err_msg, fit_get_name(fit, noffset, NULL),
|
err_msg, fit_get_name(fit, noffset, NULL),
|
||||||
fit_get_name(fit, conf_noffset, NULL));
|
fit_get_name(fit, conf_noffset, NULL));
|
||||||
return -1;
|
return -EPERM;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fit_config_verify_required_sigs(const void *fit, int conf_noffset,
|
int fit_config_verify_required_sigs(const void *fit, int conf_noffset,
|
||||||
|
@ -526,7 +558,8 @@ int fit_config_verify_required_sigs(const void *fit, int conf_noffset,
|
||||||
const char *required;
|
const char *required;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
required = fdt_getprop(sig_blob, noffset, "required", NULL);
|
required = fdt_getprop(sig_blob, noffset, FIT_KEY_REQUIRED,
|
||||||
|
NULL);
|
||||||
if (!required || strcmp(required, "conf"))
|
if (!required || strcmp(required, "conf"))
|
||||||
continue;
|
continue;
|
||||||
ret = fit_config_verify_sig(fit, conf_noffset, sig_blob,
|
ret = fit_config_verify_sig(fit, conf_noffset, sig_blob,
|
||||||
|
|
|
@ -939,12 +939,14 @@ int booti_setup(ulong image, ulong *relocated_addr, ulong *size,
|
||||||
#define FIT_IMAGES_PATH "/images"
|
#define FIT_IMAGES_PATH "/images"
|
||||||
#define FIT_CONFS_PATH "/configurations"
|
#define FIT_CONFS_PATH "/configurations"
|
||||||
|
|
||||||
/* hash/signature node */
|
/* hash/signature/key node */
|
||||||
#define FIT_HASH_NODENAME "hash"
|
#define FIT_HASH_NODENAME "hash"
|
||||||
#define FIT_ALGO_PROP "algo"
|
#define FIT_ALGO_PROP "algo"
|
||||||
#define FIT_VALUE_PROP "value"
|
#define FIT_VALUE_PROP "value"
|
||||||
#define FIT_IGNORE_PROP "uboot-ignore"
|
#define FIT_IGNORE_PROP "uboot-ignore"
|
||||||
#define FIT_SIG_NODENAME "signature"
|
#define FIT_SIG_NODENAME "signature"
|
||||||
|
#define FIT_KEY_REQUIRED "required"
|
||||||
|
#define FIT_KEY_HINT "key-name-hint"
|
||||||
|
|
||||||
/* cipher node */
|
/* cipher node */
|
||||||
#define FIT_CIPHER_NODENAME "cipher"
|
#define FIT_CIPHER_NODENAME "cipher"
|
||||||
|
@ -1092,7 +1094,27 @@ int fit_image_check_comp(const void *fit, int noffset, uint8_t comp);
|
||||||
int fit_check_format(const void *fit);
|
int fit_check_format(const void *fit);
|
||||||
|
|
||||||
int fit_conf_find_compat(const void *fit, const void *fdt);
|
int fit_conf_find_compat(const void *fit, const void *fdt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fit_conf_get_node - get node offset for configuration of a given unit name
|
||||||
|
* @fit: pointer to the FIT format image header
|
||||||
|
* @conf_uname: configuration node unit name (NULL to use default)
|
||||||
|
*
|
||||||
|
* fit_conf_get_node() finds a configuration (within the '/configurations'
|
||||||
|
* parent node) of a provided unit name. If configuration is found its node
|
||||||
|
* offset is returned to the caller.
|
||||||
|
*
|
||||||
|
* When NULL is provided in second argument fit_conf_get_node() will search
|
||||||
|
* for a default configuration node instead. Default configuration node unit
|
||||||
|
* name is retrieved from FIT_DEFAULT_PROP property of the '/configurations'
|
||||||
|
* node.
|
||||||
|
*
|
||||||
|
* returns:
|
||||||
|
* configuration node offset when found (>=0)
|
||||||
|
* negative number on failure (FDT_ERR_* code)
|
||||||
|
*/
|
||||||
int fit_conf_get_node(const void *fit, const char *conf_uname);
|
int fit_conf_get_node(const void *fit, const char *conf_uname);
|
||||||
|
|
||||||
int fit_conf_get_prop_node_count(const void *fit, int noffset,
|
int fit_conf_get_prop_node_count(const void *fit, int noffset,
|
||||||
const char *prop_name);
|
const char *prop_name);
|
||||||
int fit_conf_get_prop_node_index(const void *fit, int noffset,
|
int fit_conf_get_prop_node_index(const void *fit, int noffset,
|
||||||
|
|
|
@ -792,8 +792,8 @@ int rsa_add_verify_data(struct image_sign_info *info, void *keydest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
ret = fdt_setprop_string(keydest, node, "key-name-hint",
|
ret = fdt_setprop_string(keydest, node, FIT_KEY_HINT,
|
||||||
info->keyname);
|
info->keyname);
|
||||||
}
|
}
|
||||||
if (!ret)
|
if (!ret)
|
||||||
ret = fdt_setprop_u32(keydest, node, "rsa,num-bits", bits);
|
ret = fdt_setprop_u32(keydest, node, "rsa,num-bits", bits);
|
||||||
|
@ -815,7 +815,7 @@ int rsa_add_verify_data(struct image_sign_info *info, void *keydest)
|
||||||
info->name);
|
info->name);
|
||||||
}
|
}
|
||||||
if (!ret && info->require_keys) {
|
if (!ret && info->require_keys) {
|
||||||
ret = fdt_setprop_string(keydest, node, "required",
|
ret = fdt_setprop_string(keydest, node, FIT_KEY_REQUIRED,
|
||||||
info->require_keys);
|
info->require_keys);
|
||||||
}
|
}
|
||||||
done:
|
done:
|
||||||
|
|
|
@ -24,10 +24,18 @@ For configuration verification:
|
||||||
Tests run with both SHA1 and SHA256 hashing.
|
Tests run with both SHA1 and SHA256 hashing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
|
||||||
import sys
|
|
||||||
import struct
|
import struct
|
||||||
|
import pytest
|
||||||
import u_boot_utils as util
|
import u_boot_utils as util
|
||||||
|
import vboot_forge
|
||||||
|
|
||||||
|
TESTDATA = [
|
||||||
|
['sha1', '', False],
|
||||||
|
['sha1', '-pss', False],
|
||||||
|
['sha256', '', False],
|
||||||
|
['sha256', '-pss', False],
|
||||||
|
['sha256', '-pss', True],
|
||||||
|
]
|
||||||
|
|
||||||
@pytest.mark.boardspec('sandbox')
|
@pytest.mark.boardspec('sandbox')
|
||||||
@pytest.mark.buildconfigspec('fit_signature')
|
@pytest.mark.buildconfigspec('fit_signature')
|
||||||
|
@ -35,7 +43,8 @@ import u_boot_utils as util
|
||||||
@pytest.mark.requiredtool('fdtget')
|
@pytest.mark.requiredtool('fdtget')
|
||||||
@pytest.mark.requiredtool('fdtput')
|
@pytest.mark.requiredtool('fdtput')
|
||||||
@pytest.mark.requiredtool('openssl')
|
@pytest.mark.requiredtool('openssl')
|
||||||
def test_vboot(u_boot_console):
|
@pytest.mark.parametrize("sha_algo,padding,required", TESTDATA)
|
||||||
|
def test_vboot(u_boot_console, sha_algo, padding, required):
|
||||||
"""Test verified boot signing with mkimage and verification with 'bootm'.
|
"""Test verified boot signing with mkimage and verification with 'bootm'.
|
||||||
|
|
||||||
This works using sandbox only as it needs to update the device tree used
|
This works using sandbox only as it needs to update the device tree used
|
||||||
|
@ -75,13 +84,14 @@ def test_vboot(u_boot_console):
|
||||||
with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
|
with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
|
||||||
output = cons.run_command_list(
|
output = cons.run_command_list(
|
||||||
['host load hostfs - 100 %stest.fit' % tmpdir,
|
['host load hostfs - 100 %stest.fit' % tmpdir,
|
||||||
'fdt addr 100',
|
'fdt addr 100',
|
||||||
'bootm 100'])
|
'bootm 100'])
|
||||||
assert(expect_string in ''.join(output))
|
assert expect_string in ''.join(output)
|
||||||
if boots:
|
if boots:
|
||||||
assert('sandbox: continuing, as we cannot run' in ''.join(output))
|
assert 'sandbox: continuing, as we cannot run' in ''.join(output)
|
||||||
else:
|
else:
|
||||||
assert('sandbox: continuing, as we cannot run' not in ''.join(output))
|
assert('sandbox: continuing, as we cannot run'
|
||||||
|
not in ''.join(output))
|
||||||
|
|
||||||
def make_fit(its):
|
def make_fit(its):
|
||||||
"""Make a new FIT from the .its source file.
|
"""Make a new FIT from the .its source file.
|
||||||
|
@ -108,20 +118,6 @@ def test_vboot(u_boot_console):
|
||||||
util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
|
util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
|
||||||
'-r', fit])
|
'-r', fit])
|
||||||
|
|
||||||
def sign_fit_norequire(sha_algo):
|
|
||||||
"""Sign the FIT
|
|
||||||
|
|
||||||
Signs the FIT and writes the signature into it. It also writes the
|
|
||||||
public key into the dtb.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
|
|
||||||
use.
|
|
||||||
"""
|
|
||||||
cons.log.action('%s: Sign images' % sha_algo)
|
|
||||||
util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
|
|
||||||
fit])
|
|
||||||
|
|
||||||
def replace_fit_totalsize(size):
|
def replace_fit_totalsize(size):
|
||||||
"""Replace FIT header's totalsize with something greater.
|
"""Replace FIT header's totalsize with something greater.
|
||||||
|
|
||||||
|
@ -142,6 +138,22 @@ def test_vboot(u_boot_console):
|
||||||
handle.write(struct.pack(">I", size))
|
handle.write(struct.pack(">I", size))
|
||||||
return struct.unpack(">I", total_size)[0]
|
return struct.unpack(">I", total_size)[0]
|
||||||
|
|
||||||
|
def create_rsa_pair(name):
|
||||||
|
"""Generate a new RSA key paid and certificate
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of of the key (e.g. 'dev')
|
||||||
|
"""
|
||||||
|
public_exponent = 65537
|
||||||
|
util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %s%s.key '
|
||||||
|
'-pkeyopt rsa_keygen_bits:2048 '
|
||||||
|
'-pkeyopt rsa_keygen_pubexp:%d' %
|
||||||
|
(tmpdir, name, public_exponent))
|
||||||
|
|
||||||
|
# Create a certificate containing the public key
|
||||||
|
util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key '
|
||||||
|
'-out %s%s.crt' % (tmpdir, name, tmpdir, name))
|
||||||
|
|
||||||
def test_with_algo(sha_algo, padding):
|
def test_with_algo(sha_algo, padding):
|
||||||
"""Test verified boot with the given hash algorithm.
|
"""Test verified boot with the given hash algorithm.
|
||||||
|
|
||||||
|
@ -160,7 +172,7 @@ def test_vboot(u_boot_console):
|
||||||
|
|
||||||
# Build the FIT, but don't sign anything yet
|
# Build the FIT, but don't sign anything yet
|
||||||
cons.log.action('%s: Test FIT with signed images' % sha_algo)
|
cons.log.action('%s: Test FIT with signed images' % sha_algo)
|
||||||
make_fit('sign-images-%s%s.its' % (sha_algo , padding))
|
make_fit('sign-images-%s%s.its' % (sha_algo, padding))
|
||||||
run_bootm(sha_algo, 'unsigned images', 'dev-', True)
|
run_bootm(sha_algo, 'unsigned images', 'dev-', True)
|
||||||
|
|
||||||
# Sign images with our dev keys
|
# Sign images with our dev keys
|
||||||
|
@ -171,7 +183,7 @@ def test_vboot(u_boot_console):
|
||||||
dtc('sandbox-u-boot.dts')
|
dtc('sandbox-u-boot.dts')
|
||||||
|
|
||||||
cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
|
cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
|
||||||
make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
|
make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
|
||||||
run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
|
run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
|
||||||
|
|
||||||
# Sign images with our dev keys
|
# Sign images with our dev keys
|
||||||
|
@ -180,14 +192,29 @@ def test_vboot(u_boot_console):
|
||||||
|
|
||||||
cons.log.action('%s: Check signed config on the host' % sha_algo)
|
cons.log.action('%s: Check signed config on the host' % sha_algo)
|
||||||
|
|
||||||
util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir,
|
util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
|
||||||
'-k', dtb])
|
|
||||||
|
|
||||||
# Replace header bytes
|
# Make sure that U-Boot checks that the config is in the list of hashed
|
||||||
|
# nodes. If it isn't, a security bypass is possible.
|
||||||
|
with open(fit, 'rb') as fd:
|
||||||
|
root, strblock = vboot_forge.read_fdt(fd)
|
||||||
|
root, strblock = vboot_forge.manipulate(root, strblock)
|
||||||
|
with open(fit, 'w+b') as fd:
|
||||||
|
vboot_forge.write_fdt(root, strblock, fd)
|
||||||
|
util.run_and_log_expect_exception(
|
||||||
|
cons, [fit_check_sign, '-f', fit, '-k', dtb],
|
||||||
|
1, 'Failed to verify required signature')
|
||||||
|
|
||||||
|
run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False)
|
||||||
|
|
||||||
|
# Create a new properly signed fit and replace header bytes
|
||||||
|
make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
|
||||||
|
sign_fit(sha_algo)
|
||||||
bcfg = u_boot_console.config.buildconfig
|
bcfg = u_boot_console.config.buildconfig
|
||||||
max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
|
max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
|
||||||
existing_size = replace_fit_totalsize(max_size + 1)
|
existing_size = replace_fit_totalsize(max_size + 1)
|
||||||
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
|
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
|
||||||
|
False)
|
||||||
cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
|
cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
|
||||||
|
|
||||||
# Replace with existing header bytes
|
# Replace with existing header bytes
|
||||||
|
@ -205,21 +232,22 @@ def test_vboot(u_boot_console):
|
||||||
util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
|
util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
|
||||||
(fit, sig_node, sig))
|
(fit, sig_node, sig))
|
||||||
|
|
||||||
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
|
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
|
||||||
|
False)
|
||||||
|
|
||||||
cons.log.action('%s: Check bad config on the host' % sha_algo)
|
cons.log.action('%s: Check bad config on the host' % sha_algo)
|
||||||
util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit,
|
util.run_and_log_expect_exception(
|
||||||
'-k', dtb], 1, 'Failed to verify required signature')
|
cons, [fit_check_sign, '-f', fit, '-k', dtb],
|
||||||
|
1, 'Failed to verify required signature')
|
||||||
|
|
||||||
def test_required_key(sha_algo, padding):
|
def test_required_key(sha_algo, padding):
|
||||||
"""Test verified boot with the given hash algorithm.
|
"""Test verified boot with the given hash algorithm.
|
||||||
|
|
||||||
This function test if u-boot reject an image when a required
|
This function tests if U-Boot rejects an image when a required key isn't
|
||||||
key isn't used to sign a FIT.
|
used to sign a FIT.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
|
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
|
||||||
use.
|
|
||||||
"""
|
"""
|
||||||
# Compile our device tree files for kernel and U-Boot. These are
|
# Compile our device tree files for kernel and U-Boot. These are
|
||||||
# regenerated here since mkimage will modify them (by adding a
|
# regenerated here since mkimage will modify them (by adding a
|
||||||
|
@ -227,22 +255,27 @@ def test_vboot(u_boot_console):
|
||||||
dtc('sandbox-kernel.dts')
|
dtc('sandbox-kernel.dts')
|
||||||
dtc('sandbox-u-boot.dts')
|
dtc('sandbox-u-boot.dts')
|
||||||
|
|
||||||
# Build the FIT with prod key (keys required)
|
|
||||||
# Build the FIT with dev key (keys NOT required)
|
|
||||||
# The dtb contain the key prod and dev and the key prod are set as required.
|
|
||||||
# Then try to boot the FIT with dev key
|
|
||||||
# This FIT should not be accepted by u-boot because the key prod is required
|
|
||||||
cons.log.action('%s: Test FIT with configs images' % sha_algo)
|
cons.log.action('%s: Test FIT with configs images' % sha_algo)
|
||||||
make_fit('sign-configs-%s%s-prod.its' % (sha_algo , padding))
|
|
||||||
sign_fit(sha_algo)
|
# Build the FIT with prod key (keys required) and sign it. This puts the
|
||||||
make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
|
# signature into sandbox-u-boot.dtb, marked 'required'
|
||||||
|
make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
|
||||||
sign_fit(sha_algo)
|
sign_fit(sha_algo)
|
||||||
|
|
||||||
run_bootm(sha_algo, 'signed configs', '', False)
|
# Build the FIT with dev key (keys NOT required). This adds the
|
||||||
|
# signature into sandbox-u-boot.dtb, NOT marked 'required'.
|
||||||
|
make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
|
||||||
|
sign_fit(sha_algo)
|
||||||
|
|
||||||
|
# So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
|
||||||
|
# Only the prod key is set as 'required'. But FIT we just built has
|
||||||
|
# a dev signature only (sign_fit() overwrites the FIT).
|
||||||
|
# Try to boot the FIT with dev key. This FIT should not be accepted by
|
||||||
|
# U-Boot because the prod key is required.
|
||||||
|
run_bootm(sha_algo, 'required key', '', False)
|
||||||
|
|
||||||
cons = u_boot_console
|
cons = u_boot_console
|
||||||
tmpdir = cons.config.result_dir + '/'
|
tmpdir = cons.config.result_dir + '/'
|
||||||
tmp = tmpdir + 'vboot.tmp'
|
|
||||||
datadir = cons.config.source_dir + '/test/py/tests/vboot/'
|
datadir = cons.config.source_dir + '/test/py/tests/vboot/'
|
||||||
fit = '%stest.fit' % tmpdir
|
fit = '%stest.fit' % tmpdir
|
||||||
mkimage = cons.config.build_dir + '/tools/mkimage'
|
mkimage = cons.config.build_dir + '/tools/mkimage'
|
||||||
|
@ -251,42 +284,22 @@ def test_vboot(u_boot_console):
|
||||||
dtb = '%ssandbox-u-boot.dtb' % tmpdir
|
dtb = '%ssandbox-u-boot.dtb' % tmpdir
|
||||||
sig_node = '/configurations/conf-1/signature'
|
sig_node = '/configurations/conf-1/signature'
|
||||||
|
|
||||||
# Create an RSA key pair
|
create_rsa_pair('dev')
|
||||||
public_exponent = 65537
|
create_rsa_pair('prod')
|
||||||
util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sdev.key '
|
|
||||||
'-pkeyopt rsa_keygen_bits:2048 '
|
|
||||||
'-pkeyopt rsa_keygen_pubexp:%d' %
|
|
||||||
(tmpdir, public_exponent))
|
|
||||||
|
|
||||||
# Create a certificate containing the public key
|
|
||||||
util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sdev.key -out '
|
|
||||||
'%sdev.crt' % (tmpdir, tmpdir))
|
|
||||||
|
|
||||||
# Create an RSA key pair (prod)
|
|
||||||
public_exponent = 65537
|
|
||||||
util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sprod.key '
|
|
||||||
'-pkeyopt rsa_keygen_bits:2048 '
|
|
||||||
'-pkeyopt rsa_keygen_pubexp:%d' %
|
|
||||||
(tmpdir, public_exponent))
|
|
||||||
|
|
||||||
# Create a certificate containing the public key (prod)
|
|
||||||
util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sprod.key -out '
|
|
||||||
'%sprod.crt' % (tmpdir, tmpdir))
|
|
||||||
|
|
||||||
# Create a number kernel image with zeroes
|
# Create a number kernel image with zeroes
|
||||||
with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
|
with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
|
||||||
fd.write(5000 * chr(0))
|
fd.write(500 * chr(0))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# We need to use our own device tree file. Remember to restore it
|
# We need to use our own device tree file. Remember to restore it
|
||||||
# afterwards.
|
# afterwards.
|
||||||
old_dtb = cons.config.dtb
|
old_dtb = cons.config.dtb
|
||||||
cons.config.dtb = dtb
|
cons.config.dtb = dtb
|
||||||
test_with_algo('sha1','')
|
if required:
|
||||||
test_with_algo('sha1','-pss')
|
test_required_key(sha_algo, padding)
|
||||||
test_with_algo('sha256','')
|
else:
|
||||||
test_with_algo('sha256','-pss')
|
test_with_algo(sha_algo, padding)
|
||||||
test_required_key('sha256','-pss')
|
|
||||||
finally:
|
finally:
|
||||||
# Go back to the original U-Boot with the correct dtb.
|
# Go back to the original U-Boot with the correct dtb.
|
||||||
cons.config.dtb = old_dtb
|
cons.config.dtb = old_dtb
|
||||||
|
|
423
test/py/tests/vboot_forge.py
Normal file
423
test/py/tests/vboot_forge.py
Normal file
|
@ -0,0 +1,423 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
# Copyright (c) 2020, F-Secure Corporation, https://foundry.f-secure.com
|
||||||
|
#
|
||||||
|
# pylint: disable=E1101,W0201,C0103
|
||||||
|
|
||||||
|
"""
|
||||||
|
Verified boot image forgery tools and utilities
|
||||||
|
|
||||||
|
This module provides services to both take apart and regenerate FIT images
|
||||||
|
in a way that preserves all existing verified boot signatures, unless you
|
||||||
|
manipulate nodes in the process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import binascii
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
#
|
||||||
|
# struct parsing helpers
|
||||||
|
#
|
||||||
|
|
||||||
|
class BetterStructMeta(type):
|
||||||
|
"""
|
||||||
|
Preprocesses field definitions and creates a struct.Struct instance from them
|
||||||
|
"""
|
||||||
|
def __new__(cls, clsname, superclasses, attributedict):
|
||||||
|
if clsname != 'BetterStruct':
|
||||||
|
fields = attributedict['__fields__']
|
||||||
|
field_types = [_[0] for _ in fields]
|
||||||
|
field_names = [_[1] for _ in fields if _[1] is not None]
|
||||||
|
attributedict['__names__'] = field_names
|
||||||
|
s = struct.Struct(attributedict.get('__endian__', '') + ''.join(field_types))
|
||||||
|
attributedict['__struct__'] = s
|
||||||
|
attributedict['size'] = s.size
|
||||||
|
return type.__new__(cls, clsname, superclasses, attributedict)
|
||||||
|
|
||||||
|
class BetterStruct(metaclass=BetterStructMeta):
|
||||||
|
"""
|
||||||
|
Base class for better structures
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
for t, n in self.__fields__:
|
||||||
|
if 's' in t:
|
||||||
|
setattr(self, n, '')
|
||||||
|
elif t in ('Q', 'I', 'H', 'B'):
|
||||||
|
setattr(self, n, 0)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unpack_from(cls, buffer, offset=0):
|
||||||
|
"""
|
||||||
|
Unpack structure instance from a buffer
|
||||||
|
"""
|
||||||
|
fields = cls.__struct__.unpack_from(buffer, offset)
|
||||||
|
instance = cls()
|
||||||
|
for n, v in zip(cls.__names__, fields):
|
||||||
|
setattr(instance, n, v)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def pack(self):
|
||||||
|
"""
|
||||||
|
Pack structure instance into bytes
|
||||||
|
"""
|
||||||
|
return self.__struct__.pack(*[getattr(self, n) for n in self.__names__])
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
items = ["'%s': %s" % (n, repr(getattr(self, n))) for n in self.__names__ if n is not None]
|
||||||
|
return '(' + ', '.join(items) + ')'
|
||||||
|
|
||||||
|
#
|
||||||
|
# some defs for flat DT data
|
||||||
|
#
|
||||||
|
|
||||||
|
class HeaderV17(BetterStruct):
|
||||||
|
__endian__ = '>'
|
||||||
|
__fields__ = [
|
||||||
|
('I', 'magic'),
|
||||||
|
('I', 'totalsize'),
|
||||||
|
('I', 'off_dt_struct'),
|
||||||
|
('I', 'off_dt_strings'),
|
||||||
|
('I', 'off_mem_rsvmap'),
|
||||||
|
('I', 'version'),
|
||||||
|
('I', 'last_comp_version'),
|
||||||
|
('I', 'boot_cpuid_phys'),
|
||||||
|
('I', 'size_dt_strings'),
|
||||||
|
('I', 'size_dt_struct'),
|
||||||
|
]
|
||||||
|
|
||||||
|
class RRHeader(BetterStruct):
|
||||||
|
__endian__ = '>'
|
||||||
|
__fields__ = [
|
||||||
|
('Q', 'address'),
|
||||||
|
('Q', 'size'),
|
||||||
|
]
|
||||||
|
|
||||||
|
class PropHeader(BetterStruct):
|
||||||
|
__endian__ = '>'
|
||||||
|
__fields__ = [
|
||||||
|
('I', 'value_size'),
|
||||||
|
('I', 'name_offset'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# magical constants for DTB format
|
||||||
|
OF_DT_HEADER = 0xd00dfeed
|
||||||
|
OF_DT_BEGIN_NODE = 1
|
||||||
|
OF_DT_END_NODE = 2
|
||||||
|
OF_DT_PROP = 3
|
||||||
|
OF_DT_END = 9
|
||||||
|
|
||||||
|
class StringsBlock:
|
||||||
|
"""
|
||||||
|
Represents a parsed device tree string block
|
||||||
|
"""
|
||||||
|
def __init__(self, values=None):
|
||||||
|
if values is None:
|
||||||
|
self.values = []
|
||||||
|
else:
|
||||||
|
self.values = values
|
||||||
|
|
||||||
|
def __getitem__(self, at):
|
||||||
|
if isinstance(at, str):
|
||||||
|
offset = 0
|
||||||
|
for value in self.values:
|
||||||
|
if value == at:
|
||||||
|
break
|
||||||
|
offset += len(value) + 1
|
||||||
|
else:
|
||||||
|
self.values.append(at)
|
||||||
|
return offset
|
||||||
|
|
||||||
|
if isinstance(at, int):
|
||||||
|
offset = 0
|
||||||
|
for value in self.values:
|
||||||
|
if offset == at:
|
||||||
|
return value
|
||||||
|
offset += len(value) + 1
|
||||||
|
raise IndexError('no string found corresponding to the given offset')
|
||||||
|
|
||||||
|
raise TypeError('only strings and integers are accepted')
|
||||||
|
|
||||||
|
class Prop:
|
||||||
|
"""
|
||||||
|
Represents a parsed device tree property
|
||||||
|
"""
|
||||||
|
def __init__(self, name=None, value=None):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
return Prop(self.name, self.value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Prop(name='%s', value=%s>" % (self.name, repr(self.value))
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
"""
|
||||||
|
Represents a parsed device tree node
|
||||||
|
"""
|
||||||
|
def __init__(self, name=None):
|
||||||
|
self.name = name
|
||||||
|
self.props = []
|
||||||
|
self.children = []
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
o = Node(self.name)
|
||||||
|
o.props = [x.clone() for x in self.props]
|
||||||
|
o.children = [x.clone() for x in self.children]
|
||||||
|
return o
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return self.children[index]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Node('%s'), %s, %s>" % (self.name, repr(self.props), repr(self.children))
|
||||||
|
|
||||||
|
#
|
||||||
|
# flat DT to memory
|
||||||
|
#
|
||||||
|
|
||||||
|
def parse_strings(strings):
|
||||||
|
"""
|
||||||
|
Converts the bytes into a StringsBlock instance so it is convenient to work with
|
||||||
|
"""
|
||||||
|
strings = strings.split(b'\x00')
|
||||||
|
return StringsBlock(strings)
|
||||||
|
|
||||||
|
def parse_struct(stream):
|
||||||
|
"""
|
||||||
|
Parses DTB structure(s) into a Node or Prop instance
|
||||||
|
"""
|
||||||
|
tag = bytearray(stream.read(4))[3]
|
||||||
|
if tag == OF_DT_BEGIN_NODE:
|
||||||
|
name = b''
|
||||||
|
while b'\x00' not in name:
|
||||||
|
name += stream.read(4)
|
||||||
|
name = name.rstrip(b'\x00')
|
||||||
|
node = Node(name)
|
||||||
|
|
||||||
|
item = parse_struct(stream)
|
||||||
|
while item is not None:
|
||||||
|
if isinstance(item, Node):
|
||||||
|
node.children.append(item)
|
||||||
|
elif isinstance(item, Prop):
|
||||||
|
node.props.append(item)
|
||||||
|
item = parse_struct(stream)
|
||||||
|
|
||||||
|
return node
|
||||||
|
|
||||||
|
if tag == OF_DT_PROP:
|
||||||
|
h = PropHeader.unpack_from(stream.read(PropHeader.size))
|
||||||
|
length = (h.value_size + 3) & (~3)
|
||||||
|
value = stream.read(length)[:h.value_size]
|
||||||
|
prop = Prop(h.name_offset, value)
|
||||||
|
return prop
|
||||||
|
|
||||||
|
if tag in (OF_DT_END_NODE, OF_DT_END):
|
||||||
|
return None
|
||||||
|
|
||||||
|
raise ValueError('unexpected tag value')
|
||||||
|
|
||||||
|
def read_fdt(fp):
|
||||||
|
"""
|
||||||
|
Reads and parses the flattened device tree (or derivatives like FIT)
|
||||||
|
"""
|
||||||
|
header = HeaderV17.unpack_from(fp.read(HeaderV17.size))
|
||||||
|
if header.magic != OF_DT_HEADER:
|
||||||
|
raise ValueError('invalid magic value %08x; expected %08x' % (header.magic, OF_DT_HEADER))
|
||||||
|
# TODO: read/parse reserved regions
|
||||||
|
fp.seek(header.off_dt_struct)
|
||||||
|
structs = fp.read(header.size_dt_struct)
|
||||||
|
fp.seek(header.off_dt_strings)
|
||||||
|
strings = fp.read(header.size_dt_strings)
|
||||||
|
strblock = parse_strings(strings)
|
||||||
|
root = parse_struct(BytesIO(structs))
|
||||||
|
|
||||||
|
return root, strblock
|
||||||
|
|
||||||
|
#
|
||||||
|
# memory to flat DT
|
||||||
|
#
|
||||||
|
|
||||||
|
def compose_structs_r(item):
|
||||||
|
"""
|
||||||
|
Recursive part of composing Nodes and Props into a bytearray
|
||||||
|
"""
|
||||||
|
t = bytearray()
|
||||||
|
|
||||||
|
if isinstance(item, Node):
|
||||||
|
t.extend(struct.pack('>I', OF_DT_BEGIN_NODE))
|
||||||
|
if isinstance(item.name, str):
|
||||||
|
item.name = bytes(item.name, 'utf-8')
|
||||||
|
name = item.name + b'\x00'
|
||||||
|
if len(name) & 3:
|
||||||
|
name += b'\x00' * (4 - (len(name) & 3))
|
||||||
|
t.extend(name)
|
||||||
|
for p in item.props:
|
||||||
|
t.extend(compose_structs_r(p))
|
||||||
|
for c in item.children:
|
||||||
|
t.extend(compose_structs_r(c))
|
||||||
|
t.extend(struct.pack('>I', OF_DT_END_NODE))
|
||||||
|
|
||||||
|
elif isinstance(item, Prop):
|
||||||
|
t.extend(struct.pack('>I', OF_DT_PROP))
|
||||||
|
value = item.value
|
||||||
|
h = PropHeader()
|
||||||
|
h.name_offset = item.name
|
||||||
|
if value:
|
||||||
|
h.value_size = len(value)
|
||||||
|
t.extend(h.pack())
|
||||||
|
if len(value) & 3:
|
||||||
|
value += b'\x00' * (4 - (len(value) & 3))
|
||||||
|
t.extend(value)
|
||||||
|
else:
|
||||||
|
h.value_size = 0
|
||||||
|
t.extend(h.pack())
|
||||||
|
|
||||||
|
return t
|
||||||
|
|
||||||
|
def compose_structs(root):
|
||||||
|
"""
|
||||||
|
Composes the parsed Nodes into a flat bytearray instance
|
||||||
|
"""
|
||||||
|
t = compose_structs_r(root)
|
||||||
|
t.extend(struct.pack('>I', OF_DT_END))
|
||||||
|
return t
|
||||||
|
|
||||||
|
def compose_strings(strblock):
|
||||||
|
"""
|
||||||
|
Composes the StringsBlock instance back into a bytearray instance
|
||||||
|
"""
|
||||||
|
b = bytearray()
|
||||||
|
for s in strblock.values:
|
||||||
|
b.extend(s)
|
||||||
|
b.append(0)
|
||||||
|
return bytes(b)
|
||||||
|
|
||||||
|
def write_fdt(root, strblock, fp):
|
||||||
|
"""
|
||||||
|
Writes out a complete flattened device tree (or FIT)
|
||||||
|
"""
|
||||||
|
header = HeaderV17()
|
||||||
|
header.magic = OF_DT_HEADER
|
||||||
|
header.version = 17
|
||||||
|
header.last_comp_version = 16
|
||||||
|
fp.write(header.pack())
|
||||||
|
|
||||||
|
header.off_mem_rsvmap = fp.tell()
|
||||||
|
fp.write(RRHeader().pack())
|
||||||
|
|
||||||
|
structs = compose_structs(root)
|
||||||
|
header.off_dt_struct = fp.tell()
|
||||||
|
header.size_dt_struct = len(structs)
|
||||||
|
fp.write(structs)
|
||||||
|
|
||||||
|
strings = compose_strings(strblock)
|
||||||
|
header.off_dt_strings = fp.tell()
|
||||||
|
header.size_dt_strings = len(strings)
|
||||||
|
fp.write(strings)
|
||||||
|
|
||||||
|
header.totalsize = fp.tell()
|
||||||
|
|
||||||
|
fp.seek(0)
|
||||||
|
fp.write(header.pack())
|
||||||
|
|
||||||
|
#
|
||||||
|
# pretty printing / converting to DT source
|
||||||
|
#
|
||||||
|
|
||||||
|
def as_bytes(value):
|
||||||
|
return ' '.join(["%02X" % x for x in value])
|
||||||
|
|
||||||
|
def prety_print_value(value):
|
||||||
|
"""
|
||||||
|
Formats a property value as appropriate depending on the guessed data type
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
return '""'
|
||||||
|
if value[-1] == b'\x00':
|
||||||
|
printable = True
|
||||||
|
for x in value[:-1]:
|
||||||
|
x = ord(x)
|
||||||
|
if x != 0 and (x < 0x20 or x > 0x7F):
|
||||||
|
printable = False
|
||||||
|
break
|
||||||
|
if printable:
|
||||||
|
value = value[:-1]
|
||||||
|
return ', '.join('"' + x + '"' for x in value.split(b'\x00'))
|
||||||
|
if len(value) > 0x80:
|
||||||
|
return '[' + as_bytes(value[:0x80]) + ' ... ]'
|
||||||
|
return '[' + as_bytes(value) + ']'
|
||||||
|
|
||||||
|
def pretty_print_r(node, strblock, indent=0):
|
||||||
|
"""
|
||||||
|
Prints out a single node, recursing further for each of its children
|
||||||
|
"""
|
||||||
|
spaces = ' ' * indent
|
||||||
|
print((spaces + '%s {' % (node.name.decode('utf-8') if node.name else '/')))
|
||||||
|
for p in node.props:
|
||||||
|
print((spaces + ' %s = %s;' % (strblock[p.name].decode('utf-8'), prety_print_value(p.value))))
|
||||||
|
for c in node.children:
|
||||||
|
pretty_print_r(c, strblock, indent+1)
|
||||||
|
print((spaces + '};'))
|
||||||
|
|
||||||
|
def pretty_print(node, strblock):
|
||||||
|
"""
|
||||||
|
Generates an almost-DTS formatted printout of the parsed device tree
|
||||||
|
"""
|
||||||
|
print('/dts-v1/;')
|
||||||
|
pretty_print_r(node, strblock, 0)
|
||||||
|
|
||||||
|
#
|
||||||
|
# manipulating the DT structure
|
||||||
|
#
|
||||||
|
|
||||||
|
def manipulate(root, strblock):
|
||||||
|
"""
|
||||||
|
Maliciously manipulates the structure to create a crafted FIT file
|
||||||
|
"""
|
||||||
|
# locate /images/kernel@1 (frankly, it just expects it to be the first one)
|
||||||
|
kernel_node = root[0][0]
|
||||||
|
# clone it to save time filling all the properties
|
||||||
|
fake_kernel = kernel_node.clone()
|
||||||
|
# rename the node
|
||||||
|
fake_kernel.name = b'kernel@2'
|
||||||
|
# get rid of signatures/hashes
|
||||||
|
fake_kernel.children = []
|
||||||
|
# NOTE: this simply replaces the first prop... either description or data
|
||||||
|
# should be good for testing purposes
|
||||||
|
fake_kernel.props[0].value = b'Super 1337 kernel\x00'
|
||||||
|
# insert the new kernel node under /images
|
||||||
|
root[0].children.append(fake_kernel)
|
||||||
|
|
||||||
|
# modify the default configuration
|
||||||
|
root[1].props[0].value = b'conf@2\x00'
|
||||||
|
# clone the first (only?) configuration
|
||||||
|
fake_conf = root[1][0].clone()
|
||||||
|
# rename and change kernel and fdt properties to select the crafted kernel
|
||||||
|
fake_conf.name = b'conf@2'
|
||||||
|
fake_conf.props[0].value = b'kernel@2\x00'
|
||||||
|
fake_conf.props[1].value = b'fdt@1\x00'
|
||||||
|
# insert the new configuration under /configurations
|
||||||
|
root[1].children.append(fake_conf)
|
||||||
|
|
||||||
|
return root, strblock
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
with open(argv[1], 'rb') as fp:
|
||||||
|
root, strblock = read_fdt(fp)
|
||||||
|
|
||||||
|
print("Before:")
|
||||||
|
pretty_print(root, strblock)
|
||||||
|
|
||||||
|
root, strblock = manipulate(root, strblock)
|
||||||
|
print("After:")
|
||||||
|
pretty_print(root, strblock)
|
||||||
|
|
||||||
|
with open('blah', 'w+b') as fp:
|
||||||
|
write_fdt(root, strblock, fp)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
main(sys.argv)
|
||||||
|
# EOF
|
|
@ -27,6 +27,7 @@
|
||||||
*/
|
*/
|
||||||
int fdt_remove_unused_strings(const void *old, void *new);
|
int fdt_remove_unused_strings(const void *old, void *new);
|
||||||
|
|
||||||
int fit_check_sign(const void *working_fdt, const void *key);
|
int fit_check_sign(const void *fit, const void *key,
|
||||||
|
const char *fit_uname_config);
|
||||||
|
|
||||||
#endif /* __FDT_HOST_H__ */
|
#endif /* __FDT_HOST_H__ */
|
||||||
|
|
|
@ -41,6 +41,7 @@ int main(int argc, char **argv)
|
||||||
void *fit_blob;
|
void *fit_blob;
|
||||||
char *fdtfile = NULL;
|
char *fdtfile = NULL;
|
||||||
char *keyfile = NULL;
|
char *keyfile = NULL;
|
||||||
|
char *config_name = NULL;
|
||||||
char cmdname[256];
|
char cmdname[256];
|
||||||
int ret;
|
int ret;
|
||||||
void *key_blob;
|
void *key_blob;
|
||||||
|
@ -48,7 +49,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
strncpy(cmdname, *argv, sizeof(cmdname) - 1);
|
strncpy(cmdname, *argv, sizeof(cmdname) - 1);
|
||||||
cmdname[sizeof(cmdname) - 1] = '\0';
|
cmdname[sizeof(cmdname) - 1] = '\0';
|
||||||
while ((c = getopt(argc, argv, "f:k:")) != -1)
|
while ((c = getopt(argc, argv, "f:k:c:")) != -1)
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'f':
|
case 'f':
|
||||||
fdtfile = optarg;
|
fdtfile = optarg;
|
||||||
|
@ -56,6 +57,9 @@ int main(int argc, char **argv)
|
||||||
case 'k':
|
case 'k':
|
||||||
keyfile = optarg;
|
keyfile = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'c':
|
||||||
|
config_name = optarg;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
usage(cmdname);
|
usage(cmdname);
|
||||||
break;
|
break;
|
||||||
|
@ -78,7 +82,7 @@ int main(int argc, char **argv)
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
image_set_host_blob(key_blob);
|
image_set_host_blob(key_blob);
|
||||||
ret = fit_check_sign(fit_blob, key_blob);
|
ret = fit_check_sign(fit_blob, key_blob, config_name);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
ret = EXIT_SUCCESS;
|
ret = EXIT_SUCCESS;
|
||||||
fprintf(stderr, "Signature check OK\n");
|
fprintf(stderr, "Signature check OK\n");
|
||||||
|
|
|
@ -170,7 +170,7 @@ static int fit_image_setup_sig(struct image_sign_info *info,
|
||||||
|
|
||||||
memset(info, '\0', sizeof(*info));
|
memset(info, '\0', sizeof(*info));
|
||||||
info->keydir = keydir;
|
info->keydir = keydir;
|
||||||
info->keyname = fdt_getprop(fit, noffset, "key-name-hint", NULL);
|
info->keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL);
|
||||||
info->fit = fit;
|
info->fit = fit;
|
||||||
info->node_offset = noffset;
|
info->node_offset = noffset;
|
||||||
info->name = strdup(algo_name);
|
info->name = strdup(algo_name);
|
||||||
|
@ -249,7 +249,7 @@ static int fit_image_process_sig(const char *keydir, void *keydest,
|
||||||
free(value);
|
free(value);
|
||||||
|
|
||||||
/* Get keyname again, as FDT has changed and invalidated our pointer */
|
/* Get keyname again, as FDT has changed and invalidated our pointer */
|
||||||
info.keyname = fdt_getprop(fit, noffset, "key-name-hint", NULL);
|
info.keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write the public key into the supplied FDT file; this might fail
|
* Write the public key into the supplied FDT file; this might fail
|
||||||
|
@ -337,7 +337,7 @@ static int fit_image_setup_cipher(struct image_cipher_info *info,
|
||||||
info->keydir = keydir;
|
info->keydir = keydir;
|
||||||
|
|
||||||
/* Read the key name */
|
/* Read the key name */
|
||||||
info->keyname = fdt_getprop(fit, noffset, "key-name-hint", NULL);
|
info->keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL);
|
||||||
if (!info->keyname) {
|
if (!info->keyname) {
|
||||||
printf("Can't get key name for cipher '%s' in image '%s'\n",
|
printf("Can't get key name for cipher '%s' in image '%s'\n",
|
||||||
node_name, image_name);
|
node_name, image_name);
|
||||||
|
@ -886,7 +886,7 @@ static int fit_config_process_sig(const char *keydir, void *keydest,
|
||||||
free(region_prop);
|
free(region_prop);
|
||||||
|
|
||||||
/* Get keyname again, as FDT has changed and invalidated our pointer */
|
/* Get keyname again, as FDT has changed and invalidated our pointer */
|
||||||
info.keyname = fdt_getprop(fit, noffset, "key-name-hint", NULL);
|
info.keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL);
|
||||||
|
|
||||||
/* Write the public key into the supplied FDT file */
|
/* Write the public key into the supplied FDT file */
|
||||||
if (keydest) {
|
if (keydest) {
|
||||||
|
@ -1025,19 +1025,22 @@ int fit_add_verification_data(const char *keydir, void *keydest, void *fit,
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_FIT_SIGNATURE
|
#ifdef CONFIG_FIT_SIGNATURE
|
||||||
int fit_check_sign(const void *fit, const void *key)
|
int fit_check_sign(const void *fit, const void *key,
|
||||||
|
const char *fit_uname_config)
|
||||||
{
|
{
|
||||||
int cfg_noffset;
|
int cfg_noffset;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
cfg_noffset = fit_conf_get_node(fit, NULL);
|
cfg_noffset = fit_conf_get_node(fit, fit_uname_config);
|
||||||
if (!cfg_noffset)
|
if (!cfg_noffset)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
printf("Verifying Hash Integrity ... ");
|
printf("Verifying Hash Integrity for node '%s'... ",
|
||||||
|
fdt_get_name(fit, cfg_noffset, NULL));
|
||||||
ret = fit_config_verify(fit, cfg_noffset);
|
ret = fit_config_verify(fit, cfg_noffset);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
printf("Verified OK, loading images\n");
|
||||||
ret = bootm_host_load_images(fit, cfg_noffset);
|
ret = bootm_host_load_images(fit, cfg_noffset);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
Loading…
Add table
Reference in a new issue