[PATCH] smb: client: validate DFS referral offsets

Yichong Chen posted 1 patch 1 week, 4 days ago
There is a newer version of this series
fs/smb/client/misc.c | 94 +++++++++++++++++++++++++++++---------------
1 file changed, 63 insertions(+), 31 deletions(-)
[PATCH] smb: client: validate DFS referral offsets
Posted by Yichong Chen 1 week, 4 days ago
parse_dfs_referrals() checks that the response contains the fixed
referral entries, but it still trusts offsets stored inside each referral.
A malformed server response can set DfsPathOffset or NetworkAddressOffset
past the end of the received buffer.  The resulting pointer is then used
to derive a remaining length and passed to cifs_strndup_from_utf16().

Validate the per-referral offsets before using them.  Also validate the
Unicode PathConsumed value against the converted search name length before
passing it to cifs_utf16_bytes(), and only publish the parsed referral
array to the caller after the number of referrals has been validated.

Fixes: 6447b0e35556 ("cifs: parse_dfs_referrals: prevent oob on malformed input")
Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
---
 fs/smb/client/misc.c | 94 +++++++++++++++++++++++++++++---------------
 1 file changed, 63 insertions(+), 31 deletions(-)

diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index 0c54b9b..73676d2 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -678,6 +678,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
 	int i, rc = 0;
 	char *data_end;
 	struct dfs_referral_level_3 *ref;
+	unsigned int num_refs, path_consumed;
+	size_t search_name_len;
+
+	*target_nodes = NULL;
+	*num_of_nodes = 0;
 
 	if (rsp_size < sizeof(*rsp)) {
 		cifs_dbg(VFS | ONCE,
@@ -687,85 +692,112 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
 		goto parse_DFS_referrals_exit;
 	}
 
-	*num_of_nodes = le16_to_cpu(rsp->NumberOfReferrals);
+	num_refs = le16_to_cpu(rsp->NumberOfReferrals);
 
-	if (*num_of_nodes < 1) {
+	if (num_refs < 1) {
 		cifs_dbg(VFS | ONCE, "%s: [path=%s] num_referrals must be at least > 0, but we got %d\n",
-			 __func__, searchName, *num_of_nodes);
+			 __func__, searchName, num_refs);
 		rc = -ENOENT;
 		goto parse_DFS_referrals_exit;
 	}
 
-	if (sizeof(*rsp) + *num_of_nodes * sizeof(REFERRAL3) > rsp_size) {
+	if (sizeof(*rsp) + num_refs * sizeof(REFERRAL3) > rsp_size) {
 		cifs_dbg(VFS | ONCE,
 			 "%s: malformed buffer (size is %u, must be at least %zu)\n",
 			 __func__, rsp_size,
-			 sizeof(*rsp) + *num_of_nodes * sizeof(REFERRAL3));
+			 sizeof(*rsp) + num_refs * sizeof(REFERRAL3));
 		rc = -EINVAL;
 		goto parse_DFS_referrals_exit;
 	}
 
 	ref = (struct dfs_referral_level_3 *) &(rsp->referrals);
-	if (ref->VersionNumber != cpu_to_le16(3)) {
-		cifs_dbg(VFS, "Referrals of V%d version are not supported, should be V3\n",
-			 le16_to_cpu(ref->VersionNumber));
-		rc = -EINVAL;
-		goto parse_DFS_referrals_exit;
-	}
 
 	/* get the upper boundary of the resp buffer */
 	data_end = (char *)rsp + rsp_size;
 
 	cifs_dbg(FYI, "num_referrals: %d dfs flags: 0x%x ...\n",
-		 *num_of_nodes, le32_to_cpu(rsp->DFSFlags));
+		 num_refs, le32_to_cpu(rsp->DFSFlags));
 
-	*target_nodes = kzalloc_objs(struct dfs_info3_param, *num_of_nodes);
-	if (*target_nodes == NULL) {
+	*target_nodes = kzalloc_objs(struct dfs_info3_param, num_refs);
+	if (!*target_nodes) {
 		rc = -ENOMEM;
 		goto parse_DFS_referrals_exit;
 	}
+	*num_of_nodes = num_refs;
+	search_name_len = strlen(searchName);
 
 	/* collect necessary data from referrals */
-	for (i = 0; i < *num_of_nodes; i++) {
+	for (i = 0; i < num_refs; i++) {
 		char *temp;
 		int max_len;
-		struct dfs_info3_param *node = (*target_nodes)+i;
+		unsigned int offset, remaining;
+		struct dfs_info3_param *node = (*target_nodes) + i;
+
+		if (ref->VersionNumber != cpu_to_le16(3)) {
+			cifs_dbg(VFS,
+				 "Referrals of V%d version are not supported, should be V3\n",
+				 le16_to_cpu(ref->VersionNumber));
+			rc = -EINVAL;
+			goto parse_DFS_referrals_exit;
+		}
 
 		node->flags = le32_to_cpu(rsp->DFSFlags);
+		path_consumed = le16_to_cpu(rsp->PathConsumed);
 		if (is_unicode) {
-			__le16 *tmp = kmalloc(strlen(searchName)*2 + 2,
-						GFP_KERNEL);
-			if (tmp == NULL) {
+			size_t search_name_utf16_len = search_name_len * 2 + 2;
+			__le16 *tmp;
+
+			if (path_consumed > search_name_utf16_len) {
+				rc = -EINVAL;
+				goto parse_DFS_referrals_exit;
+			}
+
+			tmp = kmalloc(search_name_utf16_len, GFP_KERNEL);
+			if (!tmp) {
 				rc = -ENOMEM;
 				goto parse_DFS_referrals_exit;
 			}
-			cifsConvertToUTF16((__le16 *) tmp, searchName,
+			cifsConvertToUTF16((__le16 *)tmp, searchName,
 					   PATH_MAX, nls_codepage, remap);
-			node->path_consumed = cifs_utf16_bytes(tmp,
-					le16_to_cpu(rsp->PathConsumed),
-					nls_codepage);
+			node->path_consumed = cifs_utf16_bytes(tmp, path_consumed,
+							       nls_codepage);
 			kfree(tmp);
-		} else
-			node->path_consumed = le16_to_cpu(rsp->PathConsumed);
+		} else {
+			node->path_consumed = path_consumed;
+		}
 
 		node->server_type = le16_to_cpu(ref->ServerType);
 		node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags);
 
+		remaining = data_end - (char *)ref;
+
 		/* copy DfsPath */
-		temp = (char *)ref + le16_to_cpu(ref->DfsPathOffset);
-		max_len = data_end - temp;
+		offset = le16_to_cpu(ref->DfsPathOffset);
+		if (offset > remaining) {
+			rc = -EINVAL;
+			goto parse_DFS_referrals_exit;
+		}
+		temp = (char *)ref + offset;
+		max_len = remaining - offset;
 		node->path_name = cifs_strndup_from_utf16(temp, max_len,
-						is_unicode, nls_codepage);
+							  is_unicode,
+							  nls_codepage);
 		if (!node->path_name) {
 			rc = -ENOMEM;
 			goto parse_DFS_referrals_exit;
 		}
 
 		/* copy link target UNC */
-		temp = (char *)ref + le16_to_cpu(ref->NetworkAddressOffset);
-		max_len = data_end - temp;
+		offset = le16_to_cpu(ref->NetworkAddressOffset);
+		if (offset > remaining) {
+			rc = -EINVAL;
+			goto parse_DFS_referrals_exit;
+		}
+		temp = (char *)ref + offset;
+		max_len = remaining - offset;
 		node->node_name = cifs_strndup_from_utf16(temp, max_len,
-						is_unicode, nls_codepage);
+							  is_unicode,
+							  nls_codepage);
 		if (!node->node_name) {
 			rc = -ENOMEM;
 			goto parse_DFS_referrals_exit;
-- 
2.51.0