Commit eee7f5b4 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag '6.7-rc6-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6

Pull smb client fixes from Steve French:

 - two multichannel reconnect fixes, one fixing an important refcounting
   problem that can lead to umount problems

 - atime fix

 - five fixes for various potential OOB accesses, including a CVE fix,
   and two additional fixes for problems pointed out by Robert Morris's
   fuzzing investigation

* tag '6.7-rc6-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  cifs: do not let cifs_chan_update_iface deallocate channels
  cifs: fix a pending undercount of srv_count
  fs: cifs: Fix atime update check
  smb: client: fix potential OOB in smb2_dump_detail()
  smb: client: fix potential OOB in cifs_dump_detail()
  smb: client: fix OOB in smbCalcSize()
  smb: client: fix OOB in SMB2_query_info_init()
  smb: client: fix OOB in cifsd when receiving compounded resps
parents 1bf5c892 12d1e301
......@@ -40,11 +40,13 @@ void cifs_dump_detail(void *buf, struct TCP_Server_Info *server)
#ifdef CONFIG_CIFS_DEBUG2
struct smb_hdr *smb = buf;
cifs_dbg(VFS, "Cmd: %d Err: 0x%x Flags: 0x%x Flgs2: 0x%x Mid: %d Pid: %d\n",
smb->Command, smb->Status.CifsError,
smb->Flags, smb->Flags2, smb->Mid, smb->Pid);
cifs_dbg(VFS, "smb buf %p len %u\n", smb,
server->ops->calc_smb_size(smb));
cifs_dbg(VFS, "Cmd: %d Err: 0x%x Flags: 0x%x Flgs2: 0x%x Mid: %d Pid: %d Wct: %d\n",
smb->Command, smb->Status.CifsError, smb->Flags,
smb->Flags2, smb->Mid, smb->Pid, smb->WordCount);
if (!server->ops->check_message(buf, server->total_read, server)) {
cifs_dbg(VFS, "smb buf %p len %u\n", smb,
server->ops->calc_smb_size(smb));
}
#endif /* CONFIG_CIFS_DEBUG2 */
}
......
......@@ -532,7 +532,8 @@ struct smb_version_operations {
struct mid_q_entry **, char **, int *);
enum securityEnum (*select_sectype)(struct TCP_Server_Info *,
enum securityEnum);
int (*next_header)(char *);
int (*next_header)(struct TCP_Server_Info *server, char *buf,
unsigned int *noff);
/* ioctl passthrough for query_info */
int (*ioctl_query_info)(const unsigned int xid,
struct cifs_tcon *tcon,
......
......@@ -1201,7 +1201,12 @@ cifs_demultiplex_thread(void *p)
server->total_read += length;
if (server->ops->next_header) {
next_offset = server->ops->next_header(buf);
if (server->ops->next_header(server, buf, &next_offset)) {
cifs_dbg(VFS, "%s: malformed response (next_offset=%u)\n",
__func__, next_offset);
cifs_reconnect(server, true);
continue;
}
if (next_offset)
server->pdu_size = next_offset;
}
......
......@@ -4671,7 +4671,7 @@ static int cifs_readpage_worker(struct file *file, struct page *page,
/* we do not want atime to be less than mtime, it broke some apps */
atime = inode_set_atime_to_ts(inode, current_time(inode));
mtime = inode_get_mtime(inode);
if (timespec64_compare(&atime, &mtime))
if (timespec64_compare(&atime, &mtime) < 0)
inode_set_atime_to_ts(inode, inode_get_mtime(inode));
if (PAGE_SIZE > rc)
......
......@@ -363,6 +363,10 @@ checkSMB(char *buf, unsigned int total_read, struct TCP_Server_Info *server)
cifs_dbg(VFS, "Length less than smb header size\n");
}
return -EIO;
} else if (total_read < sizeof(*smb) + 2 * smb->WordCount) {
cifs_dbg(VFS, "%s: can't read BCC due to invalid WordCount(%u)\n",
__func__, smb->WordCount);
return -EIO;
}
/* otherwise, there is enough to get to the BCC */
......
......@@ -439,7 +439,7 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server)
cifs_dbg(FYI, "unable to find a suitable iface\n");
}
if (!chan_index && !iface) {
if (!iface) {
cifs_dbg(FYI, "unable to get the interface matching: %pIS\n",
&ss);
spin_unlock(&ses->iface_lock);
......@@ -447,7 +447,7 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server)
}
/* now drop the ref to the current iface */
if (old_iface && iface) {
if (old_iface) {
cifs_dbg(FYI, "replacing iface: %pIS with %pIS\n",
&old_iface->sockaddr,
&iface->sockaddr);
......@@ -460,44 +460,32 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server)
kref_put(&old_iface->refcount, release_iface);
} else if (old_iface) {
cifs_dbg(FYI, "releasing ref to iface: %pIS\n",
/* if a new candidate is not found, keep things as is */
cifs_dbg(FYI, "could not replace iface: %pIS\n",
&old_iface->sockaddr);
old_iface->num_channels--;
if (old_iface->weight_fulfilled)
old_iface->weight_fulfilled--;
kref_put(&old_iface->refcount, release_iface);
} else if (!chan_index) {
/* special case: update interface for primary channel */
cifs_dbg(FYI, "referencing primary channel iface: %pIS\n",
&iface->sockaddr);
iface->num_channels++;
iface->weight_fulfilled++;
} else {
WARN_ON(!iface);
cifs_dbg(FYI, "adding new iface: %pIS\n", &iface->sockaddr);
if (iface) {
cifs_dbg(FYI, "referencing primary channel iface: %pIS\n",
&iface->sockaddr);
iface->num_channels++;
iface->weight_fulfilled++;
}
}
spin_unlock(&ses->iface_lock);
spin_lock(&ses->chan_lock);
chan_index = cifs_ses_get_chan_index(ses, server);
if (chan_index == CIFS_INVAL_CHAN_INDEX) {
if (iface) {
spin_lock(&ses->chan_lock);
chan_index = cifs_ses_get_chan_index(ses, server);
if (chan_index == CIFS_INVAL_CHAN_INDEX) {
spin_unlock(&ses->chan_lock);
return 0;
}
ses->chans[chan_index].iface = iface;
spin_unlock(&ses->chan_lock);
return 0;
}
ses->chans[chan_index].iface = iface;
/* No iface is found. if secondary chan, drop connection */
if (!iface && SERVER_IS_CHAN(server))
ses->chans[chan_index].server = NULL;
spin_unlock(&ses->chan_lock);
if (!iface && SERVER_IS_CHAN(server))
cifs_put_tcp_session(server, false);
return rc;
}
......
......@@ -173,6 +173,21 @@ smb2_check_message(char *buf, unsigned int len, struct TCP_Server_Info *server)
}
mid = le64_to_cpu(shdr->MessageId);
if (check_smb2_hdr(shdr, mid))
return 1;
if (shdr->StructureSize != SMB2_HEADER_STRUCTURE_SIZE) {
cifs_dbg(VFS, "Invalid structure size %u\n",
le16_to_cpu(shdr->StructureSize));
return 1;
}
command = le16_to_cpu(shdr->Command);
if (command >= NUMBER_OF_SMB2_COMMANDS) {
cifs_dbg(VFS, "Invalid SMB2 command %d\n", command);
return 1;
}
if (len < pdu_size) {
if ((len >= hdr_size)
&& (shdr->Status != 0)) {
......@@ -193,21 +208,6 @@ smb2_check_message(char *buf, unsigned int len, struct TCP_Server_Info *server)
return 1;
}
if (check_smb2_hdr(shdr, mid))
return 1;
if (shdr->StructureSize != SMB2_HEADER_STRUCTURE_SIZE) {
cifs_dbg(VFS, "Invalid structure size %u\n",
le16_to_cpu(shdr->StructureSize));
return 1;
}
command = le16_to_cpu(shdr->Command);
if (command >= NUMBER_OF_SMB2_COMMANDS) {
cifs_dbg(VFS, "Invalid SMB2 command %d\n", command);
return 1;
}
if (smb2_rsp_struct_sizes[command] != pdu->StructureSize2) {
if (command != SMB2_OPLOCK_BREAK_HE && (shdr->Status == 0 ||
pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE)) {
......
......@@ -403,8 +403,10 @@ smb2_dump_detail(void *buf, struct TCP_Server_Info *server)
cifs_server_dbg(VFS, "Cmd: %d Err: 0x%x Flags: 0x%x Mid: %llu Pid: %d\n",
shdr->Command, shdr->Status, shdr->Flags, shdr->MessageId,
shdr->Id.SyncId.ProcessId);
cifs_server_dbg(VFS, "smb buf %p len %u\n", buf,
server->ops->calc_smb_size(buf));
if (!server->ops->check_message(buf, server->total_read, server)) {
cifs_server_dbg(VFS, "smb buf %p len %u\n", buf,
server->ops->calc_smb_size(buf));
}
#endif
}
......@@ -5074,17 +5076,22 @@ smb3_handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid)
NULL, 0, false);
}
static int
smb2_next_header(char *buf)
static int smb2_next_header(struct TCP_Server_Info *server, char *buf,
unsigned int *noff)
{
struct smb2_hdr *hdr = (struct smb2_hdr *)buf;
struct smb2_transform_hdr *t_hdr = (struct smb2_transform_hdr *)buf;
if (hdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM)
return sizeof(struct smb2_transform_hdr) +
le32_to_cpu(t_hdr->OriginalMessageSize);
return le32_to_cpu(hdr->NextCommand);
if (hdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM) {
*noff = le32_to_cpu(t_hdr->OriginalMessageSize);
if (unlikely(check_add_overflow(*noff, sizeof(*t_hdr), noff)))
return -EINVAL;
} else {
*noff = le32_to_cpu(hdr->NextCommand);
}
if (unlikely(*noff && *noff < MID_HEADER_SIZE(server)))
return -EINVAL;
return 0;
}
int cifs_sfu_make_node(unsigned int xid, struct inode *inode,
......
......@@ -411,8 +411,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
}
if (smb2_command != SMB2_INTERNAL_CMD)
if (mod_delayed_work(cifsiod_wq, &server->reconnect, 0))
cifs_put_tcp_session(server, false);
mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
atomic_inc(&tconInfoReconnectCount);
out:
......@@ -471,10 +470,15 @@ static int __smb2_plain_req_init(__le16 smb2_command, struct cifs_tcon *tcon,
void **request_buf, unsigned int *total_len)
{
/* BB eventually switch this to SMB2 specific small buf size */
if (smb2_command == SMB2_SET_INFO)
switch (smb2_command) {
case SMB2_SET_INFO:
case SMB2_QUERY_INFO:
*request_buf = cifs_buf_get();
else
break;
default:
*request_buf = cifs_small_buf_get();
break;
}
if (*request_buf == NULL) {
/* BB should we add a retry in here if not a writepage? */
return -ENOMEM;
......@@ -3587,8 +3591,13 @@ SMB2_query_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
struct smb2_query_info_req *req;
struct kvec *iov = rqst->rq_iov;
unsigned int total_len;
size_t len;
int rc;
if (unlikely(check_add_overflow(input_len, sizeof(*req), &len) ||
len > CIFSMaxBufSize))
return -EINVAL;
rc = smb2_plain_req_init(SMB2_QUERY_INFO, tcon, server,
(void **) &req, &total_len);
if (rc)
......@@ -3610,7 +3619,7 @@ SMB2_query_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
iov[0].iov_base = (char *)req;
/* 1 for Buffer */
iov[0].iov_len = total_len - 1 + input_len;
iov[0].iov_len = len;
return 0;
}
......@@ -3618,7 +3627,7 @@ void
SMB2_query_info_free(struct smb_rqst *rqst)
{
if (rqst && rqst->rq_iov)
cifs_small_buf_release(rqst->rq_iov[0].iov_base); /* request */
cifs_buf_release(rqst->rq_iov[0].iov_base); /* request */
}
static int
......@@ -5493,6 +5502,11 @@ build_qfs_info_req(struct kvec *iov, struct cifs_tcon *tcon,
return 0;
}
static inline void free_qfs_info_req(struct kvec *iov)
{
cifs_buf_release(iov->iov_base);
}
int
SMB311_posix_qfs_info(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid, struct kstatfs *fsdata)
......@@ -5524,7 +5538,7 @@ SMB311_posix_qfs_info(const unsigned int xid, struct cifs_tcon *tcon,
rc = cifs_send_recv(xid, ses, server,
&rqst, &resp_buftype, flags, &rsp_iov);
cifs_small_buf_release(iov.iov_base);
free_qfs_info_req(&iov);
if (rc) {
cifs_stats_fail_inc(tcon, SMB2_QUERY_INFO_HE);
goto posix_qfsinf_exit;
......@@ -5575,7 +5589,7 @@ SMB2_QFS_info(const unsigned int xid, struct cifs_tcon *tcon,
rc = cifs_send_recv(xid, ses, server,
&rqst, &resp_buftype, flags, &rsp_iov);
cifs_small_buf_release(iov.iov_base);
free_qfs_info_req(&iov);
if (rc) {
cifs_stats_fail_inc(tcon, SMB2_QUERY_INFO_HE);
goto qfsinf_exit;
......@@ -5642,7 +5656,7 @@ SMB2_QFS_attr(const unsigned int xid, struct cifs_tcon *tcon,
rc = cifs_send_recv(xid, ses, server,
&rqst, &resp_buftype, flags, &rsp_iov);
cifs_small_buf_release(iov.iov_base);
free_qfs_info_req(&iov);
if (rc) {
cifs_stats_fail_inc(tcon, SMB2_QUERY_INFO_HE);
goto qfsattr_exit;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment