mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 20:54:03 +02:00
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:
commit
17e7b8eacf
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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") \
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user