CVE-2020-28374: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: In drivers/target/target_core_xcopy.c in the Linux kernel before 5.10.7, insufficient identifier checking in the LIO SCSI target code can be used by remote attackers to read or write files via directory traversal in an XCOPY request, aka CID-2896c93811e3. For example, an attack can occur over a network if the attacker has access to one iSCSI LUN. The attacker gains control over file access because I/O operations are proxied via an attacker-selected backstore.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 8.1
Published: 2021-01-13T04:15Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: drivers/target/target_core_xcopy.c, drivers/target/target_core_xcopy.h
Commit date: 2021-01-11 17:06:48 -0500
Commit message: scsi: target: Fix XCOPY NAA identifier lookup

When attempting to match EXTENDED COPY CSCD descriptors with corresponding
se_devices, target_xcopy_locate_se_dev_e4() currently iterates over LIO's
global devices list which includes all configured backstores.

This change ensures that only initiator-accessible backstores are
considered during CSCD descriptor lookup, according to the session's
se_node_acl LUN list.

To avoid LUN removal race conditions, device pinning is changed from being
configfs based to instead using the se_node_acl lun_ref.

Reference: CVE-2020-28374
Fixes: cbf031f425fd ("target: Add support for EXTENDED_COPY copy offload emulation")
Reviewed-by: Lee Duncan <lduncan@suse.com>
Signed-off-by: David Disseldorp <ddiss@suse.de>
Signed-off-by: Mike Christie <michael.christie@oracle.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>

Diff: diff --git a/drivers/target/target_core_xcopy.c b/drivers/target/target_core_xcopy.c
index 44e15d7fb2f0..66d6f1d06f21 100644
--- a/drivers/target/target_core_xcopy.c
+++ b/drivers/target/target_core_xcopy.c
@@ -44,64 +44,87 @@ static int target_xcopy_gen_naa_ieee(struct se_device *dev, unsigned char *buf)
 
 	spc_parse_naa_6h_vendor_specific(dev, &buf[off]);
 	return 0;
 }
 
-struct xcopy_dev_search_info {
-	const unsigned char *dev_wwn;
-	struct se_device *found_dev;
-};
-
+/**
+ * target_xcopy_locate_se_dev_e4_iter - compare XCOPY NAA device identifiers
+ *
+ * @se_dev: device being considered for match
+ * @dev_wwn: XCOPY requested NAA dev_wwn
+ * @return: 1 on match, 0 on no-match
+ */
 static int target_xcopy_locate_se_dev_e4_iter(struct se_device *se_dev,
-					      void *data)
+					      const unsigned char *dev_wwn)
 {
-	struct xcopy_dev_search_info *info = data;
 	unsigned char tmp_dev_wwn[XCOPY_NAA_IEEE_REGEX_LEN];
 	int rc;
 
-	if (!se_dev->dev_attrib.emulate_3pc)
+	if (!se_dev->dev_attrib.emulate_3pc) {
+		pr_debug("XCOPY: emulate_3pc disabled on se_dev %p\n", se_dev);
 		return 0;
+	}
 
 	memset(&tmp_dev_wwn[0], 0, XCOPY_NAA_IEEE_REGEX_LEN);
 	target_xcopy_gen_naa_ieee(se_dev, &tmp_dev_wwn[0]);
 
-	rc = memcmp(&tmp_dev_wwn[0], info->dev_wwn, XCOPY_NAA_IEEE_REGEX_LEN);
-	if (rc != 0)
-		return 0;
-
-	info->found_dev = se_dev;
-	pr_debug("XCOPY 0xe4: located se_dev: %p\n", se_dev);
-
-	rc = target_depend_item(&se_dev->dev_group.cg_item);
+	rc = memcmp(&tmp_dev_wwn[0], dev_wwn, XCOPY_NAA_IEEE_REGEX_LEN);
 	if (rc != 0) {
-		pr_err("configfs_depend_item attempt failed: %d for se_dev: %p\n",
-		       rc, se_dev);
-		return rc;
+		pr_debug("XCOPY: skip non-matching: %*ph\n",
+			 XCOPY_NAA_IEEE_REGEX_LEN, tmp_dev_wwn);
+		return 0;
 	}
+	pr_debug("XCOPY 0xe4: located se_dev: %p\n", se_dev);
 
-	pr_debug("Called configfs_depend_item for se_dev: %p se_dev->se_dev_group: %p\n",
-		 se_dev, &se_dev->dev_group);
 	return 1;
 }
 
-static int target_xcopy_locate_se_dev_e4(const unsigned char *dev_wwn,
-					struct se_device **found_dev)
+static int target_xcopy_locate_se_dev_e4(struct se_session *sess,
+					const unsigned char *dev_wwn,
+					struct se_device **_found_dev,
+					struct percpu_ref **_found_lun_ref)
 {
-	struct xcopy_dev_search_info info;
-	int ret;
-
-	memset(&info, 0, sizeof(info));
-	info.dev_wwn = dev_wwn;
-
-	ret = target_for_each_device(target_xcopy_locate_se_dev_e4_iter, &info);
-	if (ret == 1) {
-		*found_dev = info.found_dev;
-		return 0;
-	} else {
-		pr_debug_ratelimited("Unable to locate 0xe4 descriptor for EXTENDED_COPY\n");
-		return -EINVAL;
+	struct se_dev_entry *deve;
+	struct se_node_acl *nacl;
+	struct se_lun *this_lun = NULL;
+	struct se_device *found_dev = NULL;
+
+	/* cmd with NULL sess indicates no associated $FABRIC_MOD */
+	if (!sess)
+		goto err_out;
+
+	pr_debug("XCOPY 0xe4: searching for: %*ph\n",
+		 XCOPY_NAA_IEEE_REGEX_LEN, dev_wwn);
+
+	nacl = sess->se_node_acl;
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(deve, &nacl->lun_entry_hlist, link) {
+		struct se_device *this_dev;
+		int rc;
+
+		this_lun = rcu_dereference(deve->se_lun);
+		this_dev = rcu_dereference_raw(this_lun->lun_se_dev);
+
+		rc = target_xcopy_locate_se_dev_e4_iter(this_dev, dev_wwn);
+		if (rc) {
+			if (percpu_ref_tryget_live(&this_lun->lun_ref))
+				found_dev = this_dev;
+			break;
+		}
 	}
+	rcu_read_unlock();
+	if (found_dev == NULL)
+		goto err_out;
+
+	pr_debug("lun_ref held for se_dev: %p se_dev->se_dev_group: %p\n",
+		 found_dev, &found_dev->dev_group);
+	*_found_dev = found_dev;
+	*_found_lun_ref = &this_lun->lun_ref;
+	return 0;
+err_out:
+	pr_debug_ratelimited("Unable to locate 0xe4 descriptor for EXTENDED_COPY\n");
+	return -EINVAL;
 }
 
 static int target_xcopy_parse_tiddesc_e4(struct se_cmd *se_cmd, struct xcopy_op *xop,
 				unsigned char *p, unsigned short cscd_index)
 {
@@ -244,16 +267,20 @@ static int target_xcopy_parse_target_descriptors(struct se_cmd *se_cmd,
 		}
 	}
 
 	switch (xop->op_origin) {
 	case XCOL_SOURCE_RECV_OP:
-		rc = target_xcopy_locate_se_dev_e4(xop->dst_tid_wwn,
-						&xop->dst_dev);
+		rc = target_xcopy_locate_se_dev_e4(se_cmd->se_sess,
+						xop->dst_tid_wwn,
+						&xop->dst_dev,
+						&xop->remote_lun_ref);
 		break;
 	case XCOL_DEST_RECV_OP:
-		rc = target_xcopy_locate_se_dev_e4(xop->src_tid_wwn,
-						&xop->src_dev);
+		rc = target_xcopy_locate_se_dev_e4(se_cmd->se_sess,
+						xop->src_tid_wwn,
+						&xop->src_dev,
+						&xop->remote_lun_ref);
 		break;
 	default:
 		pr_err("XCOPY CSCD descriptor IDs not found in CSCD list - "
 			"stdi: %hu dtdi: %hu\n", xop->stdi, xop->dtdi);
 		rc = -EINVAL;
@@ -389,22 +416,16 @@ static int xcopy_pt_get_cmd_state(struct se_cmd *se_cmd)
         return 0;
 }
 
 static void xcopy_pt_undepend_remotedev(struct xcopy_op *xop)
 {
-	struct se_device *remote_dev;
-
 	if (xop->op_origin == XCOL_SOURCE_RECV_OP)
-		remote_dev = xop->dst_dev;
+		pr_debug("putting dst lun_ref for %p\n", xop->dst_dev);
 	else
-		remote_dev = xop->src_dev;
-
-	pr_debug("Calling configfs_undepend_item for"
-		  " remote_dev: %p remote_dev->dev_group: %p\n",
-		  remote_dev, &remote_dev->dev_group.cg_item);
+		pr_debug("putting src lun_ref for %p\n", xop->src_dev);
 
-	target_undepend_item(&remote_dev->dev_group.cg_item);
+	percpu_ref_put(xop->remote_lun_ref);
 }
 
 static void xcopy_pt_release_cmd(struct se_cmd *se_cmd)
 {
 	struct xcopy_pt_cmd *xpt_cmd = container_of(se_cmd,
diff --git a/drivers/target/target_core_xcopy.h b/drivers/target/target_core_xcopy.h
index c56a1bde9417..e5f20005179a 100644
--- a/drivers/target/target_core_xcopy.h
+++ b/drivers/target/target_core_xcopy.h
@@ -25,10 +25,11 @@ struct xcopy_op {
 	struct se_device *src_dev;
 	unsigned char src_tid_wwn[XCOPY_NAA_IEEE_REGEX_LEN];
 	struct se_device *dst_dev;
 	unsigned char dst_tid_wwn[XCOPY_NAA_IEEE_REGEX_LEN];
 	unsigned char local_dev_wwn[XCOPY_NAA_IEEE_REGEX_LEN];
+	struct percpu_ref *remote_lun_ref;
 
 	sector_t src_lba;
 	sector_t dst_lba;
 	unsigned short stdi;
 	unsigned short dtdi;

Vulnerable code: struct xcopy_dev_search_info {
	const unsigned char *dev_wwn;
	struct se_device *found_dev;
};

					      void *data)
	struct xcopy_dev_search_info *info = data;
	if (!se_dev->dev_attrib.emulate_3pc)
	rc = memcmp(&tmp_dev_wwn[0], info->dev_wwn, XCOPY_NAA_IEEE_REGEX_LEN);
	if (rc != 0)
		return 0;

	info->found_dev = se_dev;
	pr_debug("XCOPY 0xe4: located se_dev: %p\n", se_dev);

	rc = target_depend_item(&se_dev->dev_group.cg_item);
		pr_err("configfs_depend_item attempt failed: %d for se_dev: %p\n",
		       rc, se_dev);
		return rc;
	pr_debug("Called configfs_depend_item for se_dev: %p se_dev->se_dev_group: %p\n",
		 se_dev, &se_dev->dev_group);
static int target_xcopy_locate_se_dev_e4(const unsigned char *dev_wwn,
					struct se_device **found_dev)
	struct xcopy_dev_search_info info;
	int ret;

	memset(&info, 0, sizeof(info));
	info.dev_wwn = dev_wwn;

	ret = target_for_each_device(target_xcopy_locate_se_dev_e4_iter, &info);
	if (ret == 1) {
		*found_dev = info.found_dev;
		return 0;
	} else {
		pr_debug_ratelimited("Unable to locate 0xe4 descriptor for EXTENDED_COPY\n");
		return -EINVAL;
		rc = target_xcopy_locate_se_dev_e4(xop->dst_tid_wwn,
						&xop->dst_dev);
		rc = target_xcopy_locate_se_dev_e4(xop->src_tid_wwn,
						&xop->src_dev);
	struct se_device *remote_dev;

		remote_dev = xop->dst_dev;
		remote_dev = xop->src_dev;

	pr_debug("Calling configfs_undepend_item for"
		  " remote_dev: %p remote_dev->dev_group: %p\n",
		  remote_dev, &remote_dev->dev_group.cg_item);
	target_undepend_item(&remote_dev->dev_group.cg_item);
Fixed code: /**
 * target_xcopy_locate_se_dev_e4_iter - compare XCOPY NAA device identifiers
 *
 * @se_dev: device being considered for match
 * @dev_wwn: XCOPY requested NAA dev_wwn
 * @return: 1 on match, 0 on no-match
 */
					      const unsigned char *dev_wwn)
	if (!se_dev->dev_attrib.emulate_3pc) {
		pr_debug("XCOPY: emulate_3pc disabled on se_dev %p\n", se_dev);
	}
	rc = memcmp(&tmp_dev_wwn[0], dev_wwn, XCOPY_NAA_IEEE_REGEX_LEN);
		pr_debug("XCOPY: skip non-matching: %*ph\n",
			 XCOPY_NAA_IEEE_REGEX_LEN, tmp_dev_wwn);
		return 0;
	pr_debug("XCOPY 0xe4: located se_dev: %p\n", se_dev);
static int target_xcopy_locate_se_dev_e4(struct se_session *sess,
					const unsigned char *dev_wwn,
					struct se_device **_found_dev,
					struct percpu_ref **_found_lun_ref)
	struct se_dev_entry *deve;
	struct se_node_acl *nacl;
	struct se_lun *this_lun = NULL;
	struct se_device *found_dev = NULL;

	/* cmd with NULL sess indicates no associated $FABRIC_MOD */
	if (!sess)
		goto err_out;

	pr_debug("XCOPY 0xe4: searching for: %*ph\n",
		 XCOPY_NAA_IEEE_REGEX_LEN, dev_wwn);

	nacl = sess->se_node_acl;
	rcu_read_lock();
	hlist_for_each_entry_rcu(deve, &nacl->lun_entry_hlist, link) {
		struct se_device *this_dev;
		int rc;

		this_lun = rcu_dereference(deve->se_lun);
		this_dev = rcu_dereference_raw(this_lun->lun_se_dev);

		rc = target_xcopy_locate_se_dev_e4_iter(this_dev, dev_wwn);
		if (rc) {
			if (percpu_ref_tryget_live(&this_lun->lun_ref))
				found_dev = this_dev;
			break;
		}
	rcu_read_unlock();
	if (found_dev == NULL)
		goto err_out;

	pr_debug("lun_ref held for se_dev: %p se_dev->se_dev_group: %p\n",
		 found_dev, &found_dev->dev_group);
	*_found_dev = found_dev;
	*_found_lun_ref = &this_lun->lun_ref;
	return 0;
err_out:
	pr_debug_ratelimited("Unable to locate 0xe4 descriptor for EXTENDED_COPY\n");
	return -EINVAL;
		rc = target_xcopy_locate_se_dev_e4(se_cmd->se_sess,
						xop->dst_tid_wwn,
						&xop->dst_dev,
						&xop->remote_lun_ref);
		rc = target_xcopy_locate_se_dev_e4(se_cmd->se_sess,
						xop->src_tid_wwn,
						&xop->src_dev,
						&xop->remote_lun_ref);
		pr_debug("putting dst lun_ref for %p\n", xop->dst_dev);
		pr_debug("putting src lun_ref for %p\n", xop->src_dev);
	percpu_ref_put(xop->remote_lun_ref);
	struct percpu_ref *remote_lun_ref;