diff options
Diffstat (limited to 'fs/smb')
| -rw-r--r-- | fs/smb/client/cifs_swn.c | 6 | ||||
| -rw-r--r-- | fs/smb/client/cifsacl.c | 3 | ||||
| -rw-r--r-- | fs/smb/client/cifsfs.c | 2 | ||||
| -rw-r--r-- | fs/smb/client/cifssmb.c | 12 | ||||
| -rw-r--r-- | fs/smb/client/file.c | 37 | ||||
| -rw-r--r-- | fs/smb/client/fscache.c | 6 | ||||
| -rw-r--r-- | fs/smb/client/inode.c | 27 | ||||
| -rw-r--r-- | fs/smb/client/misc.c | 51 | ||||
| -rw-r--r-- | fs/smb/client/sess.c | 2 | ||||
| -rw-r--r-- | fs/smb/client/smb1maperror.c | 6 | ||||
| -rw-r--r-- | fs/smb/client/smb1transport.c | 28 | ||||
| -rw-r--r-- | fs/smb/client/smb2ops.c | 2 | ||||
| -rw-r--r-- | fs/smb/common/compress/compress.c | 11 | ||||
| -rw-r--r-- | fs/smb/common/compress/compress.h | 3 | ||||
| -rw-r--r-- | fs/smb/server/compress.c | 14 | ||||
| -rw-r--r-- | fs/smb/server/connection.c | 6 | ||||
| -rw-r--r-- | fs/smb/server/connection.h | 14 | ||||
| -rw-r--r-- | fs/smb/server/smb2pdu.c | 14 | ||||
| -rw-r--r-- | fs/smb/server/smb_common.c | 37 | ||||
| -rw-r--r-- | fs/smb/server/vfs_cache.c | 2 |
20 files changed, 161 insertions, 122 deletions
diff --git a/fs/smb/client/cifs_swn.c b/fs/smb/client/cifs_swn.c index 9951817d0d7f..fe10719e627e 100644 --- a/fs/smb/client/cifs_swn.c +++ b/fs/smb/client/cifs_swn.c @@ -425,7 +425,7 @@ static struct cifs_swn_reg *cifs_find_swn_reg(struct cifs_tcon *tcon) /* * Get a registration for the tcon's server and share name, allocating a new one if it does not - * exists + * exist. */ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon) { @@ -443,7 +443,7 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon) goto unlock; } - reg = kmalloc_obj(struct cifs_swn_reg, GFP_ATOMIC); + reg = kmalloc_obj(struct cifs_swn_reg, GFP_KERNEL); if (reg == NULL) { ret = -ENOMEM; goto fail_unlock; @@ -451,7 +451,7 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon) kref_init(®->ref_count); - reg->id = idr_alloc(&cifs_swnreg_idr, reg, 1, 0, GFP_ATOMIC); + reg->id = idr_alloc(&cifs_swnreg_idr, reg, 1, 0, GFP_KERNEL); if (reg->id < 0) { cifs_dbg(FYI, "%s: failed to allocate registration id\n", __func__); ret = reg->id; diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 9424281a7674..12005f46307d 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -68,6 +68,9 @@ cifs_idmap_key_instantiate(struct key *key, struct key_preparsed_payload *prep) { char *payload; + if (prep->datalen > U16_MAX) + return -EINVAL; + /* * If the payload is less than or equal to the size of a pointer, then * an allocation here is wasteful. Just copy the data directly to the diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 1788d93a2522..a1dacc7d8f74 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -692,6 +692,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root) seq_puts(s, ",seal"); else if (tcon->ses->server->ignore_signature) seq_puts(s, ",signloosely"); + if (cifs_sb->ctx->compress) + seq_puts(s, ",compress"); if (tcon->nocase) seq_puts(s, ",nocase"); if (tcon->nodelete) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 40162d5554ea..1f77512252e7 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1681,8 +1681,10 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms, pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid >> 16)); /* tcon and ses pointer are checked in smb_init */ - if (tcon->ses->server == NULL) + if (!tcon->ses->server) { + cifs_small_buf_release(pSMB); return -ECONNABORTED; + } pSMB->AndXCommand = 0xFF; /* none */ pSMB->Fid = netfid; @@ -1796,8 +1798,10 @@ CIFSSMBWrite(const unsigned int xid, struct cifs_io_parms *io_parms, pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid >> 16)); /* tcon and ses pointer are checked in smb_init */ - if (tcon->ses->server == NULL) + if (!tcon->ses->server) { + cifs_buf_release(pSMB); return -ECONNABORTED; + } pSMB->AndXCommand = 0xFF; /* none */ pSMB->Fid = netfid; @@ -2077,8 +2081,10 @@ CIFSSMBWrite2(const unsigned int xid, struct cifs_io_parms *io_parms, pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid >> 16)); /* tcon and ses pointer are checked in smb_init */ - if (tcon->ses->server == NULL) + if (!tcon->ses->server) { + cifs_small_buf_release(pSMB); return -ECONNABORTED; + } pSMB->AndXCommand = 0xFF; /* none */ pSMB->Fid = netfid; diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index b279a44be729..ac89c1ba56b1 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -915,6 +915,14 @@ void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, cifs_set_oplock_level(cifsi, 0); } + if (OPEN_FMODE(cifs_file->f_flags) & FMODE_WRITE) { + /* Stamp while open_file_lock is held; covers all close paths + * including background I/O. Pairs with smp_load_acquire() in + * is_size_safe_to_change(). + */ + smp_store_release(&cifsi->time_last_write, jiffies); + } + spin_unlock(&cifsi->open_file_lock); spin_unlock(&tcon->open_file_lock); @@ -1429,15 +1437,6 @@ void smb2_deferred_work_close(struct work_struct *work) cifs_del_deferred_close(cfile); cfile->deferred_close_scheduled = false; spin_unlock(&cinode->deferred_lock); - /* - * Refresh time_last_write immediately before the actual server close - * so the protection window is anchored to the real close time, not - * the earlier userspace close time stored by cifs_close(). - */ - if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&cinode->time_last_write, jiffies); - } _cifsFileInfo_put(cfile, true, false); } @@ -1467,10 +1466,6 @@ int cifs_close(struct inode *inode, struct file *file) if (file->private_data != NULL) { cfile = file->private_data; file->private_data = NULL; - if (file->f_mode & FMODE_WRITE) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&cinode->time_last_write, jiffies); - } dclose = kmalloc_obj(struct cifs_deferred_close); if ((cfile->status_file_deleted == false) && (smb2_can_defer_close(inode, dclose))) { @@ -3276,13 +3271,13 @@ bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file, * No writable handles open. Check whether we are within the attribute * cache validity window of a recent local modification. * - * For the close() path: cifs_close() calls smp_store_release() on - * time_last_write before _cifsFileInfo_put() removes the handle under - * open_file_lock. That spin_unlock() is a store-release that pairs - * with the spin_lock() (load-acquire) in is_inode_writable() above, - * so if is_inode_writable() returned false the smp_load_acquire() - * below is guaranteed to observe any time_last_write update from a - * concurrent close(). + * For the close() path: _cifsFileInfo_put() stamps time_last_write + * (via smp_store_release()) before releasing open_file_lock. That + * spin_unlock() is a store-release that pairs with the spin_lock() + * (load-acquire) in is_inode_writable() above, so if + * is_inode_writable() returned false the smp_load_acquire() below is + * guaranteed to observe any time_last_write update from a concurrent + * close(), covering all close paths including background I/O. * * For the setattr/truncate paths: those callers use smp_store_release() * directly; the smp_load_acquire() below pairs with that store. There @@ -3297,7 +3292,7 @@ bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file, * jiffies is still close to INITIAL_JIFFIES on 32-bit systems. */ if (from_readdir) { - /* Pairs with smp_store_release() at close and truncate sites. */ + /* Pairs with smp_store_release() in _cifsFileInfo_put() and setattr. */ tlw = smp_load_acquire(&cifsInode->time_last_write); if (tlw && time_before(jiffies, tlw + cifs_sb->ctx->acregmax)) return false; diff --git a/fs/smb/client/fscache.c b/fs/smb/client/fscache.c index 01424a5cdb99..9f66bbcd3801 100644 --- a/fs/smb/client/fscache.c +++ b/fs/smb/client/fscache.c @@ -38,7 +38,6 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon) struct TCP_Server_Info *server = tcon->ses->server; struct fscache_volume *vcookie; const struct sockaddr *sa = (struct sockaddr *)&server->dstaddr; - size_t slen, i; char *sharename; char *key; int ret = -ENOMEM; @@ -73,10 +72,7 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon) return PTR_ERR(sharename); } - slen = strlen(sharename); - for (i = 0; i < slen; i++) - if (sharename[i] == '/') - sharename[i] = ';'; + strreplace(sharename, '/', ';'); key = kasprintf(GFP_KERNEL, "cifs,%pISpc,%s", sa, sharename); if (!key) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index b2806371bfde..0afff761aab9 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -3059,6 +3059,7 @@ void cifs_setsize(struct inode *inode, loff_t offset) inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); truncate_pagecache(inode, offset); netfs_wait_for_outstanding_io(inode); + fscache_resize_cookie(cifs_inode_cookie(inode), offset); } int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, @@ -3190,6 +3191,17 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) rc = 0; if (attrs->ia_valid & ATTR_SIZE) { + if (attrs->ia_size != i_size_read(inode)) { + /* Stamp before RPC. On failure the stamp remains: restoring a + * stale snapshot could silently erase a concurrent + * _cifsFileInfo_put() close stamp. readdir is suppressed + * until the stamp expires; stat() bypasses this via the + * from_readdir=false path in is_size_safe_to_change() and + * always returns an authoritative QUERY_INFO result. + * Pairs with smp_load_acquire() in is_size_safe_to_change(). + */ + smp_store_release(&cifsInode->time_last_write, jiffies); + } rc = cifs_file_set_size(xid, direntry, full_path, open_file, attrs->ia_size); if (rc != 0) @@ -3279,8 +3291,6 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) if ((attrs->ia_valid & ATTR_SIZE) && attrs->ia_size != i_size_read(inode)) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&cifsInode->time_last_write, jiffies); truncate_setsize(inode, attrs->ia_size); netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true); fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size); @@ -3370,6 +3380,17 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) } if (attrs->ia_valid & ATTR_SIZE) { + if (attrs->ia_size != i_size_read(inode)) { + /* Stamp before RPC. On failure the stamp remains: restoring a + * stale snapshot could silently erase a concurrent + * _cifsFileInfo_put() close stamp. readdir is suppressed + * until the stamp expires; stat() bypasses this via the + * from_readdir=false path in is_size_safe_to_change() and + * always returns an authoritative QUERY_INFO result. + * Pairs with smp_load_acquire() in is_size_safe_to_change(). + */ + smp_store_release(&cifsInode->time_last_write, jiffies); + } rc = cifs_file_set_size(xid, direntry, full_path, cfile, attrs->ia_size); if (rc != 0) @@ -3482,8 +3503,6 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) if ((attrs->ia_valid & ATTR_SIZE) && attrs->ia_size != i_size_read(inode)) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&cifsInode->time_last_write, jiffies); truncate_setsize(inode, attrs->ia_size); netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true); fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size); diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index 6edebc0807ea..46e1382e8e04 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -525,24 +525,11 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode) } spin_unlock(&cifs_inode->open_file_lock); - if (failed_cfile) { - if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write, - jiffies); - } + if (failed_cfile) _cifsFileInfo_put(failed_cfile, false, false); - } list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) { - struct cifsFileInfo *cfile = tmp_list->cfile; - - if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&CIFS_I(d_inode(cfile->dentry))->time_last_write, - jiffies); - } - _cifsFileInfo_put(cfile, false, false); + _cifsFileInfo_put(tmp_list->cfile, false, false); list_del(&tmp_list->list); kfree(tmp_list); } @@ -576,24 +563,11 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon) } spin_unlock(&tcon->open_file_lock); - if (failed_cfile) { - if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write, - jiffies); - } + if (failed_cfile) _cifsFileInfo_put(failed_cfile, true, false); - } list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) { - struct cifsFileInfo *cfile = tmp_list->cfile; - - if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&CIFS_I(d_inode(cfile->dentry))->time_last_write, - jiffies); - } - _cifsFileInfo_put(cfile, true, false); + _cifsFileInfo_put(tmp_list->cfile, true, false); list_del(&tmp_list->list); kfree(tmp_list); } @@ -663,24 +637,11 @@ void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon, } spin_unlock(&tcon->open_file_lock); - if (failed_cfile) { - if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write, - jiffies); - } + if (failed_cfile) _cifsFileInfo_put(failed_cfile, true, false); - } list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) { - struct cifsFileInfo *cfile = tmp_list->cfile; - - if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) { - /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ - smp_store_release(&CIFS_I(d_inode(cfile->dentry))->time_last_write, - jiffies); - } - _cifsFileInfo_put(cfile, true, false); + _cifsFileInfo_put(tmp_list->cfile, true, false); list_del(&tmp_list->list); kfree(tmp_list); } diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index de2012cc9cf3..7cf7dd104f7c 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -233,9 +233,9 @@ int cifs_try_adding_channels(struct cifs_ses *ses) cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n", &iface->sockaddr, rc); - kref_put(&iface->refcount, release_iface); /* failure to add chan should increase weight */ iface->weight_fulfilled++; + kref_put(&iface->refcount, release_iface); continue; } diff --git a/fs/smb/client/smb1maperror.c b/fs/smb/client/smb1maperror.c index ab3d09613c91..395299f9121b 100644 --- a/fs/smb/client/smb1maperror.c +++ b/fs/smb/client/smb1maperror.c @@ -234,11 +234,7 @@ int __init smb1_init_maperror(void) if (rc) return rc; - rc = mapping_table_ERRSRV_is_sorted(); - if (rc) - return rc; - - return rc; + return mapping_table_ERRSRV_is_sorted(); } #if IS_ENABLED(CONFIG_SMB1_KUNIT_TESTS) diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c index 53abb29fe71b..966f2cf83a51 100644 --- a/fs/smb/client/smb1transport.c +++ b/fs/smb/client/smb1transport.c @@ -260,9 +260,23 @@ SendReceive(const unsigned int xid, struct cifs_ses *ses, goto out; if (out_buf) { - *pbytes_returned = resp_iov.iov_len; - if (resp_iov.iov_len) - memcpy(out_buf, resp_iov.iov_base, resp_iov.iov_len); + /* Use smbCalcSize() for both single- and multi-part T2 responses, + * both here and in coalesce_t2(). + */ + unsigned int copy_len; + if (WARN_ON_ONCE(!resp_iov.iov_base)) { + rc = -EIO; + goto out; + } + copy_len = smbCalcSize(resp_iov.iov_base); + if (copy_len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) { + cifs_dbg(VFS, "response size %u exceeds buffer\n", + copy_len); + rc = -ENOBUFS; + goto out; + } + *pbytes_returned = copy_len; + memcpy(out_buf, resp_iov.iov_base, copy_len); } out: @@ -386,11 +400,13 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr, unsigned int *pdu_len) } put_bcc(byte_count, target_hdr); - byte_count = *pdu_len; - byte_count += total_in_src; + /* use smbCalcSize() rather than *pdu_len: the demux loop resets + * *pdu_len to each secondary's pdu_length, making it unreliable. + */ + byte_count = smbCalcSize(target_hdr); /* don't allow buffer to overflow */ if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) { - cifs_dbg(FYI, "coalesced BCC exceeds buffer size (%u)\n", + cifs_dbg(FYI, "coalesced size exceeds buffer size (%u)\n", byte_count); return -ENOBUFS; } diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index cbd51a08e97e..192649fec25d 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4745,10 +4745,10 @@ smb3_init_transform_rq(struct TCP_Server_Info *server, int num_rqst, size_t cur_size = 0; rc = netfs_alloc_folioq_buffer(NULL, &buffer, &cur_size, size, GFP_NOFS); + new->rq_buffer = buffer; if (rc < 0) goto err_free; - new->rq_buffer = buffer; iov_iter_folio_queue(&new->rq_iter, ITER_SOURCE, buffer, 0, 0, size); diff --git a/fs/smb/common/compress/compress.c b/fs/smb/common/compress/compress.c index b07a317597a4..a4123c8f1c0a 100644 --- a/fs/smb/common/compress/compress.c +++ b/fs/smb/common/compress/compress.c @@ -95,6 +95,7 @@ static int smb_decompress_lz77_payload(const u8 **src, u32 *slen, u8 **dst, } static int smb_decompress_chained(__le16 alg, bool allow_chained, + bool allow_pattern, const struct smb2_compression_hdr *hdr, u32 slen, void *dst, u32 dlen) { @@ -143,6 +144,8 @@ static int smb_decompress_chained(__le16 alg, bool allow_chained, rc = smb_decompress_none(&src, &remaining, &out, &out_remaining, len); } else if (payload_alg == SMB3_COMPRESS_PATTERN) { + if (!allow_pattern) + return -EINVAL; rc = smb_decompress_pattern(&src, &remaining, &out, &out_remaining, len); } else if (payload_alg == alg && alg == SMB3_COMPRESS_LZ77) { @@ -185,6 +188,7 @@ static int smb_decompress_unchained(__le16 alg, * smb_compression_decompress() - decode an SMB2 compression transform * @alg: negotiated general-purpose compression algorithm * @allow_chained: whether chained transforms were negotiated + * @allow_pattern: whether Pattern_V1 payloads were negotiated * @src: transform header followed by compressed payload data * @slen: total number of bytes available at @src * @dst: output buffer for the reconstructed SMB2 message @@ -197,7 +201,8 @@ static int smb_decompress_unchained(__le16 alg, * Return: 0 on success, otherwise a negative errno. */ int smb_compression_decompress(__le16 alg, bool allow_chained, - const void *src, u32 slen, void *dst, u32 dlen) + bool allow_pattern, const void *src, u32 slen, + void *dst, u32 dlen) { const struct smb2_compression_hdr *hdr = src; @@ -207,8 +212,8 @@ int smb_compression_decompress(__le16 alg, bool allow_chained, return -EINVAL; if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) - return smb_decompress_chained(alg, allow_chained, hdr, slen, - dst, dlen); + return smb_decompress_chained(alg, allow_chained, allow_pattern, + hdr, slen, dst, dlen); if (hdr->Flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)) return -EINVAL; diff --git a/fs/smb/common/compress/compress.h b/fs/smb/common/compress/compress.h index 7ace3bf4b664..d6916669f887 100644 --- a/fs/smb/common/compress/compress.h +++ b/fs/smb/common/compress/compress.h @@ -20,7 +20,8 @@ static __always_inline bool smb_compress_alg_valid(__le16 alg, bool valid_none) } int smb_compression_decompress(__le16 alg, bool allow_chained, - const void *src, u32 slen, void *dst, u32 dlen); + bool allow_pattern, const void *src, u32 slen, + void *dst, u32 dlen); int smb_compression_compress_chained(__le16 alg, bool allow_pattern, const void *src, u32 slen, void *dst, u32 *dlen); diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c index 95e48fa6b448..01d1771ff663 100644 --- a/fs/smb/server/compress.c +++ b/fs/smb/server/compress.c @@ -46,16 +46,25 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn) return -EINVAL; orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize); + /* + * For chained transforms the top-level header is only eight bytes; the + * Flags field overlays the first payload header. Reject unknown Flags + * and unnegotiated chained mode before allocating the output buffer. + */ if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) { + if (!conn->compress_chained) + return -EINVAL; out_size = orig_size; - } else { + } else if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)) { offset = le32_to_cpu(hdr->Offset); if (offset > pdu_size - sizeof(*hdr) || check_add_overflow(orig_size, offset, &out_size)) return -EINVAL; + } else { + return -EINVAL; } - max_allowed_pdu_size = SMB3_MAX_MSGSIZE + conn->vals->max_write_size; + max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn); if (out_size < sizeof(struct smb2_pdu) || out_size > max_allowed_pdu_size || out_size > MAX_STREAM_PROT_LEN) @@ -69,6 +78,7 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn) *(__be32 *)out = cpu_to_be32(out_size); rc = smb_compression_decompress(conn->compress_algorithm, conn->compress_chained, + conn->compress_pattern, buf, pdu_size, out + 4, out_size); if (rc) { kvfree(out); diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c index dee8e4aced99..ef6f202f4024 100644 --- a/fs/smb/server/connection.c +++ b/fs/smb/server/connection.c @@ -488,11 +488,7 @@ recheck: pdu_size = get_rfc1002_len(hdr_buf); ksmbd_debug(CONN, "RFC1002 header %u bytes\n", pdu_size); - if (ksmbd_conn_good(conn)) - max_allowed_pdu_size = - SMB3_MAX_MSGSIZE + conn->vals->max_write_size; - else - max_allowed_pdu_size = SMB3_MAX_MSGSIZE; + max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn); if (pdu_size > max_allowed_pdu_size) { pr_err_ratelimited("PDU length(%u) exceeded maximum allowed pdu size(%u) on connection(%d)\n", diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h index ec75633b7da0..0e4ebfac5558 100644 --- a/fs/smb/server/connection.h +++ b/fs/smb/server/connection.h @@ -200,11 +200,25 @@ void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn); * This is a hack. We will move status to a proper place once we land * a multi-sessions support. */ +static inline bool ksmbd_conn_new(struct ksmbd_conn *conn) +{ + return READ_ONCE(conn->status) == KSMBD_SESS_NEW; +} + static inline bool ksmbd_conn_good(struct ksmbd_conn *conn) { return READ_ONCE(conn->status) == KSMBD_SESS_GOOD; } +static inline unsigned int +ksmbd_max_allowed_pdu_size(struct ksmbd_conn *conn) +{ + if (ksmbd_conn_good(conn)) + return SMB3_MAX_MSGSIZE + conn->vals->max_write_size; + + return SMB3_MAX_MSGSIZE; +} + static inline bool ksmbd_conn_need_negotiate(struct ksmbd_conn *conn) { return READ_ONCE(conn->status) == KSMBD_SESS_NEED_NEGOTIATE; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index c1ba5e01aa7f..76f63f9adc72 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -1325,6 +1325,8 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn, * smb2_handle_negotiate() - handler for smb2 negotiate command * @work: smb work containing smb request buffer * + * The caller holds conn->srv_mutex. + * * Return: 0 */ int smb2_handle_negotiate(struct ksmbd_work *work) @@ -1338,13 +1340,6 @@ int smb2_handle_negotiate(struct ksmbd_work *work) ksmbd_debug(SMB, "Received negotiate request\n"); conn->need_neg = false; - if (ksmbd_conn_good(conn)) { - pr_err("conn->tcp_status is already in CifsGood State\n"); - work->send_no_response = 1; - return rc; - } - - ksmbd_conn_lock(conn); smb2_buf_len = get_rfc1002_len(work->request_buf); smb2_neg_size = offsetof(struct smb2_negotiate_req, Dialects); if (smb2_neg_size > smb2_buf_len) { @@ -1495,7 +1490,6 @@ int smb2_handle_negotiate(struct ksmbd_work *work) ksmbd_conn_set_need_setup(conn); err_out: - ksmbd_conn_unlock(conn); if (rc) rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES; @@ -1975,7 +1969,7 @@ int smb2_sess_setup(struct ksmbd_work *work) goto out_err; } - if (strncmp(conn->ClientGUID, sess->ClientGUID, + if (memcmp(conn->ClientGUID, sess->ClientGUID, SMB2_CLIENT_GUID_SIZE)) { rc = -ENOENT; goto out_err; @@ -8696,7 +8690,7 @@ static int fsctl_validate_negotiate_info(struct ksmbd_conn *conn, goto err_out; } - if (strncmp(neg_req->Guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE)) { + if (memcmp(neg_req->Guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE)) { ret = -EINVAL; goto err_out; } diff --git a/fs/smb/server/smb_common.c b/fs/smb/server/smb_common.c index 7de73223189a..080fbc9eb470 100644 --- a/fs/smb/server/smb_common.c +++ b/fs/smb/server/smb_common.c @@ -608,23 +608,46 @@ int ksmbd_smb_negotiate_common(struct ksmbd_work *work, unsigned int command) struct ksmbd_conn *conn = work->conn; int ret; - conn->dialect = - ksmbd_negotiate_smb_dialect(work->request_buf); - ksmbd_debug(SMB, "conn->dialect 0x%x\n", conn->dialect); - if (command == SMB2_NEGOTIATE_HE) { + /* + * An SMB2 NEGOTIATE is valid for a new connection, or after an + * SMB1 multi-protocol negotiate has selected SMB2. Do not allow + * a second SMB2 NEGOTIATE to replace connection-wide state + * while a session setup is pending. KSMBD_SESS_NEED_RECONNECT + * is a transient session state and does not restart transport + * negotiation. + */ + ksmbd_conn_lock(conn); + if (!ksmbd_conn_new(conn) && + !ksmbd_conn_need_negotiate(conn)) { + work->send_no_response = 1; + ksmbd_conn_set_exiting(conn); + ksmbd_conn_unlock(conn); + return 0; + } + + conn->dialect = + ksmbd_negotiate_smb_dialect(work->request_buf); + ksmbd_debug(SMB, "conn->dialect 0x%x\n", conn->dialect); ret = smb2_handle_negotiate(work); + ksmbd_conn_unlock(conn); return ret; } if (command == SMB_COM_NEGOTIATE) { + ksmbd_conn_lock(conn); + conn->dialect = + ksmbd_negotiate_smb_dialect(work->request_buf); + ksmbd_debug(SMB, "conn->dialect 0x%x\n", conn->dialect); if (__smb2_negotiate(conn)) { init_smb3_11_server(conn); - init_smb2_neg_rsp(work); + ret = init_smb2_neg_rsp(work); ksmbd_debug(SMB, "Upgrade to SMB2 negotiation\n"); - return 0; + } else { + ret = smb_handle_negotiate(work); } - return smb_handle_negotiate(work); + ksmbd_conn_unlock(conn); + return ret; } pr_err("Unknown SMB negotiation command: %u\n", command); diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index d95c405eab11..a141025581af 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -697,6 +697,8 @@ int ksmbd_close_fd(struct ksmbd_work *work, u64 id) fp = NULL; else { fp->f_state = FP_CLOSED; + idr_remove(ft->idr, id); + fp->volatile_id = KSMBD_NO_FID; closed = true; if (!atomic_dec_and_test(&fp->refcount)) fp = NULL; |
