smb client fixes for v7.3-rc4

A batch of bug fixes for the smb client:
 
  - Fix multiple out-of-bounds reads and use-after-frees in the SMB2/3
    receive path that are reachable from a malicious or compromised
    server: a stale next_buffer pointer and an integer overflow in
    compound encrypted frame handling, missing minimum-PDU-size and
    per-sub-PDU length validation before parsing command-specific
    response fields, missing bounds checks in DFS referral, server
    interface list, EA list, POSIX SID, snapshot enumeration and SMB1
    reparse point parsing
 
  - Fix use-after-frees and races in multichannel and connection
    teardown, including an interface freed while still in use when
    adding channels, a server used after its channel reference was
    dropped, a reconnect work item left queued after the server is
    freed and an uninitialized reconnect list node
 
  - Fix a heap overflow in the native symlink parser: an absolute
    target without an NT drive prefix caused out-of-bounds writes and a
    u16 length underflow leading to a 64K memcpy into a small buffer,
    triggerable by a user with write access to a mounted share under
    default settings
 
  - Fix WSL reparse point parsing: use unaligned accessors for the
    packed extended-attribute payload to avoid alignment faults on some
    architectures and stop leaving partially mutated fattr fields on
    parse failure
 
  - Fix lease break ACKs being sent through the wrong session on
    multiuser mounts, which caused read failures (e.g. on NetApp
    ONTAP/Azure Files) when copying files
 
  - Fix an smbd_connection leak when cifs_get_tcp_session() fails after
    an RDMA connection was already established
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQTcqRusfSdYROJQwGkpVtNKoQNdYwUCaq2frwAKCRApVtNKoQNd
 Y1mcAQDcDTkep03jzghyJG6xWJ3S7KNbeYpjkOPnPyR+Et7HmAD/eXLFvgkJ3wC7
 tBUDjDTLeyP6/DOBmDb/fIKEw2vfBQs=
 =+Vsw
 -----END PGP SIGNATURE-----

Merge tag 'cifs-fixes-7.3-rc4' of https://git.manguebit.org/linux

Pull smb client fixes from Paulo Alcantara:
 "A batch of bug fixes for the smb client:

   - Fix multiple out-of-bounds reads and use-after-frees in the SMB2/3
     receive path that are reachable from a malicious or compromised
     server: a stale next_buffer pointer and an integer overflow in
     compound encrypted frame handling, missing minimum-PDU-size and
     per-sub-PDU length validation before parsing command-specific
     response fields, missing bounds checks in DFS referral, server
     interface list, EA list, POSIX SID, snapshot enumeration and SMB1
     reparse point parsing

   - Fix use-after-frees and races in multichannel and connection
     teardown, including an interface freed while still in use when
     adding channels, a server used after its channel reference was
     dropped, a reconnect work item left queued after the server is
     freed and an uninitialized reconnect list node

   - Fix a heap overflow in the native symlink parser: an absolute
     target without an NT drive prefix caused out-of-bounds writes and a
     u16 length underflow leading to a 64K memcpy into a small buffer,
     triggerable by a user with write access to a mounted share under
     default settings

   - Fix WSL reparse point parsing: use unaligned accessors for the
     packed extended-attribute payload to avoid alignment faults on some
     architectures and stop leaving partially mutated fattr fields on
     parse failure

   - Fix lease break ACKs being sent through the wrong session on
     multiuser mounts, which caused read failures (e.g. on NetApp
     ONTAP/Azure Files) when copying files

   - Fix an smbd_connection leak when cifs_get_tcp_session() fails after
     an RDMA connection was already established"

* tag 'cifs-fixes-7.3-rc4' of https://git.manguebit.org/linux:
  cifs: Fix server use-after-free in cifs_chan_skip_or_disable()
  smb: client: fix reparse buffer bounds in cifs_query_reparse_point()
  smb: client: fix potential OOB read in smb3_enum_snapshots()
  smb: client: fix missing iov bounds check in parse_posix_sids()
  smb: client: fix OOB struct field reads in move_smb2_ea_to_cifs()
  smb: client: reject short Next offsets in parse_server_interfaces()
  smb: client: fix missing lower-bound check on DFS referral string offsets
  smb: client: fix server->total_read for compound encrypted PDUs
  smb: client: validate minimum PDU size before smb2_get_data_area_len()
  smb: client: fix next_buffer UAF and NextCommand bounds in compound PDUs
  smb: client: fix use-after-free of iface in cifs_try_adding_channels()
  smb: client: fix fattr leaking on wsl_to_fattr() failure
  smb: client: fix unaligned access in WSL reparse point parser
  smb: client: fix smbd_connection leak on cifs_get_tcp_session() error
  smb: client: fix rlist race and missing initialization
  smb: client: cancel reconnect work in clean_demultiplex_info()
  smb/client: send lease break ACKs thru correct session for multiuser mounts
  smb: client: validate absolute native symlink targets before NT fixups
This commit is contained in:
Linus Torvalds 2026-09-18 13:44:59 -07:00
commit 17e7b8eacf
12 changed files with 223 additions and 118 deletions

View File

@ -3080,7 +3080,7 @@ int cifs_query_reparse_point(const unsigned int xid,
end = 2 + get_bcc(&io_rsp->hdr) + (__u8 *)&io_rsp->ByteCount;
start = (__u8 *)&io_rsp->hdr.Protocol + data_offset;
if (start >= end) {
if (start >= end || (size_t)(end - start) < sizeof(*buf)) {
rc = smb_EIO2(smb_eio_trace_qreparse_data_area,
(unsigned long)start - (unsigned long)io_rsp,
(unsigned long)end - (unsigned long)io_rsp);

View File

@ -174,6 +174,8 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server,
nserver = ses->chans[i].server;
if (!nserver)
continue;
if (!list_empty(&nserver->rlist))
continue;
nserver->srv_count++;
list_add(&nserver->rlist, &reco);
}
@ -182,11 +184,15 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server,
}
}
spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry_safe(server, nserver, &reco, rlist) {
list_del_init(&server->rlist);
set_need_reco(server);
spin_unlock(&cifs_tcp_ses_lock);
cifs_put_tcp_session(server, 0);
spin_lock(&cifs_tcp_ses_lock);
}
spin_unlock(&cifs_tcp_ses_lock);
}
/*
@ -1067,6 +1073,7 @@ clean_demultiplex_info(struct TCP_Server_Info *server)
spin_unlock(&server->srv_lock);
cancel_delayed_work_sync(&server->echo);
cancel_delayed_work_sync(&server->reconnect);
spin_lock(&server->srv_lock);
server->tcpStatus = CifsExiting;
@ -1823,6 +1830,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
spin_lock_init(&tcp_ses->mid_counter_lock);
INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
INIT_LIST_HEAD(&tcp_ses->rlist);
INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
mutex_init(&tcp_ses->reconnect_mutex);
@ -1926,6 +1934,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
kfree(tcp_ses->leaf_fullpath);
if (tcp_ses->ssocket)
sock_release(tcp_ses->ssocket);
smbd_destroy(tcp_ses);
kfree(tcp_ses);
}
return ERR_PTR(rc);

View File

@ -3354,8 +3354,8 @@ void cifs_oplock_break(struct work_struct *work)
wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
TASK_UNINTERRUPTIBLE);
tlink = cifs_sb_tlink(cifs_sb);
if (IS_ERR(tlink)) {
tlink = cifs_get_tlink(cfile->tlink);
if (IS_ERR_OR_NULL(tlink)) {
/* drop the reference taken when the break was queued */
_cifsFileInfo_put(cfile, false /* do not wait for ourself */, false);
goto out;

View File

@ -788,7 +788,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags);
/* copy DfsPath */
if (le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) {
if (le16_to_cpu(ref->DfsPathOffset) < sizeof(*ref) ||
le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) {
cifs_dbg(VFS, "%s: DfsPathOffset %u out of range [%zu, %td]\n",
__func__, le16_to_cpu(ref->DfsPathOffset),
sizeof(*ref), data_end - (char *)ref);
rc = -EINVAL;
goto parse_DFS_referrals_exit;
}
@ -802,7 +806,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
}
/* copy link target UNC */
if (le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) {
if (le16_to_cpu(ref->NetworkAddressOffset) < sizeof(*ref) ||
le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) {
cifs_dbg(VFS, "%s: NetworkAddressOffset %u out of range [%zu, %td]\n",
__func__, le16_to_cpu(ref->NetworkAddressOffset),
sizeof(*ref), data_end - (char *)ref);
rc = -EINVAL;
goto parse_DFS_referrals_exit;
}

View File

@ -3,6 +3,7 @@
* Copyright (c) 2024 Paulo Alcantara <pc@manguebit.com>
*/
#include <linux/ctype.h>
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/slab.h>
@ -159,15 +160,24 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode,
convert_delimiter(sym, sep);
/*
* For absolute NT symlinks it is required to pass also leading
* backslash and to not mangle NT object prefix "\\??\\" and not to
* mangle colon in drive letter. But cifs_convert_path_to_utf16()
* removes leading backslash and replaces '?' and ':'. So temporary
* mask these characters in NT object prefix by '_' and then change
* them back.
* Absolute NT symlinks must retain the leading backslash, "\\??\\"
* prefix and drive-letter colon. cifs_convert_path_to_utf16() strips
* the leading backslash and maps '?' and ':', so temporarily mask
* these characters with '_' and restore them after conversion.
*
* When symlinkroot is unset, sym comes directly from the caller.
* Validate the complete "\\??\\X:" prefix before using fixed offsets
* or subtracting the NT prefix length below. Require an ASCII drive
* letter so the prefix occupies six characters in UTF-16 too.
*/
if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/')
if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') {
if (!strstarts(sym, "\\??\\") || !isascii(sym[4]) ||
!isalpha(sym[4]) || sym[5] != ':') {
rc = -EINVAL;
goto out;
}
sym[0] = sym[1] = sym[2] = sym[5] = '_';
}
/*
* On a POSIX paths mount the symlink target is stored verbatim, so
@ -1139,29 +1149,30 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data,
u32 tag, struct cifs_fattr *fattr)
{
unsigned int sbflags = cifs_sb_flags(cifs_sb);
kuid_t uid = cifs_sb->ctx->linux_uid;
kgid_t gid = cifs_sb->ctx->linux_gid;
struct smb2_file_full_ea_info *ea;
bool have_xattr_dev = false;
dev_t rdev = 0;
umode_t mode;
u32 next = 0;
fattr->cf_uid = cifs_sb->ctx->linux_uid;
fattr->cf_gid = cifs_sb->ctx->linux_gid;
fattr->cf_mode &= ~S_IFMT;
mode = fattr->cf_mode & ~S_IFMT;
switch (tag) {
case IO_REPARSE_TAG_LX_SYMLINK:
fattr->cf_mode |= S_IFLNK;
mode |= S_IFLNK;
break;
case IO_REPARSE_TAG_LX_FIFO:
fattr->cf_mode |= S_IFIFO;
mode |= S_IFIFO;
break;
case IO_REPARSE_TAG_AF_UNIX:
fattr->cf_mode |= S_IFSOCK;
mode |= S_IFSOCK;
break;
case IO_REPARSE_TAG_LX_CHR:
fattr->cf_mode |= S_IFCHR;
mode |= S_IFCHR;
break;
case IO_REPARSE_TAG_LX_BLK:
fattr->cf_mode |= S_IFBLK;
mode |= S_IFBLK;
break;
}
@ -1185,26 +1196,29 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data,
if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) {
if (!(sbflags & CIFS_MOUNT_OVERR_UID))
fattr->cf_uid = wsl_make_kuid(cifs_sb, v);
uid = wsl_make_kuid(cifs_sb, v);
} else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) {
if (!(sbflags & CIFS_MOUNT_OVERR_GID))
fattr->cf_gid = wsl_make_kgid(cifs_sb, v);
gid = wsl_make_kgid(cifs_sb, v);
} else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) {
/* File type in reparse point tag and in xattr mode must match. */
if (S_DT(fattr->cf_mode) != S_DT(le32_to_cpu(*(__le32 *)v)))
if (S_DT(mode) != S_DT(get_unaligned_le32(v)))
return false;
fattr->cf_mode = (umode_t)le32_to_cpu(*(__le32 *)v);
mode = get_unaligned_le32(v);
} else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) {
fattr->cf_rdev = reparse_mkdev(v);
rdev = reparse_mkdev(v);
have_xattr_dev = true;
}
} while (next);
out:
/* Major and minor numbers for char and block devices are mandatory. */
if (!have_xattr_dev && (tag == IO_REPARSE_TAG_LX_CHR || tag == IO_REPARSE_TAG_LX_BLK))
return false;
fattr->cf_uid = uid;
fattr->cf_gid = gid;
fattr->cf_mode = mode;
fattr->cf_rdev = rdev;
return true;
}

View File

@ -9,6 +9,7 @@
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/uidgid.h>
#include <linux/unaligned.h>
#include "fs_context.h"
#include "cifsglob.h"
#include "../common/smbfsctl.h"
@ -23,7 +24,7 @@
static inline dev_t reparse_mkdev(void *ptr)
{
u64 v = le64_to_cpu(*(__le64 *)ptr);
u64 v = get_unaligned_le64(ptr);
return MKDEV(v & 0xffffffff, v >> 32);
}
@ -31,7 +32,7 @@ static inline dev_t reparse_mkdev(void *ptr)
static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb,
void *ptr)
{
u32 uid = le32_to_cpu(*(__le32 *)ptr);
u32 uid = get_unaligned_le32(ptr);
if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_UID)
return cifs_sb->ctx->linux_uid;
@ -41,7 +42,7 @@ static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb,
static inline kgid_t wsl_make_kgid(struct cifs_sb_info *cifs_sb,
void *ptr)
{
u32 gid = le32_to_cpu(*(__le32 *)ptr);
u32 gid = get_unaligned_le32(ptr);
if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_GID)
return cifs_sb->ctx->linux_gid;

View File

@ -149,9 +149,9 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
int old_chan_count, new_chan_count;
int left;
int rc = 0;
int tries = 0;
int tries = 0, attempts;
size_t iface_weight = 0, iface_min_speed = 0;
struct cifs_server_iface *iface = NULL, *niface = NULL;
struct cifs_server_iface *iface = NULL, *candidate = NULL;
struct cifs_server_iface *last_iface = NULL;
spin_lock(&ses->chan_lock);
@ -197,67 +197,89 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
break;
}
if (!iface)
iface = list_first_entry(&ses->iface_list, struct cifs_server_iface,
iface_head);
last_iface = list_last_entry(&ses->iface_list, struct cifs_server_iface,
iface_head);
iface_min_speed = last_iface->speed;
spin_unlock(&ses->iface_lock);
list_for_each_entry_safe_from(iface, niface, &ses->iface_list,
iface_head) {
/* do not mix rdma and non-rdma interfaces */
if (iface->rdma_capable != ses->server->rdma)
continue;
attempts = 0;
while (left > 0) {
spin_lock(&ses->iface_lock);
/* skip ifaces that are unusable */
if (!iface->is_active ||
(is_ses_using_iface(ses, iface) &&
!iface->rss_capable))
continue;
/*
* iface_lock must be dropped while opening a channel,
* and a concurrent interface refresh may remove and
* free entries during that window, so no list entry
* may be kept across it without a reference. Scan
* the list from the beginning each time and only pass
* a referenced candidate to cifs_ses_add_channel();
* weight_fulfilled tracks the progress so that no
* iface is selected beyond its weight.
*/
candidate = NULL;
list_for_each_entry(iface, &ses->iface_list, iface_head) {
/* do not mix rdma and non-rdma interfaces */
if (iface->rdma_capable != ses->server->rdma)
continue;
/* check if we already allocated enough channels */
iface_weight = iface->speed / iface_min_speed;
/* skip ifaces that are unusable */
if (!iface->is_active ||
(is_ses_using_iface(ses, iface) &&
!iface->rss_capable))
continue;
if (iface->weight_fulfilled >= iface_weight)
continue;
/* check if we already allocated enough channels */
iface_weight = iface->speed / iface_min_speed;
/* take ref before unlock */
kref_get(&iface->refcount);
if (iface->weight_fulfilled >= iface_weight)
continue;
/* take ref before unlock */
kref_get(&iface->refcount);
candidate = iface;
break;
}
if (!candidate) {
/* no usable iface. reset weight_fulfilled and start over */
list_for_each_entry(iface, &ses->iface_list, iface_head)
iface->weight_fulfilled = 0;
spin_unlock(&ses->iface_lock);
break;
}
attempts++;
if (attempts > 3 * ses->chan_max) {
kref_put(&candidate->refcount, release_iface);
spin_unlock(&ses->iface_lock);
break;
}
spin_unlock(&ses->iface_lock);
rc = cifs_ses_add_channel(ses, iface);
rc = cifs_ses_add_channel(ses, candidate);
spin_lock(&ses->iface_lock);
if (rc) {
cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n",
&iface->sockaddr,
&candidate->sockaddr,
rc);
/* failure to add chan should increase weight */
iface->weight_fulfilled++;
kref_put(&iface->refcount, release_iface);
candidate->weight_fulfilled++;
kref_put(&candidate->refcount, release_iface);
spin_unlock(&ses->iface_lock);
continue;
}
iface->num_channels++;
iface->weight_fulfilled++;
candidate->num_channels++;
candidate->weight_fulfilled++;
cifs_info("successfully opened new channel on iface:%pIS\n",
&iface->sockaddr);
&candidate->sockaddr);
spin_unlock(&ses->iface_lock);
left--;
new_chan_count++;
break;
}
/* reached end of list. reset weight_fulfilled and start over */
if (list_entry_is_head(iface, &ses->iface_list, iface_head)) {
list_for_each_entry(iface, &ses->iface_list, iface_head)
iface->weight_fulfilled = 0;
spin_unlock(&ses->iface_lock);
iface = NULL;
continue;
}
spin_unlock(&ses->iface_lock);
left--;
new_chan_count++;
}
return new_chan_count - old_chan_count;

View File

@ -77,6 +77,17 @@ static int parse_posix_sids(struct cifs_open_info_data *data,
sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len;
sidsbuf_end = sidsbuf + out_len - qi_len;
if (sidsbuf_end < sidsbuf) {
cifs_dbg(VFS, "%s: server-supplied out_len %u caused pointer wraparound\n",
__func__, out_len);
return -EINVAL;
}
if (sidsbuf_end > (u8 *)rsp_iov->iov_base + rsp_iov->iov_len) {
cifs_dbg(VFS, "%s: server-supplied out_len %u overruns iov by %td bytes\n",
__func__, out_len,
sidsbuf_end - ((u8 *)rsp_iov->iov_base + rsp_iov->iov_len));
return -EINVAL;
}
owner_len = posix_info_sid_size(sidsbuf, sidsbuf_end);
if (owner_len == -1)

View File

@ -85,6 +85,36 @@ static const __le16 smb2_rsp_struct_sizes[NUMBER_OF_SMB2_COMMANDS] = {
/* SMB2_OPLOCK_BREAK */ cpu_to_le16(24)
};
/*
* Minimum received PDU size for commands whose response carries a
* variable-length data area. A non-zero entry marks the command as
* having one, and gives the length smb2_check_message() requires
* before smb2_get_data_area_len() reads the offset and length fields
* out of the fixed response struct.
*/
static const size_t smb2_min_pdu_len[NUMBER_OF_SMB2_COMMANDS] = {
/* SMB2_NEGOTIATE */ sizeof(struct smb2_negotiate_rsp),
/* SMB2_SESSION_SETUP */ sizeof(struct smb2_sess_setup_rsp),
/* SMB2_LOGOFF */ 0,
/* SMB2_TREE_CONNECT */ 0,
/* SMB2_TREE_DISCONNECT */ 0,
/* SMB2_CREATE */ sizeof(struct smb2_create_rsp),
/* SMB2_CLOSE */ 0,
/* SMB2_FLUSH */ 0,
/* SMB2_READ */ sizeof(struct smb2_read_rsp),
/* SMB2_WRITE */ 0,
/* SMB2_LOCK */ 0,
/* SMB2_IOCTL */ sizeof(struct smb2_ioctl_rsp),
/* SMB2_CANCEL */ 0,
/* SMB2_ECHO */ 0,
/* SMB2_QUERY_DIRECTORY */ sizeof(struct smb2_query_directory_rsp),
/* SMB2_CHANGE_NOTIFY */ sizeof(struct smb2_change_notify_rsp),
/* SMB2_QUERY_INFO */ sizeof(struct smb2_query_info_rsp),
/* SMB2_SET_INFO */ 0,
/* SMB2_OPLOCK_BREAK */ 0,
};
#define smb2_has_data_area(cmd) (smb2_min_pdu_len[cmd] != 0)
#define SMB311_NEGPROT_BASE_SIZE (sizeof(struct smb2_hdr) + sizeof(struct smb2_negotiate_rsp))
static __u32 get_neg_ctxt_len(struct smb2_hdr *hdr, __u32 len,
@ -233,6 +263,16 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
}
}
if ((shdr->Status == STATUS_SUCCESS ||
shdr->Status == STATUS_MORE_PROCESSING_REQUIRED ||
pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE) &&
smb2_has_data_area(command) &&
len < smb2_min_pdu_len[command]) {
cifs_server_dbg(VFS, "SMB2 command %d response too short: %u < %zu\n",
command, len, smb2_min_pdu_len[command]);
return 1;
}
have_data = false;
data_area_overlap = false;
calc_len = __smb2_calc_size(buf, &have_data, &data_area_overlap);
@ -298,33 +338,6 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
return 0;
}
/*
* The size of the variable area depends on the offset and length fields
* located in different fields for various SMB2 responses. SMB2 responses
* with no variable length info, show an offset of zero for the offset field.
*/
static const bool has_smb2_data_area[NUMBER_OF_SMB2_COMMANDS] = {
/* SMB2_NEGOTIATE */ true,
/* SMB2_SESSION_SETUP */ true,
/* SMB2_LOGOFF */ false,
/* SMB2_TREE_CONNECT */ false,
/* SMB2_TREE_DISCONNECT */ false,
/* SMB2_CREATE */ true,
/* SMB2_CLOSE */ false,
/* SMB2_FLUSH */ false,
/* SMB2_READ */ true,
/* SMB2_WRITE */ false,
/* SMB2_LOCK */ false,
/* SMB2_IOCTL */ true,
/* SMB2_CANCEL */ false, /* BB CHECK this not listed in documentation */
/* SMB2_ECHO */ false,
/* SMB2_QUERY_DIRECTORY */ true,
/* SMB2_CHANGE_NOTIFY */ true,
/* SMB2_QUERY_INFO */ true,
/* SMB2_SET_INFO */ false,
/* SMB2_OPLOCK_BREAK */ false
};
/*
* Returns the pointer to the beginning of the data area. Length of the data
* area and the offset to it (from the beginning of the smb are also returned.
@ -451,7 +464,7 @@ __smb2_calc_size(void *buf, bool *have_data, bool *data_area_overlap)
*/
len += le16_to_cpu(pdu->StructureSize2);
if (has_smb2_data_area[le16_to_cpu(shdr->Command)] == false)
if (!smb2_has_data_area(le16_to_cpu(shdr->Command)))
goto calc_size_exit;
smb2_get_data_area_len(&offset, &data_length, shdr);

View File

@ -785,9 +785,9 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
break;
}
/* Validate that Next doesn't point beyond the buffer */
if (next > bytes_left) {
cifs_dbg(VFS, "%s: invalid Next pointer %zu > %zd\n",
__func__, next, bytes_left);
if (next < sizeof(*p) || next > bytes_left) {
cifs_dbg(VFS, "%s: invalid Next pointer %zu out of range [%zu, %zd]\n",
__func__, next, sizeof(*p), bytes_left);
rc = -EINVAL;
goto out;
}
@ -1053,8 +1053,9 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
char *name, *value;
size_t buf_size = dst_size;
size_t name_len, value_len, user_name_len;
u32 next_off;
while (src_size > 0) {
while (src_size >= sizeof(*src)) {
name_len = (size_t)src->ea_name_length;
value_len = (size_t)le16_to_cpu(src->ea_value_length);
@ -1110,14 +1111,22 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
if (!src->next_entry_offset)
break;
if (src_size < le32_to_cpu(src->next_entry_offset)) {
/* stop before overrun buffer */
rc = -ERANGE;
break;
next_off = le32_to_cpu(src->next_entry_offset);
if (next_off < sizeof(*src) || src_size < next_off) {
cifs_dbg(FYI, "EA next_entry_offset %u out of range [%zu, %zu]\n",
next_off, sizeof(*src), src_size);
rc = smb_EIO2(smb_eio_trace_ea_next_offset,
next_off, src_size);
goto out;
}
src_size -= next_off;
src = (void *)((char *)src + next_off);
if (src_size > 0 && src_size < sizeof(*src)) {
cifs_dbg(FYI, "EA next_entry_offset %u left truncated entry (%zu bytes)\n",
next_off, src_size);
rc = smb_EIO2(smb_eio_trace_ea_next_offset, next_off, src_size);
goto out;
}
src_size -= le32_to_cpu(src->next_entry_offset);
src = (void *)((char *)src +
le32_to_cpu(src->next_entry_offset));
}
/* didn't find the named attribute */
@ -2454,8 +2463,14 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon,
* and retry the ioctl again with larger array size sufficient
* to hold all of the snapshot GMT tokens on the second try.
*/
if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE)
if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE) {
if (ret_data_len < sizeof(struct smb_snapshot_array)) {
rc = -EIO;
kfree(retbuf);
return rc;
}
ret_data_len = sizeof(struct smb_snapshot_array);
}
/*
* We return struct SRV_SNAPSHOT_ARRAY, followed by
@ -5365,11 +5380,13 @@ receive_encrypted_standard(struct TCP_Server_Info *server,
length = decrypt_raw_data(server, buf, buf_size, NULL, false);
if (length)
return length;
pdu_length = buf_size;
next_is_large = server->large_buf;
one_more:
shdr = (struct smb2_hdr *)buf;
next_cmd = le32_to_cpu(shdr->NextCommand);
server->total_read = next_cmd ? next_cmd : pdu_length;
if (*num_mids >= MAX_COMPOUND) {
cifs_server_dbg(VFS, "too many PDUs in compound\n");
@ -5377,8 +5394,15 @@ receive_encrypted_standard(struct TCP_Server_Info *server,
}
if (next_cmd) {
if (WARN_ON_ONCE(next_cmd > pdu_length))
if (next_cmd < MID_HEADER_SIZE(server) ||
next_cmd > pdu_length ||
pdu_length - next_cmd < MID_HEADER_SIZE(server)) {
unsigned int max_next = pdu_length > (unsigned int)MID_HEADER_SIZE(server) ?
pdu_length - (unsigned int)MID_HEADER_SIZE(server) : 0;
cifs_server_dbg(VFS, "invalid NextCommand offset %u out of range [%zu, %u]\n",
next_cmd, MID_HEADER_SIZE(server), max_next);
return -1;
}
if (next_is_large)
next_buffer = (char *)cifs_buf_get();
else
@ -5414,6 +5438,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server,
server->bigbuf = buf = next_buffer;
else
server->smallbuf = buf = next_buffer;
next_buffer = NULL;
goto one_more;
} else if (ret != 0) {
/*

View File

@ -189,18 +189,19 @@ cifs_chan_skip_or_disable(struct cifs_ses *ses,
spin_unlock(&ses->chan_lock);
/*
* the above reference of server by channel
* needs to be dropped without holding chan_lock
* as cifs_put_tcp_session takes a higher lock
* i.e. cifs_tcp_ses_lock
* signal the channel and its primary server to
* reconnect before dropping the above reference of
* server by channel, which is done without holding
* chan_lock as cifs_put_tcp_session takes a higher
* lock i.e. cifs_tcp_ses_lock
*/
cifs_put_tcp_session(server, from_reconnect);
cifs_signal_cifsd_for_reconnect(server, false);
/* mark primary server as needing reconnect */
pserver = server->primary_server;
cifs_signal_cifsd_for_reconnect(pserver, false);
cifs_put_tcp_session(server, from_reconnect);
skip_terminate:
return -EHOSTDOWN;
}

View File

@ -27,6 +27,7 @@
EM(smb_eio_trace_copychunk_overcopy_c, "copychunk_overcopy_c") \
EM(smb_eio_trace_create_rsp_too_small, "create_rsp_too_small") \
EM(smb_eio_trace_dfsref_no_rsp, "dfsref_no_rsp") \
EM(smb_eio_trace_ea_next_offset, "ea_next_offset") \
EM(smb_eio_trace_ea_overrun, "ea_overrun") \
EM(smb_eio_trace_extract_will_pin, "extract_will_pin") \
EM(smb_eio_trace_forced_shutdown, "forced_shutdown") \