fs/smb/client/misc.c | 99 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 31 deletions(-)
parse_dfs_referrals() validates that the response contains the fixed
referral entry array, but it still trusts offsets stored inside each
referral entry.
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().
The response also contains a PathConsumed value that is later used for DFS
path parsing. If it is larger than the search name, the parser can
advance beyond the end of the path.
Validate the per-referral offsets before using them, and reject
PathConsumed values that exceed the search name length.
Fixes: 6447b0e35556 ("cifs: parse_dfs_referrals: prevent oob on malformed input")
Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
---
Changes in v2:
- Validate non-Unicode PathConsumed against strlen(searchName), as pointed
out by Sashiko and Steve.
fs/smb/client/misc.c | 99 ++++++++++++++++++++++++++++++++++++----------------
1 file changed, 68 insertions(+), 31 deletions(-)
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index 0c54b9b79a2c..9d84cc718557 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,117 @@ 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 {
+ if (path_consumed > search_name_len) {
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+
+ 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.