diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c index 4ed4f55a0bb7..3761d3ad6088 100644 --- a/fs/smb/client/cifs_debug.c +++ b/fs/smb/client/cifs_debug.c @@ -754,7 +754,7 @@ static ssize_t cifs_stats_proc_write(struct file *file, atomic_set(&server->smb2slowcmd[i], 0); server->time_per_cmd[i] = 0; server->slowest_cmd[i] = 0; - server->fastest_cmd[0] = 0; + server->fastest_cmd[i] = 0; } #endif /* CONFIG_CIFS_STATS2 */ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { diff --git a/fs/smb/client/cifsencrypt.c b/fs/smb/client/cifsencrypt.c index 34804e9842a8..71a2a59123f5 100644 --- a/fs/smb/client/cifsencrypt.c +++ b/fs/smb/client/cifsencrypt.c @@ -249,12 +249,13 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, E_md4hash(ses->password, nt_hash, nls_cp); hmac_md5_init_usingrawkey(&hmac_ctx, nt_hash, CIFS_NTHASH_SIZE); + memzero_explicit(nt_hash, sizeof(nt_hash)); /* convert ses->user_name to unicode */ len = ses->user_name ? strlen(ses->user_name) : 0; user = kmalloc(2 + (len * 2), GFP_KERNEL); if (user == NULL) - return -ENOMEM; + goto out_nomem; if (len) { len = cifs_strtoUTF16(user, ses->user_name, len, nls_cp); @@ -272,7 +273,7 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, domain = kmalloc(2 + (len * 2), GFP_KERNEL); if (domain == NULL) - return -ENOMEM; + goto out_nomem; len = cifs_strtoUTF16((__le16 *)domain, ses->domainName, len, nls_cp); @@ -284,7 +285,7 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, server = kmalloc(2 + (len * 2), GFP_KERNEL); if (server == NULL) - return -ENOMEM; + goto out_nomem; len = cifs_strtoUTF16((__le16 *)server, ses->ip_addr, len, nls_cp); hmac_md5_update(&hmac_ctx, (const u8 *)server, 2 * len); @@ -293,6 +294,10 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, hmac_md5_final(&hmac_ctx, ntlmv2_hash); return 0; + +out_nomem: + memzero_explicit(&hmac_ctx, sizeof(hmac_ctx)); + return -ENOMEM; } static void CalcNTLMv2_response(const struct cifs_ses *ses, char *ntlmv2_hash) @@ -463,6 +468,7 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp) rc = 0; unlock: cifs_server_unlock(ses->server); + memzero_explicit(ntlmv2_hash, sizeof(ntlmv2_hash)); setup_ntlmv2_rsp_ret: kfree_sensitive(tiblob); diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index a1dacc7d8f74..7ecd70efdfea 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1413,8 +1413,19 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, */ lock_two_nondirectories(target_inode, src_inode); - if (len == 0) - len = src_inode->i_size - off; + if (len == 0) { + loff_t src_size = i_size_read(src_inode); + + if (off > src_size) { + rc = -EINVAL; + goto unlock; + } + len = src_size - off; + if (!len) { + rc = 0; + goto unlock; + } + } cifs_dbg(FYI, "clone range\n"); @@ -1466,11 +1477,7 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, if (target_tcon->ses->server->ops->duplicate_extents) { rc = target_tcon->ses->server->ops->duplicate_extents(xid, smb_file_src, smb_file_target, off, len, destoff); - if (rc == 0 && new_size > i_size) { - truncate_setsize(target_inode, new_size); - fscache_resize_cookie(cifs_inode_cookie(target_inode), - new_size); - } else if (rc == -EOPNOTSUPP) { + if (rc == -EOPNOTSUPP) { /* * copy_file_range syscall man page indicates EINVAL * is returned e.g when "fd_in and fd_out refer to the diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 287c632392c4..0c85daa8386e 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -147,6 +147,7 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, struct file *src_file, long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg); void cifs_setsize(struct inode *inode, loff_t offset); +void cifs_resize_file_locked(struct inode *inode, loff_t offset); struct fs_context; struct smb3_fs_context; diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 1f77512252e7..f5aad5f61dce 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -615,6 +615,11 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, tcon->tid = smb_buffer_response->Tid; bcc_ptr = pByteArea(smb_buffer_response); bytes_left = get_bcc(smb_buffer_response); + if (bytes_left < 2) { + rc = smb_EIO2(smb_eio_trace_tcon_bcc_too_small, + bytes_left, 2); + goto out; + } length = strnlen(bcc_ptr, bytes_left - 2); if (smb_buffer->Flags2 & SMBFLG2_UNICODE) is_unicode = true; @@ -670,6 +675,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, reset_cifs_unix_caps(xid, tcon, NULL, NULL); } } +out: cifs_buf_release(smb_buffer); return rc; } diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index ba749ec25a59..bcd7f1ae99ba 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1143,7 +1143,7 @@ clean_demultiplex_info(struct TCP_Server_Info *server) put_net(cifs_net_ns(server)); kfree(server->leaf_fullpath); kfree(server->hostname); - kfree(server); + kfree_sensitive(server); length = atomic_dec_return(&tcpSesAllocCount); if (length > 0) @@ -4003,9 +4003,6 @@ cifs_umount(struct cifs_sb_info *cifs_sb) } spin_unlock(&cifs_sb->tlink_tree_lock); - flush_workqueue(serverclose_wq); - flush_workqueue(fileinfo_put_wq); - kfree(cifs_sb->prepath); call_rcu(&cifs_sb->rcu, delayed_free); } diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 8cd93cd2f00f..86dba25b7a5a 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -122,6 +122,8 @@ static inline void free_tgts(struct cache_entry *ce) kfree(t->name); kfree(t); } + + WRITE_ONCE(ce->tgthint, NULL); } static inline void flush_cache_ent(struct cache_entry *ce) @@ -869,13 +871,22 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl goto out_free_path; } - if (ref) - rc = setup_referral(path, ce, ref, get_tgt_name(ce)); - else + if (ref) { + char *target = get_tgt_name(ce); + + if (IS_ERR(target)) { + rc = PTR_ERR(target); + goto out_unlock; + } + rc = setup_referral(path, ce, ref, target); + } else { rc = 0; + } + if (!rc && tgt_list) rc = get_targets(ce, tgt_list); +out_unlock: up_read(&htable_rw_lock); out_free_path: @@ -915,10 +926,17 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref, goto out_unlock; } - if (ref) - rc = setup_referral(path, ce, ref, get_tgt_name(ce)); - else + if (ref) { + char *target = get_tgt_name(ce); + + if (IS_ERR(target)) { + rc = PTR_ERR(target); + goto out_unlock; + } + rc = setup_referral(path, ce, ref, target); + } else { rc = 0; + } if (!rc && tgt_list) rc = get_targets(ce, tgt_list); @@ -959,7 +977,8 @@ void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt t = READ_ONCE(ce->tgthint); - if (unlikely(!strcasecmp(it->it_name, t->name))) + /* Check 't' in case ce->tgthint was cleared by free_tgts() */ + if (t && unlikely(!strcasecmp(it->it_name, t->name))) goto out_unlock; list_for_each_entry(t, &ce->tlist, list) { diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 7803bd5bd01f..6fa6d48fdfd3 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -115,11 +115,7 @@ char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *pa } if (dirsep != '/') { /* BB test paths to Windows with '/' in the midst of prepath */ - char *p; - - for (p = s; *p; p++) - if (*p == '/') - *p = dirsep; + strreplace(s, '/', dirsep); } if (dfsplen) { s -= dfsplen; @@ -1138,6 +1134,8 @@ int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, } while (unlikely(rc == -EEXIST) && ++retries < max_retries); if (rc) { + if (rc == -ENOENT) + rc = -EOPNOTSUPP; cifs_del_pending_open(&open); goto out; } diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index ac89c1ba56b1..389083f9ce00 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1016,6 +1016,7 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) if (!rc) { netfs_resize_file(&cinode->netfs, 0, true); cifs_setsize(inode, 0); + cifs_invalidate_cache(inode, 0); } } if (cfile) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 18f562ac172e..12ed8db10e00 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -1215,7 +1215,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, break; case IO_REPARSE_TAG_INTERNAL: rc = 0; - if (le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY) { + if (cifs_open_data_attrs(data) & ATTR_DIRECTORY) { cifs_create_junction_fattr(fattr, sb); goto out; } @@ -1239,7 +1239,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, */ if (rc == -EOPNOTSUPP && IS_REPARSE_TAG_NAME_SURROGATE(data->reparse.tag) && - (le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY)) { + (cifs_open_data_attrs(data) & ATTR_DIRECTORY)) { rc = 0; cifs_create_junction_fattr(fattr, sb); goto out; @@ -1257,13 +1257,14 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, } if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc) { - bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY; + bool directory = cifs_open_data_attrs(data) & ATTR_DIRECTORY; + rc = smb2_fix_symlink_target_type(&data->symlink_target, directory, cifs_sb); } break; } - if (tcon->posix_extensions) + if (data->contains_posix_file_info) smb311_posix_info_to_fattr(fattr, data, sb); else cifs_open_info_to_fattr(fattr, data, sb); @@ -2655,11 +2656,8 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, if (d_really_is_positive(target_dentry)) { if (!rc) { struct inode *inode = d_inode(target_dentry); - /* - * Samba and ksmbd servers allow renaming a target - * directory that is open, so make sure to update - * ->i_nlink and then mark it as delete pending. - */ + + /* Update the target link count after rename. */ if (S_ISDIR(inode->i_mode)) { drop_cached_dir_by_name(xid, tcon, to_name, cifs_sb); spin_lock(&inode->i_lock); @@ -2670,6 +2668,10 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, CIFS_I(inode)->time = 0; /* force reval */ inode_set_ctime_current(inode); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + } else { + cifs_mark_open_handles_for_deleted_file(inode, to_name); + cifs_drop_nlink(inode); + inode_set_ctime_current(inode); } } else if (rc == -EACCES || rc == -EEXIST) { /* @@ -3064,9 +3066,27 @@ void cifs_setsize(struct inode *inode, loff_t offset) inode->i_blocks = blocks; spin_unlock(&inode->i_lock); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + if (offset > old_size) + pagecache_isize_extended(inode, old_size, offset); truncate_pagecache(inode, offset); netfs_wait_for_outstanding_io(inode); - fscache_resize_cookie(cifs_inode_cookie(inode), offset); +} + +void cifs_resize_file_locked(struct inode *inode, loff_t offset) +{ + struct fscache_cookie *cookie = cifs_inode_cookie(inode); + + lockdep_assert_held_write(&inode->i_rwsem); + + netfs_resize_file(netfs_inode(inode), offset, true); + cifs_setsize(inode, offset); + + if (!cookie) + return; + + fscache_use_cookie(cookie, true); + fscache_resize_cookie(cookie, offset); + cifs_fscache_unuse_inode_cookie(inode, true); } int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, @@ -3107,6 +3127,7 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, size, false); cifs_dbg(FYI, "%s: set_file_size: rc = %d\n", __func__, rc); cifsFileInfo_put(open_file); + tcon = NULL; } } @@ -3132,10 +3153,8 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, cifs_put_tlink(tlink); set_size_out: - if (rc == 0) { - netfs_resize_file(&cifsInode->netfs, size, true); - cifs_setsize(inode, size); - } + if (rc == 0) + cifs_resize_file_locked(inode, size); return rc; } @@ -3221,9 +3240,13 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) attrs->ia_valid &= ~(ATTR_CTIME | ATTR_MTIME); } - /* skip mode change if it's just for clearing setuid/setgid */ - if (attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) - attrs->ia_valid &= ~ATTR_MODE; + /* + * This function is only called when Unix extensions are in effect, + * so the mode is always sent to and stored on the server. Do not + * skip the mode change when clearing setuid/setgid bits: dropping + * ATTR_MODE here would leave those bits set on the server after a + * write, which is a security issue. + */ args = kmalloc_obj(*args); if (args == NULL) { @@ -3296,13 +3319,6 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) if (rc) goto out; - if ((attrs->ia_valid & ATTR_SIZE) && - attrs->ia_size != i_size_read(inode)) { - 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); - } - setattr_copy(&nop_mnt_idmap, inode, attrs); mark_inode_dirty(inode); @@ -3432,8 +3448,23 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) attrs->ia_valid &= ~(ATTR_UID | ATTR_GID); } - /* skip mode change if it's just for clearing setuid/setgid */ - if (attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) + /* + * Skip the mode change if it is only being done to clear the + * setuid/setgid bits *and* the mode is emulated via the DOS + * read-only attribute (the default, non-ACL case), which cannot + * represent the setuid/setgid bits anyway. + * + * When the mode is instead stored on the server - i.e. with the + * cifsacl or modefromsid mount options (via an ACL) or with the + * SMB3.1.1 POSIX extensions - the cleared mode must be pushed to + * the server. Dropping ATTR_MODE here would leave the setuid/ + * setgid bit set on the server after a write, which is a security + * issue (the bits are not stripped as they are on local + * filesystems). + */ + if ((attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) && + !((sbflags & (CIFS_MOUNT_CIFS_ACL | CIFS_MOUNT_MODE_FROM_SID)) || + cifs_sb_master_tcon(cifs_sb)->posix_extensions)) attrs->ia_valid &= ~ATTR_MODE; if (attrs->ia_valid & ATTR_MODE) { @@ -3508,13 +3539,6 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) if (rc) goto cifs_setattr_exit; - if ((attrs->ia_valid & ATTR_SIZE) && - attrs->ia_size != i_size_read(inode)) { - 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); - } - setattr_copy(&nop_mnt_idmap, inode, attrs); mark_inode_dirty(inode); diff --git a/fs/smb/client/ioctl.c b/fs/smb/client/ioctl.c index 9fa743be3652..2f152d76be7d 100644 --- a/fs/smb/client/ioctl.c +++ b/fs/smb/client/ioctl.c @@ -133,8 +133,7 @@ static int cifs_set_compression_by_path(unsigned int xid, struct file *filep, close: server->ops->close(xid, tcon, &fid); - if (tmp_cfile) - kfree(tmp_cfile); + kfree(tmp_cfile); cifs_free_open_info(&data); out: free_dentry_path(page); diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h index 0164dc47bdfd..49efd85b1e94 100644 --- a/fs/smb/client/reparse.h +++ b/fs/smb/client/reparse.h @@ -98,15 +98,21 @@ static inline bool reparse_inode_match(struct inode *inode, timespec64_equal(&ctime, &fattr->cf_ctime); } +static inline u32 cifs_open_data_attrs(const struct cifs_open_info_data *data) +{ + if (data->contains_posix_file_info) + return le32_to_cpu(data->posix_fi.DosAttributes); + + return le32_to_cpu(data->fi.Attributes); +} + static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data) { - u32 attrs; - bool ret; + u32 attrs = cifs_open_data_attrs(data); if (data->contains_posix_file_info) { struct smb311_posix_qinfo *fi = &data->posix_fi; - attrs = le32_to_cpu(fi->DosAttributes); if (data->reparse_point) { attrs |= ATTR_REPARSE_POINT; fi->DosAttributes = cpu_to_le32(attrs); @@ -115,16 +121,13 @@ static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data) } else { struct smb2_file_all_info *fi = &data->fi; - attrs = le32_to_cpu(fi->Attributes); if (data->reparse_point) { attrs |= ATTR_REPARSE_POINT; fi->Attributes = cpu_to_le32(attrs); } } - ret = attrs & ATTR_REPARSE_POINT; - - return ret; + return attrs & ATTR_REPARSE_POINT; } bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, diff --git a/fs/smb/client/smb1encrypt.c b/fs/smb/client/smb1encrypt.c index bf10fdeeedca..c9eb68f04e7b 100644 --- a/fs/smb/client/smb1encrypt.c +++ b/fs/smb/client/smb1encrypt.c @@ -81,6 +81,7 @@ int cifs_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server, else memcpy(cifs_pdu->Signature.SecuritySignature, smb_signature, 8); + memzero_explicit(smb_signature, sizeof(smb_signature)); return rc; } @@ -126,15 +127,13 @@ int cifs_verify_signature(struct smb_rqst *rqst, rc = cifs_calc_signature(rqst, server, what_we_think_sig_should_be); cifs_server_unlock(server); - if (rc) - return rc; - -/* cifs_dump_mem("what we think it should be: ", - what_we_think_sig_should_be, 16); */ - - if (crypto_memneq(server_response_sig, what_we_think_sig_should_be, 8)) - return -EACCES; - else - return 0; + if (!rc) { + if (crypto_memneq(server_response_sig, + what_we_think_sig_should_be, 8)) + rc = -EACCES; + } + memzero_explicit(what_we_think_sig_should_be, + sizeof(what_we_think_sig_should_be)); + return rc; } diff --git a/fs/smb/client/smb1misc.c b/fs/smb/client/smb1misc.c index ba56023010d8..cdfbbff24b72 100644 --- a/fs/smb/client/smb1misc.c +++ b/fs/smb/client/smb1misc.c @@ -80,7 +80,8 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) (struct smb_com_transaction_change_notify_rsp *)buf; struct file_notify_information *pnotify; __u32 data_offset = 0; - size_t len = srv->total_read - srv->pdu_size; + /* total_read excludes the RFC1002 preamble */ + size_t len = srv->total_read; if (get_bcc(buf) > sizeof(struct file_notify_information)) { data_offset = le32_to_cpu(pSMBr->DataOffset); diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index dc5a8c1da623..3ac4126267f6 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -542,6 +542,7 @@ static int cifs_query_path_info(const unsigned int xid, data->reparse_point = false; data->adjust_tz = false; + data->unknown_nlink = false; /* * First try CIFSSMBQPathInfo() function which returns more info @@ -608,6 +609,7 @@ static int cifs_query_path_info(const unsigned int xid, fi.EASize = di->EaSize; } fi.NumberOfLinks = cpu_to_le32(1); + data->unknown_nlink = true; fi.DeletePending = 0; fi.Directory = !!(le32_to_cpu(fi.Attributes) & ATTR_DIRECTORY); cifs_buf_release(search_info.ntwrk_buf_start); @@ -630,6 +632,8 @@ static int cifs_query_path_info(const unsigned int xid, rc = SMBQueryInformation(xid, tcon, full_path, &fi, cifs_sb->local_nls, cifs_remap(cifs_sb)); data->adjust_tz = true; + if (!rc) + data->unknown_nlink = true; } else if ((rc == -EOPNOTSUPP || rc == -EINVAL) && non_unicode_wildcard) { /* Path with non-UNICODE wildcard character cannot exist. */ rc = -ENOENT; @@ -717,7 +721,7 @@ static int cifs_query_path_info(const unsigned int xid, ea->ea_value_length = cpu_to_le16(SMB2_WSL_XATTR_DEV_SIZE); memcpy(&ea->ea_data[0], SMB2_WSL_XATTR_DEV, SMB2_WSL_XATTR_NAME_LEN + 1); data->wsl.eas_len += ALIGN(sizeof(*ea) + SMB2_WSL_XATTR_NAME_LEN + 1 + - SMB2_WSL_XATTR_MODE_SIZE, 4); + SMB2_WSL_XATTR_DEV_SIZE, 4); rc = 0; } else if (rc >= 0) { /* It is an error if EA $LXDEV has wrong size. */ @@ -893,8 +897,10 @@ static int cifs_open_file(const unsigned int xid, struct cifs_open_parms *oparms else rc = CIFS_open(xid, oparms, oplock, &fi); - if (!rc && data) + if (!rc && data) { move_cifs_info_to_smb2(&data->fi, &fi); + data->unknown_nlink = true; + } return rc; } diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c index 966f2cf83a51..42e95cc1bd89 100644 --- a/fs/smb/client/smb1transport.c +++ b/fs/smb/client/smb1transport.c @@ -375,12 +375,31 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr, unsigned int *pdu_len) data_area_of_tgt = (char *)&pSMBt->hdr.Protocol + get_unaligned_le16(&pSMBt->t2_rsp.DataOffset); - /* validate target area */ data_area_of_src = (char *)&pSMBs->hdr.Protocol + get_unaligned_le16(&pSMBs->t2_rsp.DataOffset); data_area_of_tgt += total_in_tgt; + /* + * DataOffset fields are server-supplied and not validated against + * buffer bounds; check both data pointers before mutating the + * target header. + */ + if (data_area_of_tgt < (char *)target_hdr + + sizeof(struct smb_t2_rsp) + sizeof(__le16) || + data_area_of_tgt + total_in_src > + (char *)target_hdr + CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) { + cifs_dbg(VFS, "%s: target data area out of bounds\n", __func__); + return -EPROTO; + } + if (data_area_of_src < second_buf + + sizeof(struct smb_t2_rsp) + sizeof(__le16) || + data_area_of_src + total_in_src > + second_buf + smbCalcSize((struct smb_hdr *)second_buf)) { + cifs_dbg(VFS, "%s: secondary data area out of bounds\n", __func__); + return -EPROTO; + } + total_in_tgt += total_in_src; /* is the result too big for the field? */ if (total_in_tgt > USHRT_MAX) { @@ -430,10 +449,18 @@ bool cifs_check_trans2(struct mid_q_entry *mid, struct TCP_Server_Info *server, char *buf, int malformed) { - if (malformed) - return false; - if (check2ndT2(buf) <= 0) + if (malformed || check2ndT2(buf) <= 0) { + /* mid->multiRsp blocks the server buf detach in handle_mid(); + * returning false here would leak resp_buf and leave a dangling + * server->smallbuf/bigbuf after the user thread frees resp_buf. + */ + if (mid->multiRsp) { + mid->multiEnd = true; + dequeue_mid(server, mid, true); + return true; + } return false; + } mid->multiRsp = true; if (mid->resp_buf) { /* merge response - fix up 1st*/ diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index f35b6488d810..fb2fccbe8667 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -61,7 +61,10 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov) cifs_dbg(FYI, "%s: skipping unhandled error context: 0x%x\n", __func__, le32_to_cpu(p->ErrorId)); - len = ALIGN(le32_to_cpu(p->ErrorDataLength), 8); + len = le32_to_cpu(p->ErrorDataLength); + if (len > end - ((u8 *)p + sizeof(*p))) + return ERR_PTR(-EINVAL); + len = ALIGN(len, 8); if (len > end - ((u8 *)p + sizeof(*p))) return ERR_PTR(-EINVAL); diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 213bc298cdf2..98ea5c6c34af 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -22,6 +22,7 @@ #include "smb2glob.h" #include "smb2proto.h" #include "cached_dir.h" +#include "reparse.h" #include "../common/smb2status.h" #include "../common/smbfsctl.h" @@ -40,9 +41,11 @@ static struct reparse_data_buffer *reparse_buf_ptr(struct kvec *iov) buf = (struct reparse_data_buffer *)((u8 *)io + off); len = sizeof(*buf); - rdlen = le16_to_cpu(buf->ReparseDataLength); + if (count < len) + return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_rdlen, count, 0)); - if (count < len || count < rdlen + len) + rdlen = le16_to_cpu(buf->ReparseDataLength); + if (count < rdlen + len) return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_rdlen, count, rdlen)); return buf; } @@ -574,8 +577,10 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, idata->fi.Attributes = create_rsp->FileAttributes; idata->fi.AllocationSize = create_rsp->AllocationSize; idata->fi.EndOfFile = create_rsp->EndofFile; + idata->contains_posix_file_info = false; if (le32_to_cpu(idata->fi.NumberOfLinks) == 0) idata->fi.NumberOfLinks = cpu_to_le32(1); /* dummy value */ + idata->unknown_nlink = true; idata->fi.DeletePending = 0; /* successful open = not delete pending */ idata->fi.Directory = !!(le32_to_cpu(create_rsp->FileAttributes) & ATTR_DIRECTORY); @@ -596,7 +601,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, switch (cmds[i]) { case SMB2_OP_QUERY_INFO: idata = in_iov[i].iov_base; - idata->contains_posix_file_info = false; if (rc == 0 && cfile && cfile->symlink_target) { idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL); if (!idata->symlink_target) @@ -609,6 +613,8 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, le16_to_cpu(qi_rsp->OutputBufferOffset), le32_to_cpu(qi_rsp->OutputBufferLength), &rsp_iov[i + 1], sizeof(idata->fi), (char *)&idata->fi); + if (!rc) + idata->contains_posix_file_info = false; } SMB2_query_info_free(&rqst[num_rqst++]); if (rc) @@ -620,7 +626,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, break; case SMB2_OP_POSIX_QUERY_INFO: idata = in_iov[i].iov_base; - idata->contains_posix_file_info = true; if (rc == 0 && cfile && cfile->symlink_target) { idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL); if (!idata->symlink_target) @@ -634,6 +639,8 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, le32_to_cpu(qi_rsp->OutputBufferLength), &rsp_iov[i + 1], sizeof(idata->posix_fi) /* add SIDs */, (char *)&idata->posix_fi); + if (!rc) + idata->contains_posix_file_info = true; } if (rc == 0) rc = parse_posix_sids(idata, &rsp_iov[i + 1]); @@ -705,7 +712,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, idata = in_iov[i].iov_base; idata->reparse.io.iov = *iov; idata->reparse.io.buftype = resp_buftype[i + 1]; - idata->contains_posix_file_info = false; /* BB VERIFY */ rbuf = reparse_buf_ptr(iov); if (IS_ERR(rbuf)) { rc = PTR_ERR(rbuf); @@ -727,7 +733,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, case SMB2_OP_QUERY_WSL_EA: if (!rc) { idata = in_iov[i].iov_base; - idata->contains_posix_file_info = false; qi_rsp = rsp_iov[i + 1].iov_base; data[0] = (u8 *)qi_rsp + le16_to_cpu(qi_rsp->OutputBufferOffset); size[0] = le32_to_cpu(qi_rsp->OutputBufferLength); @@ -1000,12 +1005,13 @@ int smb2_query_path_info(const unsigned int xid, /* * If the symlink was already parsed in create response then it is needed to fix * its type now (after the second call with OPEN_REPARSE_POINT which filled the - * data->fi.Attributes). If the symlink was not parsed in create response then + * metadata attributes). If the symlink was not parsed in create response then * the data->symlink_target was not filled yet and then the type will be fixed * later after data->symlink_target is filled. */ if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc && data->symlink_target) { - bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY; + bool directory = cifs_open_data_attrs(data) & ATTR_DIRECTORY; + rc = smb2_fix_symlink_target_type(&data->symlink_target, directory, cifs_sb); } break; diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 192649fec25d..7d6738ffcb80 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1569,7 +1569,7 @@ SMB2_request_res_key(const unsigned int xid, struct cifs_tcon *tcon, memcpy(pcchunk->SourceKey, res_key->ResumeKey, COPY_CHUNK_RES_KEY_SIZE); req_res_key_exit: - kfree(res_key); + kfree_sensitive(res_key); return rc; } @@ -2222,8 +2222,7 @@ smb2_duplicate_extents(const unsigned int xid, rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false); if (rc) goto duplicate_extents_out; - netfs_resize_file(netfs_inode(inode), dest_off + len, true); - cifs_setsize(inode, dest_off + len); + cifs_resize_file_locked(inode, dest_off + len); } rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid, trgtfile->fid.volatile_fid, @@ -3776,8 +3775,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, } new_eof = off + len; - netfs_resize_file(&cifsi->netfs, new_eof, true); - cifs_setsize(inode, new_eof); + cifs_resize_file_locked(inode, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, @@ -3825,8 +3823,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, if (rc) goto out; - netfs_resize_file(&cifsi->netfs, new_eof, true); - cifs_setsize(inode, new_eof); + cifs_resize_file_locked(inode, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, @@ -4636,7 +4633,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst, rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE); else rc = crypto_aead_setkey(tfm, key, SMB3_GCM128_CRYPTKEY_SIZE); - + memzero_explicit(key, sizeof(key)); if (rc) { cifs_server_dbg(VFS, "%s: Failed to set aead key %d\n", __func__, rc); return rc; diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 4ce165e40657..dea05aeb53a1 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -3372,6 +3372,7 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path, #endif /* CIFS_DEBUG2 */ if (file_info) { + buf->contains_posix_file_info = false; file_info->CreationTime = rsp->CreationTime; file_info->LastAccessTime = rsp->LastAccessTime; file_info->LastWriteTime = rsp->LastWriteTime; @@ -4564,8 +4565,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len, if (rc) return rc; - if (server == NULL) - return -ECONNABORTED; + if (!server) { + rc = -ECONNABORTED; + goto free_req; + } shdr = &req->hdr; shdr->Id.SyncId.ProcessId = cpu_to_le32(io_parms->pid); @@ -4596,8 +4599,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len, rdata->mr = smbd_register_mr(server->smbd_conn, &rdata->subreq.io_iter, true, need_invalidate); - if (!rdata->mr) - return -EAGAIN; + if (!rdata->mr) { + rc = -EAGAIN; + goto free_req; + } req->Channel = SMB2_CHANNEL_RDMA_V1_INVALIDATE; if (need_invalidate) @@ -4638,6 +4643,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len, *buf = req; return rc; + +free_req: + cifs_small_buf_release(req); + return rc; } static void @@ -4885,6 +4894,7 @@ smb2_async_readv(struct cifs_io_subrequest *rdata) smb2_should_replay(tcon, &rdata->retries, &rdata->cur_sleep)) { + rdata->replay = true; trace_netfs_sreq(&rdata->subreq, netfs_sreq_trace_io_retry_needed); __set_bit(NETFS_SREQ_NEED_RETRY, &rdata->subreq.flags); } diff --git a/fs/smb/client/smb2transport.c b/fs/smb/client/smb2transport.c index d23566da2ac8..c407f30e0040 100644 --- a/fs/smb/client/smb2transport.c +++ b/fs/smb/client/smb2transport.c @@ -249,6 +249,8 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) if (!rc) memcpy(shdr->Signature, smb2_signature, SMB2_SIGNATURE_SIZE); + memzero_explicit(key, sizeof(key)); + memzero_explicit(&hmac_ctx, sizeof(hmac_ctx)); return rc; } @@ -283,6 +285,7 @@ static void generate_key(struct cifs_ses *ses, struct kvec label, hmac_sha256_final(&hmac_ctx, prfhash); memcpy(key, prfhash, key_size); + memzero_explicit(prfhash, sizeof(prfhash)); } struct derivation { @@ -482,6 +485,7 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE); rc = aes_cmac_preparekey(&cmac_key, key, SMB2_CMACAES_SIZE); + memzero_explicit(key, sizeof(key)); if (rc) { cifs_server_dbg(VFS, "%s: Could not set key for cmac aes\n", __func__); return rc; diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index 5b21ad3c15fb..12241abb8e2e 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -133,6 +133,7 @@ EM(smb_eio_trace_sym_slash, "sym_slash") \ EM(smb_eio_trace_sym_target_len, "sym_target_len") \ EM(smb_eio_trace_symlink_file_size, "symlink_file_size") \ + EM(smb_eio_trace_tcon_bcc_too_small, "tcon_bcc_too_small") \ EM(smb_eio_trace_tdis_in_reconnect, "tdis_in_reconnect") \ EM(smb_eio_trace_tx_chained_async, "tx_chained_async") \ EM(smb_eio_trace_tx_compress_failed, "tx_compress_failed") \ @@ -213,7 +214,7 @@ #define EM(a, b) a, #define E_(a, b) a -enum smb_eio_trace { smb_eio_traces } __mode(byte); +enum smb_eio_trace { smb_eio_traces }; enum smb3_rw_credits_trace { smb3_rw_credits_traces } __mode(byte); enum smb3_tcon_ref_trace { smb3_tcon_ref_traces } __mode(byte);