diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index 647fa26da4d2..50217f47a0ed 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -649,6 +649,17 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync) flush_delayed_work(&cfids->laundromat_work); } +static bool cached_dir_get_tcon(struct cifs_tcon *tcon, + enum smb3_tcon_ref_trace trace) +{ + guard(spinlock)(&tcon->tc_lock); + if (tcon->status == TID_EXITING || tcon->tc_count <= 0) + return false; + ++tcon->tc_count; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, trace); + return true; +} + static void cached_dir_offload_close(struct work_struct *work) { @@ -693,6 +704,9 @@ bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]) !memcmp(lease_key, cfid->fid.lease_key, SMB2_LEASE_KEY_SIZE)) { + if (!cached_dir_get_tcon(tcon, + netfs_trace_tcon_ref_get_cached_lease_break)) + break; cfid->has_lease = false; cfid->time = 0; /* @@ -703,9 +717,6 @@ bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]) cfid->on_list = false; cfids->num_entries--; - ++tcon->tc_count; - trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, - netfs_trace_tcon_ref_get_cached_lease_break); queue_work(cfid_put_wq, &cfid->put_work); spin_unlock(&cfids->cfid_list_lock); return true; @@ -813,12 +824,13 @@ static void cfids_laundromat_worker(struct work_struct *work) cfid->dentry = NULL; if (cfid->is_open) { - spin_lock(&cfid->tcon->tc_lock); - ++cfid->tcon->tc_count; - trace_smb3_tcon_ref(cfid->tcon->debug_id, cfid->tcon->tc_count, - netfs_trace_tcon_ref_get_cached_laundromat); - spin_unlock(&cfid->tcon->tc_lock); - queue_work(serverclose_wq, &cfid->close_work); + if (cached_dir_get_tcon(cfid->tcon, + netfs_trace_tcon_ref_get_cached_laundromat)) { + queue_work(serverclose_wq, &cfid->close_work); + continue; + } + scoped_guard(spinlock, &cfids->cfid_list_lock) + list_add_tail(&cfid->entry, &cfids->dying); } else /* * Drop the ref-count from above, either the lease-ref (if there diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index d1a92bb4d9a5..f9f61c758a75 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -1826,12 +1826,15 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode, /* Potentially, five new ACEs can be added to the ACL for U,G,O mapping */ if (pnmode && *pnmode != NO_CHANGE_64) { /* chmod */ + nsecdesclen = sizeof(struct smb_acl) + + 2 * sizeof(struct smb_sid); if (posix) - nsecdesclen = 1 * sizeof(struct smb_ace); + nsecdesclen += sizeof(struct smb_ntsd) + + sizeof(struct smb_ace); else if (mode_from_sid) - nsecdesclen = secdesclen + (2 * sizeof(struct smb_ace)); + nsecdesclen += secdesclen + 2 * sizeof(struct smb_ace); else /* cifsacl */ - nsecdesclen = secdesclen + (5 * sizeof(struct smb_ace)); + nsecdesclen += secdesclen + 5 * sizeof(struct smb_ace); } else { /* chown */ /* When ownership changes, changes new owner sid length could be different */ nsecdesclen = sizeof(struct smb_ntsd) + (sizeof(struct smb_sid) * 2); diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 576155caf888..bf199a5ca084 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -2019,7 +2019,6 @@ init_cifs(void) if (rc) return rc; - cifs_proc_init(); INIT_LIST_HEAD(&cifs_tcp_ses_list); /* * Initialize Global counters @@ -2068,10 +2067,8 @@ init_cifs(void) cifsiod_wq = alloc_workqueue("cifsiod", WQ_FREEZABLE | WQ_MEM_RECLAIM | WQ_PERCPU, 0); - if (!cifsiod_wq) { - rc = -ENOMEM; - goto out_clean_proc; - } + if (!cifsiod_wq) + return -ENOMEM; /* * Consider in future setting limit!=0 maybe to min(num_of_cores - 1, 3) @@ -2162,19 +2159,22 @@ init_cifs(void) if (rc) goto out_cifs_swn_init; + cifs_proc_init(); + rc = register_filesystem(&cifs_fs_type); if (rc) - goto out_init_cifs_idmap; + goto out_clean_proc; rc = register_filesystem(&smb3_fs_type); if (rc) { unregister_filesystem(&cifs_fs_type); - goto out_init_cifs_idmap; + goto out_clean_proc; } return 0; -out_init_cifs_idmap: +out_clean_proc: + cifs_proc_clean(); exit_cifs_idmap(); out_cifs_swn_init: #ifdef CONFIG_CIFS_SWN_UPCALL @@ -2210,8 +2210,6 @@ init_cifs(void) destroy_workqueue(decrypt_wq); out_destroy_cifsiod_wq: destroy_workqueue(cifsiod_wq); -out_clean_proc: - cifs_proc_clean(); return rc; } @@ -2221,6 +2219,7 @@ exit_cifs(void) cifs_dbg(NOISY, "exit_smb3\n"); unregister_filesystem(&cifs_fs_type); unregister_filesystem(&smb3_fs_type); + cifs_proc_clean(); cifs_release_automount_timer(); exit_cifs_idmap(); #ifdef CONFIG_CIFS_SWN_UPCALL @@ -2243,7 +2242,6 @@ exit_cifs(void) destroy_workqueue(serverclose_wq); destroy_workqueue(cfid_put_wq); destroy_workqueue(cifsiod_wq); - cifs_proc_clean(); } MODULE_AUTHOR("Steve French"); diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 73b49dcb5215..d3ca688b6aba 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -681,6 +682,7 @@ struct TCP_Server_Info { spinlock_t srv_lock; /* protect anything here that is not protected */ __u64 conn_id; /* connection identifier (useful for debugging) */ int srv_count; /* reference counter */ + refcount_t obj_refs; /* struct lifetime: srv_count users + cifsd */ int rfc1001_sessinit; /* whether to estasblish netbios session */ bool with_rfc1001; /* if netbios session is used */ /* 15 character server name + 0x20 16th byte indicating type = srv */ diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 50ca062a22bf..2936b38de98f 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -2369,6 +2369,7 @@ CIFSSMBPosixLock(const unsigned int xid, struct cifs_tcon *tcon, cifs_dbg(FYI, "Send error in Posix Lock = %d\n", rc); } else if (pLockData) { /* lock structure can be returned on get */ + unsigned int rsp_len; __u16 data_offset; __u16 data_count; rc = validate_t2((struct smb_t2_rsp *)pSMBr); @@ -2380,11 +2381,18 @@ CIFSSMBPosixLock(const unsigned int xid, struct cifs_tcon *tcon, } data_offset = le16_to_cpu(pSMBr->t2.DataOffset); data_count = le16_to_cpu(pSMBr->t2.DataCount); + rsp_len = smbCalcSize(&pSMBr->hdr); if (data_count < sizeof(struct cifs_posix_lock)) { rc = smb_EIO2(smb_eio_trace_lock_data_too_small, data_count, sizeof(struct cifs_posix_lock)); goto plk_err_exit; } + if (data_offset > rsp_len || + data_count > rsp_len - data_offset) { + rc = smb_EIO2(smb_eio_trace_lock_data_area, + data_offset, data_count); + goto plk_err_exit; + } parm_data = (struct cifs_posix_lock *) ((char *)&pSMBr->hdr.Protocol + data_offset); if (parm_data->lock_type == cpu_to_le16(CIFS_UNLCK)) @@ -3779,9 +3787,10 @@ static int validate_ntransact(char *buf, char **ppparm, char **ppdata, __u32 *pparmlen, __u32 *pdatalen) { - char *end_of_smb; __u32 data_count, data_offset, parm_count, parm_offset; struct smb_com_ntransact_rsp *pSMBr; + size_t buflen; + char *end_buf; u16 bcc; *pdatalen = 0; @@ -3792,37 +3801,42 @@ validate_ntransact(char *buf, char **ppparm, char **ppdata, pSMBr = (struct smb_com_ntransact_rsp *)buf; + if (pSMBr->hdr.WordCount != 18) { + cifs_dbg(FYI, "invalid NT transaction WordCount %u\n", + pSMBr->hdr.WordCount); + return -EINVAL; + } + bcc = get_bcc(&pSMBr->hdr); - end_of_smb = 2 /* sizeof byte count */ + bcc + - (char *)&pSMBr->ByteCount; + end_buf = 2 /* sizeof byte count */ + bcc + (char *)&pSMBr->ByteCount; + buflen = end_buf - buf; data_offset = le32_to_cpu(pSMBr->DataOffset); data_count = le32_to_cpu(pSMBr->DataCount); parm_offset = le32_to_cpu(pSMBr->ParameterOffset); parm_count = le32_to_cpu(pSMBr->ParameterCount); - *ppparm = (char *)&pSMBr->hdr.Protocol + parm_offset; - *ppdata = (char *)&pSMBr->hdr.Protocol + data_offset; - /* should we also check that parm and data areas do not overlap? */ - if (*ppparm > end_of_smb) { + if (parm_offset > buflen) { cifs_dbg(FYI, "parms start after end of smb\n"); return -EINVAL; - } else if (parm_count + *ppparm > end_of_smb) { + } else if (parm_count > buflen - parm_offset) { cifs_dbg(FYI, "parm end after end of smb\n"); return -EINVAL; - } else if (*ppdata > end_of_smb) { + } else if (data_offset > buflen) { cifs_dbg(FYI, "data starts after end of smb\n"); return -EINVAL; - } else if (data_count + *ppdata > end_of_smb) { - cifs_dbg(FYI, "data %p + count %d (%p) past smb end %p start %p\n", - *ppdata, data_count, (data_count + *ppdata), - end_of_smb, pSMBr); + } else if (data_count > buflen - data_offset) { + cifs_dbg(FYI, "data offset %u + count %u past smb length %zu\n", + data_offset, data_count, buflen); return -EINVAL; } else if (parm_count + data_count > bcc) { cifs_dbg(FYI, "parm count and data count larger than SMB\n"); return -EINVAL; } + + *ppparm = buf + parm_offset; + *ppdata = buf + data_offset; *pdatalen = data_count; *pparmlen = parm_count; return 0; @@ -6201,9 +6215,11 @@ CIFSSMBQAllEAs(const unsigned int xid, struct cifs_tcon *tcon, int rc = 0; int bytes_returned; int list_len; + u32 ea_list_len; struct fealist *ea_response_data; struct fea *temp_fea; char *temp_ptr; + char *byte_area; char *end_of_smb; __u16 params, byte_count, data_offset; unsigned int ea_name_len = ea_name ? strlen(ea_name) : 0; @@ -6282,9 +6298,19 @@ CIFSSMBQAllEAs(const unsigned int xid, struct cifs_tcon *tcon, ea_response_data = (struct fealist *) (((char *) &pSMBr->hdr.Protocol) + data_offset); - list_len = le32_to_cpu(ea_response_data->list_len); - cifs_dbg(FYI, "ea length %d\n", list_len); - if (list_len <= 8) { + byte_area = pByteArea(&pSMBr->hdr); + end_of_smb = byte_area + get_bcc(&pSMBr->hdr); + if ((char *)ea_response_data < byte_area || + (char *)ea_response_data > + end_of_smb - sizeof(ea_response_data->list_len)) { + rc = smb_EIO2(smb_eio_trace_qalleas_overlong, + data_offset, get_bcc(&pSMBr->hdr)); + goto QAllEAsOut; + } + + ea_list_len = le32_to_cpu(ea_response_data->list_len); + cifs_dbg(FYI, "%s: ea length: %u\n", __func__, ea_list_len); + if (ea_list_len <= 8) { cifs_dbg(FYI, "empty EA list returned from server\n"); /* didn't find the named attribute */ if (ea_name) @@ -6293,14 +6319,14 @@ CIFSSMBQAllEAs(const unsigned int xid, struct cifs_tcon *tcon, } /* make sure list_len doesn't go past end of SMB */ - end_of_smb = (char *)pByteArea(&pSMBr->hdr) + get_bcc(&pSMBr->hdr); - if ((char *)ea_response_data + list_len > end_of_smb) { + if (ea_list_len > end_of_smb - (char *)ea_response_data) { cifs_dbg(FYI, "EA list appears to go beyond SMB\n"); rc = smb_EIO2(smb_eio_trace_qalleas_overlong, - (unsigned long)ea_response_data + list_len - (unsigned long)pSMBr, - (unsigned long)end_of_smb - (unsigned long)pSMBr); + ea_list_len, + end_of_smb - (char *)ea_response_data); goto QAllEAsOut; } + list_len = ea_list_len; /* account for ea list len */ list_len -= 4; diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index c3614c077746..e68fffb2ab2e 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1083,6 +1083,19 @@ cifs_enable_signing(struct TCP_Server_Info *server, bool mnt_sign_required) return 0; } +static void cifs_tcp_server_put(struct TCP_Server_Info *server) +{ + if (!refcount_dec_and_test(&server->obj_refs)) + return; + + cifs_crypto_secmech_release(server); + kfree_sensitive(server->session_key.response); + put_net(cifs_net_ns(server)); + kfree(server->leaf_fullpath); + kfree(server->hostname); + kfree_sensitive(server); +} + static noinline_for_stack void clean_demultiplex_info(struct TCP_Server_Info *server) { @@ -1168,14 +1181,11 @@ clean_demultiplex_info(struct TCP_Server_Info *server) */ } - put_net(cifs_net_ns(server)); - kfree(server->leaf_fullpath); - kfree(server->hostname); - kfree_sensitive(server); - length = atomic_dec_return(&tcpSesAllocCount); if (length > 0) mempool_resize(cifs_req_poolp, length + cifs_min_rcv); + + cifs_tcp_server_put(server); } static int @@ -1751,7 +1761,7 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect) else cancel_delayed_work_sync(&server->reconnect); - /* For secondary channels, we pick up ref-count on the primary server */ + /* For secondary channels, drop the reference on the primary server */ if (SERVER_IS_CHAN(server)) cifs_put_tcp_session(server->primary_server, from_reconnect); @@ -1759,15 +1769,11 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect) server->tcpStatus = CifsExiting; spin_unlock(&server->srv_lock); - cifs_crypto_secmech_release(server); - - kfree_sensitive(server->session_key.response); - server->session_key.response = NULL; - server->session_key.len = 0; - task = xchg(&server->tsk, NULL); if (task) send_sig(SIGKILL, task, 1); + + cifs_tcp_server_put(server); } struct TCP_Server_Info * @@ -1789,6 +1795,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, rc = -ENOMEM; goto out_err; } + refcount_set(&tcp_ses->obj_refs, 1); tcp_ses->hostname = kstrdup(ctx->server_hostname, GFP_KERNEL); if (!tcp_ses->hostname) { @@ -1927,6 +1934,8 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, module_put(THIS_MODULE); goto out_err_crypto_release; } + /* Keep the server alive until the demultiplex thread has exited */ + refcount_inc(&tcp_ses->obj_refs); /* thread created, put it on the list */ spin_lock(&cifs_tcp_ses_lock); list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list); diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index 5e5cf92d1eb3..5f13bd7ce73c 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -225,6 +225,22 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len, return 1; } + /* + * A SESSION_SETUP response that asks for more processing carries a + * security blob and is parsed as a normal reply. Do not let the + * short-error path below accept a frame that cannot hold the fixed + * response fields; the minimum-PDU check later is unreachable once + * the early return has accepted the frame. + */ + if (command == SMB2_SESSION_SETUP_HE && + shdr->Status == STATUS_MORE_PROCESSING_REQUIRED && + len < smb2_min_pdu_len[command]) { + cifs_server_dbg(VFS, + "%s: session setup response too short: %u < %zu\n", + __func__, len, smb2_min_pdu_len[command]); + return 1; + } + if (len < pdu_size) { if ((len >= hdr_size) && (shdr->Status != 0)) { diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 20624f5dff9b..dc55cd512616 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4360,6 +4360,15 @@ static int smb3_fiemap(struct cifs_tcon *tcon, goto out; } + /* + * A truncated range blob must contain at least one range, otherwise + * there is no offset to resume from and out_data is NULL. + */ + if (!last_blob && out_data_len == 0) { + rc = -EINVAL; + goto out; + } + num = out_data_len / sizeof(struct file_allocated_range_buffer); for (i = 0; i < num; i++) { flags = 0; @@ -4380,10 +4389,25 @@ static int smb3_fiemap(struct cifs_tcon *tcon, } if (!last_blob) { - next = le64_to_cpu(out_data[num - 1].file_offset) + - le64_to_cpu(out_data[num - 1].length); - len = len - (next - start); + u64 last_start, last_len; + + last_start = le64_to_cpu(out_data[num - 1].file_offset); + last_len = le64_to_cpu(out_data[num - 1].length); + + if (check_add_overflow(last_start, last_len, &next) || + next <= start) { + rc = -EINVAL; + goto out; + } + if (next - start >= len) { + rc = 0; + goto out; + } + + len -= next - start; start = next; + kfree(out_data); + out_data = NULL; goto again; } diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index b140ec0a71a8..f85595136ef8 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -1857,14 +1857,20 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data) goto out; u16 boff = le16_to_cpu(rsp->SecurityBufferOffset); + u16 blen = le16_to_cpu(rsp->SecurityBufferLength); if (offsetof(struct smb2_sess_setup_rsp, Buffer) != boff) { cifs_dbg(VFS, "Invalid security buffer offset %d\n", boff); rc = smb_EIO1(smb_eio_trace_sess_buf_off, boff); goto out; } - rc = decode_ntlmssp_challenge(rsp->Buffer, - le16_to_cpu(rsp->SecurityBufferLength), ses); + if ((u32)boff + blen > sess_data->iov[0].iov_len) { + cifs_dbg(VFS, "%s: invalid security buffer length: %u\n", + __func__, blen); + rc = -EINVAL; + goto out; + } + rc = decode_ntlmssp_challenge(rsp->Buffer, blen, ses); if (rc) goto out; @@ -2104,8 +2110,6 @@ SMB2_logoff(const unsigned int xid, struct cifs_ses *ses) return rc; } -#define MAX_SHARENAME_LENGTH (255 /* server */ + 80 /* share */ + 1 /* NULL */) - /* These are similar values to what Windows uses */ static inline void init_copy_chunk_defaults(struct cifs_tcon *tcon) { @@ -2135,7 +2139,15 @@ int SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree, if (!server || !tree) return smb_EIO(smb_eio_trace_null_pointers); - unc_path = kmalloc(MAX_SHARENAME_LENGTH * 2, GFP_KERNEL); + total_len = strlen(tree); + if (total_len > MAX_TREE_SIZE) { + cifs_server_dbg(VFS | ONCE, + "%s: tree name exceeded %u characters\n", + __func__, MAX_TREE_SIZE); + return -EINVAL; + } + + unc_path = kmalloc((total_len + 1) * sizeof(*unc_path), GFP_KERNEL); if (unc_path == NULL) return -ENOMEM; @@ -4936,6 +4948,7 @@ SMB2_read(const unsigned int xid, struct cifs_io_parms *io_parms, struct kvec iov[1]; struct kvec rsp_iov; unsigned int total_len; + u16 data_offset; int flags = CIFS_LOG_ERROR; struct cifs_ses *ses = io_parms->tcon->ses; @@ -4995,8 +5008,29 @@ SMB2_read(const unsigned int xid, struct cifs_io_parms *io_parms, *nbytes = 0; } + data_offset = rsp->DataOffset; + if (data_offset < io_parms->server->vals->read_rsp_size) { + /* + * win2k8 sometimes sends an offset of 0 when the read is beyond + * the EOF. Treat it as if the data starts just after the header, + * as handle_read_data() does. + */ + cifs_dbg(FYI, "data offset %u inside read response header\n", + data_offset); + data_offset = io_parms->server->vals->read_rsp_size; + } + + /* The data area must lie within the response actually received */ + if (*nbytes && (size_t)data_offset + *nbytes > rsp_iov.iov_len) { + cifs_dbg(FYI, "bad data offset %u length %u for response of %zu\n", + data_offset, *nbytes, rsp_iov.iov_len); + rc = smb_EIO2(smb_eio_trace_read_bad_offset, + data_offset, *nbytes); + *nbytes = 0; + } + if (*buf) { - memcpy(*buf, (char *)rsp + rsp->DataOffset, *nbytes); + memcpy(*buf, (char *)rsp + data_offset, *nbytes); free_rsp_buf(resp_buftype, rsp_iov.iov_base); } else if (resp_buftype != CIFS_NO_BUFFER) { *buf = rsp_iov.iov_base; diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index 2708bedb49ab..5331ce6dc8b3 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -44,6 +44,7 @@ EM(smb_eio_trace_ioctl_no_rsp, "ioctl_no_rsp") \ EM(smb_eio_trace_ioctl_out_off, "ioctl_out_off") \ EM(smb_eio_trace_lock_bcc_too_small, "lock_bcc_too_small") \ + EM(smb_eio_trace_lock_data_area, "lock_data_area") \ EM(smb_eio_trace_lock_data_too_small, "lock_data_too_small") \ EM(smb_eio_trace_malformed_ksid_key, "malformed_ksid_key") \ EM(smb_eio_trace_malformed_sid_key, "malformed_sid_key") \ diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index 60699d2799fb..d0d0159c1061 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -1345,6 +1345,8 @@ cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid) #endif length = cifs_read_iter_from_socket(server, &rdata->subreq.io_iter, data_len); + if (length < 0) + return length; if (length > 0) rdata->got_bytes += length; server->total_read += length;