summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2020-04-01 14:29:21 -0400
committerTom Rini <trini@konsulko.com>2020-04-01 14:29:21 -0400
commite0718b3ab754860bd47677e6b4fc5b70da42c4ab (patch)
treed10a15f1a7ab4ac7bb45301cc2a4560975341c6c
parente88c9e6ff15144f64f031f6a7b9323a096ab5a4d (diff)
parent0e29648f8e7e0aa60c0f7efe9d2efed98f8c0c6e (diff)
downloadu-boot-WIP/01Apr2020.tar.gz
Merge tag 'dm-pull-1apr20' of git://git.denx.de/u-boot-dmWIP/01Apr2020
Vboot vulnerability fix
-rw-r--r--common/bootm.c6
-rw-r--r--common/image-cipher.c2
-rw-r--r--common/image-fit.c26
-rw-r--r--common/image-sig.c49
-rw-r--r--include/image.h24
-rw-r--r--lib/rsa/rsa-sign.c6
-rw-r--r--test/py/tests/test_vboot.py155
-rw-r--r--test/py/tests/vboot_forge.py423
-rw-r--r--tools/fdt_host.h3
-rw-r--r--tools/fit_check_sign.c8
-rw-r--r--tools/image-host.c17
11 files changed, 601 insertions, 118 deletions
diff --git a/common/bootm.c b/common/bootm.c
index 902c13880d..db4362a643 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -819,7 +819,8 @@ void __weak switch_to_non_secure_mode(void)
#else /* USE_HOSTCC */
#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;
ulong data, len;
@@ -831,6 +832,7 @@ static int bootm_host_load_image(const void *fit, int req_image_type)
void *load_buf;
int ret;
+ fit_uname_config = fdt_get_name(fit, cfg_noffset, NULL);
memset(&images, '\0', sizeof(images));
images.verify = 1;
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++) {
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)
err = ret;
}
diff --git a/common/image-cipher.c b/common/image-cipher.c
index cee3b03ee5..f50c3d31bd 100644
--- a/common/image-cipher.c
+++ b/common/image-cipher.c
@@ -88,7 +88,7 @@ static int fit_image_setup_decrypt(struct image_cipher_info *info,
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) {
printf("Can't get key name\n");
return -1;
diff --git a/common/image-fit.c b/common/image-fit.c
index 6da69d25ff..0fef0a918d 100644
--- a/common/image-fit.c
+++ b/common/image-fit.c
@@ -168,7 +168,7 @@ static void fit_image_print_data(const void *fit, int noffset, const char *p,
int value_len;
char *algo;
const char *padding;
- int required;
+ bool required;
int ret, i;
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;
}
printf("%s", algo);
- keyname = fdt_getprop(fit, noffset, "key-name-hint", NULL);
- required = fdt_getprop(fit, noffset, "required", NULL) != NULL;
+ keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL);
+ required = fdt_getprop(fit, noffset, FIT_KEY_REQUIRED, NULL) != NULL;
if (keyname)
printf(":%s", keyname);
if (required)
@@ -1712,24 +1712,6 @@ int fit_conf_find_compat(const void *fit, const void *fdt)
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 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);
}
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);
return -ENOENT;
}
diff --git a/common/image-sig.c b/common/image-sig.c
index 639a112450..6563effcf3 100644
--- a/common/image-sig.c
+++ b/common/image-sig.c
@@ -229,7 +229,7 @@ static int fit_image_setup_verify(struct image_sign_info *info,
padding_name = RSA_DEFAULT_PADDING_NAME;
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->node_offset = noffset;
info->name = algo_name;
@@ -340,7 +340,8 @@ int fit_image_verify_required_sigs(const void *fit, int image_noffset,
const char *required;
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"))
continue;
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;
}
-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"};
const char *prop, *end, *name;
struct image_sign_info info;
const uint32_t *strings;
+ const char *config_name;
uint8_t *fit_value;
int fit_value_len;
+ bool found_config;
int max_regions;
int i, prop_len;
char path[200];
int count;
+ config_name = fit_get_name(fit, conf_noffset, NULL);
debug("%s: fdt=%p, conf='%s', sig='%s'\n", __func__, gd_fdt_blob(),
fit_get_name(fit, noffset, 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];
debug("Hash nodes (%d):\n", count);
+ found_config = false;
for (name = prop, i = 0; name < end; name += strlen(name) + 1, i++) {
debug(" '%s'\n", 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,
strlen(FIT_SIG_NODENAME))) {
ret = fit_config_check_sig(fit, noffset, sig_offset,
- &err_msg);
+ conf_noffset, &err_msg);
if (ret) {
puts("- ");
} else {
@@ -499,13 +530,14 @@ static int fit_config_verify_sig(const void *fit, int conf_noffset,
goto error;
}
- return verified ? 0 : -EPERM;
+ if (verified)
+ return 0;
error:
printf(" error!\n%s for '%s' hash node in '%s' config node\n",
err_msg, fit_get_name(fit, 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,
@@ -526,7 +558,8 @@ int fit_config_verify_required_sigs(const void *fit, int conf_noffset,
const char *required;
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"))
continue;
ret = fit_config_verify_sig(fit, conf_noffset, sig_blob,
diff --git a/include/image.h b/include/image.h
index b316d167d8..3ffc0fdd68 100644
--- a/include/image.h
+++ b/include/image.h
@@ -939,12 +939,14 @@ int booti_setup(ulong image, ulong *relocated_addr, ulong *size,
#define FIT_IMAGES_PATH "/images"
#define FIT_CONFS_PATH "/configurations"
-/* hash/signature node */
+/* hash/signature/key node */
#define FIT_HASH_NODENAME "hash"
#define FIT_ALGO_PROP "algo"
#define FIT_VALUE_PROP "value"
#define FIT_IGNORE_PROP "uboot-ignore"
#define FIT_SIG_NODENAME "signature"
+#define FIT_KEY_REQUIRED "required"
+#define FIT_KEY_HINT "key-name-hint"
/* cipher node */
#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_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_prop_node_count(const void *fit, int noffset,
const char *prop_name);
int fit_conf_get_prop_node_index(const void *fit, int noffset,
diff --git a/lib/rsa/rsa-sign.c b/lib/rsa/rsa-sign.c
index 6400ef63d6..580c744709 100644
--- a/lib/rsa/rsa-sign.c
+++ b/lib/rsa/rsa-sign.c
@@ -792,8 +792,8 @@ int rsa_add_verify_data(struct image_sign_info *info, void *keydest)
}
if (!ret) {
- ret = fdt_setprop_string(keydest, node, "key-name-hint",
- info->keyname);
+ ret = fdt_setprop_string(keydest, node, FIT_KEY_HINT,
+ info->keyname);
}
if (!ret)
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);
}
if (!ret && info->require_keys) {
- ret = fdt_setprop_string(keydest, node, "required",
+ ret = fdt_setprop_string(keydest, node, FIT_KEY_REQUIRED,
info->require_keys);
}
done:
diff --git a/test/py/tests/test_vboot.py b/test/py/tests/test_vboot.py
index 9c41ee56b1..e67f2b3d0f 100644
--- a/test/py/tests/test_vboot.py
+++ b/test/py/tests/test_vboot.py
@@ -24,10 +24,18 @@ For configuration verification:
Tests run with both SHA1 and SHA256 hashing.
"""
-import pytest
-import sys
import struct
+import pytest
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.buildconfigspec('fit_signature')
@@ -35,7 +43,8 @@ import u_boot_utils as util
@pytest.mark.requiredtool('fdtget')
@pytest.mark.requiredtool('fdtput')
@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'.
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)):
output = cons.run_command_list(
['host load hostfs - 100 %stest.fit' % tmpdir,
- 'fdt addr 100',
- 'bootm 100'])
- assert(expect_string in ''.join(output))
+ 'fdt addr 100',
+ 'bootm 100'])
+ assert expect_string in ''.join(output)
if boots:
- assert('sandbox: continuing, as we cannot run' in ''.join(output))
+ assert 'sandbox: continuing, as we cannot run' in ''.join(output)
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):
"""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,
'-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):
"""Replace FIT header's totalsize with something greater.
@@ -142,6 +138,22 @@ def test_vboot(u_boot_console):
handle.write(struct.pack(">I", size))
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):
"""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
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)
# Sign images with our dev keys
@@ -171,7 +183,7 @@ def test_vboot(u_boot_console):
dtc('sandbox-u-boot.dts')
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)
# 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)
- util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir,
- '-k', dtb])
+ util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
+
+ # 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)
- # Replace header bytes
+ # 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
max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
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)
# 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' %
(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)
- util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit,
- '-k', dtb], 1, 'Failed to verify required signature')
+ util.run_and_log_expect_exception(
+ cons, [fit_check_sign, '-f', fit, '-k', dtb],
+ 1, 'Failed to verify required signature')
def test_required_key(sha_algo, padding):
"""Test verified boot with the given hash algorithm.
- This function test if u-boot reject an image when a required
- key isn't used to sign a FIT.
+ This function tests if U-Boot rejects an image when a required key isn't
+ used to sign a FIT.
Args:
- sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
- use.
+ sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
"""
# Compile our device tree files for kernel and U-Boot. These are
# 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-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)
- make_fit('sign-configs-%s%s-prod.its' % (sha_algo , padding))
+
+ # Build the FIT with prod key (keys required) and sign it. This puts the
+ # signature into sandbox-u-boot.dtb, marked 'required'
+ make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
sign_fit(sha_algo)
- make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
+
+ # 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)
- run_bootm(sha_algo, 'signed configs', '', False)
+ # 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
tmpdir = cons.config.result_dir + '/'
- tmp = tmpdir + 'vboot.tmp'
datadir = cons.config.source_dir + '/test/py/tests/vboot/'
fit = '%stest.fit' % tmpdir
mkimage = cons.config.build_dir + '/tools/mkimage'
@@ -251,42 +284,22 @@ def test_vboot(u_boot_console):
dtb = '%ssandbox-u-boot.dtb' % tmpdir
sig_node = '/configurations/conf-1/signature'
- # Create an RSA key pair
- public_exponent = 65537
- 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_rsa_pair('dev')
+ create_rsa_pair('prod')
# Create a number kernel image with zeroes
with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
- fd.write(5000 * chr(0))
+ fd.write(500 * chr(0))
try:
# We need to use our own device tree file. Remember to restore it
# afterwards.
old_dtb = cons.config.dtb
cons.config.dtb = dtb
- test_with_algo('sha1','')
- test_with_algo('sha1','-pss')
- test_with_algo('sha256','')
- test_with_algo('sha256','-pss')
- test_required_key('sha256','-pss')
+ if required:
+ test_required_key(sha_algo, padding)
+ else:
+ test_with_algo(sha_algo, padding)
finally:
# Go back to the original U-Boot with the correct dtb.
cons.config.dtb = old_dtb
diff --git a/test/py/tests/vboot_forge.py b/test/py/tests/vboot_forge.py
new file mode 100644
index 0000000000..0fb7ef4024
--- /dev/null
+++ b/test/py/tests/vboot_forge.py
@@ -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
diff --git a/tools/fdt_host.h b/tools/fdt_host.h
index 99b009b221..15c07c7a96 100644
--- a/tools/fdt_host.h
+++ b/tools/fdt_host.h
@@ -27,6 +27,7 @@
*/
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__ */
diff --git a/tools/fit_check_sign.c b/tools/fit_check_sign.c
index 4528743792..9375d5cf72 100644
--- a/tools/fit_check_sign.c
+++ b/tools/fit_check_sign.c
@@ -41,6 +41,7 @@ int main(int argc, char **argv)
void *fit_blob;
char *fdtfile = NULL;
char *keyfile = NULL;
+ char *config_name = NULL;
char cmdname[256];
int ret;
void *key_blob;
@@ -48,7 +49,7 @@ int main(int argc, char **argv)
strncpy(cmdname, *argv, sizeof(cmdname) - 1);
cmdname[sizeof(cmdname) - 1] = '\0';
- while ((c = getopt(argc, argv, "f:k:")) != -1)
+ while ((c = getopt(argc, argv, "f:k:c:")) != -1)
switch (c) {
case 'f':
fdtfile = optarg;
@@ -56,6 +57,9 @@ int main(int argc, char **argv)
case 'k':
keyfile = optarg;
break;
+ case 'c':
+ config_name = optarg;
+ break;
default:
usage(cmdname);
break;
@@ -78,7 +82,7 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
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) {
ret = EXIT_SUCCESS;
fprintf(stderr, "Signature check OK\n");
diff --git a/tools/image-host.c b/tools/image-host.c
index 76a361b9d6..4e57ddea96 100644
--- a/tools/image-host.c
+++ b/tools/image-host.c
@@ -170,7 +170,7 @@ static int fit_image_setup_sig(struct image_sign_info *info,
memset(info, '\0', sizeof(*info));
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->node_offset = noffset;
info->name = strdup(algo_name);
@@ -249,7 +249,7 @@ static int fit_image_process_sig(const char *keydir, void *keydest,
free(value);
/* 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
@@ -337,7 +337,7 @@ static int fit_image_setup_cipher(struct image_cipher_info *info,
info->keydir = keydir;
/* 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) {
printf("Can't get key name for cipher '%s' in image '%s'\n",
node_name, image_name);
@@ -886,7 +886,7 @@ static int fit_config_process_sig(const char *keydir, void *keydest,
free(region_prop);
/* 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 */
if (keydest) {
@@ -1025,19 +1025,22 @@ int fit_add_verification_data(const char *keydir, void *keydest, void *fit,
}
#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 ret;
- cfg_noffset = fit_conf_get_node(fit, NULL);
+ cfg_noffset = fit_conf_get_node(fit, fit_uname_config);
if (!cfg_noffset)
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);
if (ret)
return ret;
+ printf("Verified OK, loading images\n");
ret = bootm_host_load_images(fit, cfg_noffset);
return ret;