fs/smb/client/misc.c | 94 +++++++++++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 31 deletions(-)
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
© 2016 - 2026 Red Hat, Inc.