CVE-2021-27138 (published 2021-02-17T23:15Z)

The boot loader in Das U-Boot before 2021.04-rc2 mishandles use of unit addresses in a FIT.

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 7.8, HIGH (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/u-boot/u-boot
commit_details: repo_url: https://github.com/u-boot/u-boot
author: Simon Glass
author_date: 2021-02-15 17:08:12-07:00
msg: image: Check for unit addresses in FITs

Using unit addresses in a FIT is a security risk. Add a check for this
and disallow it.

CVE-2021-27138

Signed-off-by: Simon Glass <sjg@chromium.org>
Reported-by: Bruce Monroe <bruce.monroe@intel.com>
Reported-by: Arie Haenel <arie.haenel@intel.com>
Reported-by: Julien Lenoir <julien.lenoir@intel.com>
repository: repo_url: https://github.com/u-boot/u-boot
repo_name: u-boot/u-boot
description: "Das U-Boot" Source Tree
repo_language: C
owner: u-boot
file_changes: filename: image-fit.c
change_type: ModificationType.MODIFY
diff: @@ -1568,6 +1568,34 @@ int fit_image_check_comp(const void *fit, int noffset, uint8_t comp)
 	return (comp == image_comp);
 }
 
+/**
+ * fdt_check_no_at() - Check for nodes whose names contain '@'
+ *
+ * This checks the parent node and all subnodes recursively
+ *
+ * @fit: FIT to check
+ * @parent: Parent node to check
+ * @return 0 if OK, -EADDRNOTAVAIL is a node has a name containing '@'
+ */
+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;
@@ -1589,10 +1617,27 @@ int fit_check_format(const void *fit, ulong size)
 		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 -EINVAL;
+			return ret;
 		}
 	}
 
@@ -1955,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, IMAGE_SIZE_INVAL)) {
-		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) {

programming_language: C
filename: test_vboot.py
change_type: ModificationType.MODIFY
diff: @@ -232,8 +232,8 @@ def test_vboot(u_boot_console, sha_algo, padding, sign_options, required,
         util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
 
         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.
+            # 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:
@@ -263,10 +263,11 @@ def test_vboot(u_boot_console, sha_algo, padding, sign_options, required,
             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, 'Node name contains @')
-            run_bootm(sha_algo, 'evil kernel@', 'Bad Data Hash', False, efit)
+                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))

programming_language: Python
repo_url: https://github.com/u-boot/u-boot
commit_details: repo_url: https://github.com/u-boot/u-boot
author: Simon Glass
author_date: 2021-02-15 17:08:06-07:00
msg: fit: Don't allow verification of images with @ nodes

When searching for a node called 'fred', any unit address appended to the
name is ignored by libfdt, meaning that 'fred' can match 'fred@1'. This
means that we cannot be sure that the node originally intended is the one
that is used.

Disallow use of nodes with unit addresses.

Update the forge test also, since it uses @ addresses.

CVE-2021-27138

Signed-off-by: Simon Glass <sjg@chromium.org>
Reported-by: Bruce Monroe <bruce.monroe@intel.com>
Reported-by: Arie Haenel <arie.haenel@intel.com>
Reported-by: Julien Lenoir <julien.lenoir@intel.com>
repository: repo_url: https://github.com/u-boot/u-boot
repo_name: u-boot/u-boot
description: "Das U-Boot" Source Tree
repo_language: C
owner: u-boot
file_changes: filename: image-fit-sig.c
change_type: ModificationType.MODIFY
diff: @@ -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) {

programming_language: C
filename: image-fit.c
change_type: ModificationType.MODIFY
diff: @@ -1369,21 +1369,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;
 }
 
 /**

programming_language: C
filename: test_fit.py
change_type: ModificationType.MODIFY
diff: @@ -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>;' %

programming_language: Python
filename: vboot_forge.py
change_type: ModificationType.MODIFY
diff: @@ -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)
 

programming_language: Python
repo_url: https://github.com/u-boot/u-boot
commit_details: repo_url: https://github.com/u-boot/u-boot
author: Tom Rini
author_date: 2021-02-15 19:19:56-05:00
msg: 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
repository: repo_url: https://github.com/u-boot/u-boot
repo_name: u-boot/u-boot
description: "Das U-Boot" Source Tree
repo_language: C
owner: u-boot