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) ...@@ -40,11 +40,13 @@ void cifs_dump_detail(void *buf, struct TCP_Server_Info *server)
#ifdef CONFIG_CIFS_DEBUG2 #ifdef CONFIG_CIFS_DEBUG2
struct smb_hdr *smb = buf; struct smb_hdr *smb = buf;
cifs_dbg(VFS, "Cmd: %d Err: 0x%x Flags: 0x%x Flgs2: 0x%x Mid: %d Pid: %d\n", 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->Command, smb->Status.CifsError, smb->Flags,
smb->Flags, smb->Flags2, smb->Mid, smb->Pid); smb->Flags2, smb->Mid, smb->Pid, smb->WordCount);
cifs_dbg(VFS, "smb buf %p len %u\n", smb, if (!server->ops->check_message(buf, server->total_read, server)) {
server->ops->calc_smb_size(smb)); cifs_dbg(VFS, "smb buf %p len %u\n", smb,
server->ops->calc_smb_size(smb));
}
#endif /* CONFIG_CIFS_DEBUG2 */ #endif /* CONFIG_CIFS_DEBUG2 */
} }
......
...@@ -532,7 +532,8 @@ struct smb_version_operations { ...@@ -532,7 +532,8 @@ struct smb_version_operations {
struct mid_q_entry **, char **, int *); struct mid_q_entry **, char **, int *);
enum securityEnum (*select_sectype)(struct TCP_Server_Info *, enum securityEnum (*select_sectype)(struct TCP_Server_Info *,
enum securityEnum); enum securityEnum);
int (*next_header)(char *); int (*next_header)(struct TCP_Server_Info *server, char *buf,
unsigned int *noff);
/* ioctl passthrough for query_info */ /* ioctl passthrough for query_info */
int (*ioctl_query_info)(const unsigned int xid, int (*ioctl_query_info)(const unsigned int xid,
struct cifs_tcon *tcon, struct cifs_tcon *tcon,
......
...@@ -1201,7 +1201,12 @@ cifs_demultiplex_thread(void *p) ...@@ -1201,7 +1201,12 @@ cifs_demultiplex_thread(void *p)
server->total_read += length; server->total_read += length;
if (server->ops->next_header) { 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) if (next_offset)
server->pdu_size = next_offset; server->pdu_size = next_offset;
} }
......
...@@ -4671,7 +4671,7 @@ static int cifs_readpage_worker(struct file *file, struct page *page, ...@@ -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 */ /* we do not want atime to be less than mtime, it broke some apps */
atime = inode_set_atime_to_ts(inode, current_time(inode)); atime = inode_set_atime_to_ts(inode, current_time(inode));
mtime = inode_get_mtime(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)); inode_set_atime_to_ts(inode, inode_get_mtime(inode));
if (PAGE_SIZE > rc) if (PAGE_SIZE > rc)
......
...@@ -363,6 +363,10 @@ checkSMB(char *buf, unsigned int total_read, struct TCP_Server_Info *server) ...@@ -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"); cifs_dbg(VFS, "Length less than smb header size\n");
} }
return -EIO; 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 */ /* 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) ...@@ -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"); 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", cifs_dbg(FYI, "unable to get the interface matching: %pIS\n",
&ss); &ss);
spin_unlock(&ses->iface_lock); spin_unlock(&ses->iface_lock);
...@@ -447,7 +447,7 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server) ...@@ -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 */ /* now drop the ref to the current iface */
if (old_iface && iface) { if (old_iface) {
cifs_dbg(FYI, "replacing iface: %pIS with %pIS\n", cifs_dbg(FYI, "replacing iface: %pIS with %pIS\n",
&old_iface->sockaddr, &old_iface->sockaddr,
&iface->sockaddr); &iface->sockaddr);
...@@ -460,44 +460,32 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server) ...@@ -460,44 +460,32 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server)
kref_put(&old_iface->refcount, release_iface); kref_put(&old_iface->refcount, release_iface);
} else if (old_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->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) { } else if (!chan_index) {
/* special case: update interface for primary channel */ /* special case: update interface for primary channel */
cifs_dbg(FYI, "referencing primary channel iface: %pIS\n", if (iface) {
&iface->sockaddr); cifs_dbg(FYI, "referencing primary channel iface: %pIS\n",
iface->num_channels++; &iface->sockaddr);
iface->weight_fulfilled++; iface->num_channels++;
} else { iface->weight_fulfilled++;
WARN_ON(!iface); }
cifs_dbg(FYI, "adding new iface: %pIS\n", &iface->sockaddr);
} }
spin_unlock(&ses->iface_lock); spin_unlock(&ses->iface_lock);
spin_lock(&ses->chan_lock); if (iface) {
chan_index = cifs_ses_get_chan_index(ses, server); spin_lock(&ses->chan_lock);
if (chan_index == CIFS_INVAL_CHAN_INDEX) { 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); 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; return rc;
} }
......
...@@ -173,6 +173,21 @@ smb2_check_message(char *buf, unsigned int len, struct TCP_Server_Info *server) ...@@ -173,6 +173,21 @@ smb2_check_message(char *buf, unsigned int len, struct TCP_Server_Info *server)
} }
mid = le64_to_cpu(shdr->MessageId); 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 < pdu_size) {
if ((len >= hdr_size) if ((len >= hdr_size)
&& (shdr->Status != 0)) { && (shdr->Status != 0)) {
...@@ -193,21 +208,6 @@ smb2_check_message(char *buf, unsigned int len, struct TCP_Server_Info *server) ...@@ -193,21 +208,6 @@ smb2_check_message(char *buf, unsigned int len, struct TCP_Server_Info *server)
return 1; 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 (smb2_rsp_struct_sizes[command] != pdu->StructureSize2) {
if (command != SMB2_OPLOCK_BREAK_HE && (shdr->Status == 0 || if (command != SMB2_OPLOCK_BREAK_HE && (shdr->Status == 0 ||
pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE)) { pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE)) {
......
...@@ -403,8 +403,10 @@ smb2_dump_detail(void *buf, struct TCP_Server_Info *server) ...@@ -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", 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->Command, shdr->Status, shdr->Flags, shdr->MessageId,
shdr->Id.SyncId.ProcessId); shdr->Id.SyncId.ProcessId);
cifs_server_dbg(VFS, "smb buf %p len %u\n", buf, if (!server->ops->check_message(buf, server->total_read, server)) {
server->ops->calc_smb_size(buf)); cifs_server_dbg(VFS, "smb buf %p len %u\n", buf,
server->ops->calc_smb_size(buf));
}
#endif #endif
} }
...@@ -5074,17 +5076,22 @@ smb3_handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid) ...@@ -5074,17 +5076,22 @@ smb3_handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid)
NULL, 0, false); NULL, 0, false);
} }
static int static int smb2_next_header(struct TCP_Server_Info *server, char *buf,
smb2_next_header(char *buf) unsigned int *noff)
{ {
struct smb2_hdr *hdr = (struct smb2_hdr *)buf; struct smb2_hdr *hdr = (struct smb2_hdr *)buf;
struct smb2_transform_hdr *t_hdr = (struct smb2_transform_hdr *)buf; struct smb2_transform_hdr *t_hdr = (struct smb2_transform_hdr *)buf;
if (hdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM) if (hdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM) {
return sizeof(struct smb2_transform_hdr) + *noff = le32_to_cpu(t_hdr->OriginalMessageSize);
le32_to_cpu(t_hdr->OriginalMessageSize); if (unlikely(check_add_overflow(*noff, sizeof(*t_hdr), noff)))
return -EINVAL;
return le32_to_cpu(hdr->NextCommand); } 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, int cifs_sfu_make_node(unsigned int xid, struct inode *inode,
......
...@@ -411,8 +411,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, ...@@ -411,8 +411,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
} }
if (smb2_command != SMB2_INTERNAL_CMD) if (smb2_command != SMB2_INTERNAL_CMD)
if (mod_delayed_work(cifsiod_wq, &server->reconnect, 0)) mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
cifs_put_tcp_session(server, false);
atomic_inc(&tconInfoReconnectCount); atomic_inc(&tconInfoReconnectCount);
out: out:
...@@ -471,10 +470,15 @@ static int __smb2_plain_req_init(__le16 smb2_command, struct cifs_tcon *tcon, ...@@ -471,10 +470,15 @@ static int __smb2_plain_req_init(__le16 smb2_command, struct cifs_tcon *tcon,
void **request_buf, unsigned int *total_len) void **request_buf, unsigned int *total_len)
{ {
/* BB eventually switch this to SMB2 specific small buf size */ /* 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(); *request_buf = cifs_buf_get();
else break;
default:
*request_buf = cifs_small_buf_get(); *request_buf = cifs_small_buf_get();
break;
}
if (*request_buf == NULL) { if (*request_buf == NULL) {
/* BB should we add a retry in here if not a writepage? */ /* BB should we add a retry in here if not a writepage? */
return -ENOMEM; return -ENOMEM;
...@@ -3587,8 +3591,13 @@ SMB2_query_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server, ...@@ -3587,8 +3591,13 @@ SMB2_query_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
struct smb2_query_info_req *req; struct smb2_query_info_req *req;
struct kvec *iov = rqst->rq_iov; struct kvec *iov = rqst->rq_iov;
unsigned int total_len; unsigned int total_len;
size_t len;
int rc; 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, rc = smb2_plain_req_init(SMB2_QUERY_INFO, tcon, server,
(void **) &req, &total_len); (void **) &req, &total_len);
if (rc) if (rc)
...@@ -3610,7 +3619,7 @@ SMB2_query_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server, ...@@ -3610,7 +3619,7 @@ SMB2_query_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
iov[0].iov_base = (char *)req; iov[0].iov_base = (char *)req;
/* 1 for Buffer */ /* 1 for Buffer */
iov[0].iov_len = total_len - 1 + input_len; iov[0].iov_len = len;
return 0; return 0;
} }
...@@ -3618,7 +3627,7 @@ void ...@@ -3618,7 +3627,7 @@ void
SMB2_query_info_free(struct smb_rqst *rqst) SMB2_query_info_free(struct smb_rqst *rqst)
{ {
if (rqst && rqst->rq_iov) 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 static int
...@@ -5493,6 +5502,11 @@ build_qfs_info_req(struct kvec *iov, struct cifs_tcon *tcon, ...@@ -5493,6 +5502,11 @@ build_qfs_info_req(struct kvec *iov, struct cifs_tcon *tcon,
return 0; return 0;
} }
static inline void free_qfs_info_req(struct kvec *iov)
{
cifs_buf_release(iov->iov_base);
}
int int
SMB311_posix_qfs_info(const unsigned int xid, struct cifs_tcon *tcon, SMB311_posix_qfs_info(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid, struct kstatfs *fsdata) 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, ...@@ -5524,7 +5538,7 @@ SMB311_posix_qfs_info(const unsigned int xid, struct cifs_tcon *tcon,
rc = cifs_send_recv(xid, ses, server, rc = cifs_send_recv(xid, ses, server,
&rqst, &resp_buftype, flags, &rsp_iov); &rqst, &resp_buftype, flags, &rsp_iov);
cifs_small_buf_release(iov.iov_base); free_qfs_info_req(&iov);
if (rc) { if (rc) {
cifs_stats_fail_inc(tcon, SMB2_QUERY_INFO_HE); cifs_stats_fail_inc(tcon, SMB2_QUERY_INFO_HE);
goto posix_qfsinf_exit; goto posix_qfsinf_exit;
...@@ -5575,7 +5589,7 @@ SMB2_QFS_info(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -5575,7 +5589,7 @@ SMB2_QFS_info(const unsigned int xid, struct cifs_tcon *tcon,
rc = cifs_send_recv(xid, ses, server, rc = cifs_send_recv(xid, ses, server,
&rqst, &resp_buftype, flags, &rsp_iov); &rqst, &resp_buftype, flags, &rsp_iov);
cifs_small_buf_release(iov.iov_base); free_qfs_info_req(&iov);
if (rc) { if (rc) {
cifs_stats_fail_inc(tcon, SMB2_QUERY_INFO_HE); cifs_stats_fail_inc(tcon, SMB2_QUERY_INFO_HE);
goto qfsinf_exit; goto qfsinf_exit;
...@@ -5642,7 +5656,7 @@ SMB2_QFS_attr(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -5642,7 +5656,7 @@ SMB2_QFS_attr(const unsigned int xid, struct cifs_tcon *tcon,
rc = cifs_send_recv(xid, ses, server, rc = cifs_send_recv(xid, ses, server,
&rqst, &resp_buftype, flags, &rsp_iov); &rqst, &resp_buftype, flags, &rsp_iov);
cifs_small_buf_release(iov.iov_base); free_qfs_info_req(&iov);
if (rc) { if (rc) {
cifs_stats_fail_inc(tcon, SMB2_QUERY_INFO_HE); cifs_stats_fail_inc(tcon, SMB2_QUERY_INFO_HE);
goto qfsattr_exit; 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