diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-11-30 21:14:42 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-11-30 21:14:42 +0300 |
commit | 0235da0faeeec1c1ce2265fc627f5d5d9cae7ce8 (patch) | |
tree | d1af16f735b838688bbd5a7f318db6dba4b9d34e | |
parent | 109daa2356efe73491e32a3b2431c8bf57a6c58e (diff) | |
parent | 8d7690b3c146f8ae3089918226697bf4e3943032 (diff) | |
download | linux-0235da0faeeec1c1ce2265fc627f5d5d9cae7ce8.tar.xz |
Merge tag '6.13-rc-part2-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6
Pull smb client updates from Steve French:
- directory lease fixes
- password rotation fixes
- reconnect fix
- fix for SMB3.02 mounts
- DFS (global namespace) fixes
- fixes for special file handling (most relating to better handling
various types of symlinks)
- two minor cleanups
* tag '6.13-rc-part2-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6: (22 commits)
cifs: update internal version number
cifs: unlock on error in smb3_reconfigure()
cifs: during remount, make sure passwords are in sync
cifs: support mounting with alternate password to allow password rotation
smb: Initialize cfid->tcon before performing network ops
smb: During unmount, ensure all cached dir instances drop their dentry
smb: client: fix noisy message when mounting shares
smb: client: don't try following DFS links in cifs_tree_connect()
smb: client: allow reconnect when sending ioctl
smb: client: get rid of @nlsc param in cifs_tree_connect()
smb: client: allow more DFS referrals to be cached
cifs: Fix parsing reparse point with native symlink in SMB1 non-UNICODE session
cifs: Validate content of WSL reparse point buffers
cifs: Improve guard for excluding $LXDEV xattr
cifs: Add support for parsing WSL-style symlinks
cifs: Validate content of native symlink
cifs: Fix parsing native symlinks relative to the export
smb: client: fix NULL ptr deref in crypto_aead_setkey()
Update misleading comment in cifs_chan_update_iface
smb: client: change return value in open_cached_dir_by_dentry() if !cfids
...
-rw-r--r-- | fs/smb/client/cached_dir.c | 170 | ||||
-rw-r--r-- | fs/smb/client/cached_dir.h | 6 | ||||
-rw-r--r-- | fs/smb/client/cifsfs.c | 12 | ||||
-rw-r--r-- | fs/smb/client/cifsfs.h | 4 | ||||
-rw-r--r-- | fs/smb/client/cifsglob.h | 4 | ||||
-rw-r--r-- | fs/smb/client/cifsproto.h | 4 | ||||
-rw-r--r-- | fs/smb/client/cifssmb.c | 15 | ||||
-rw-r--r-- | fs/smb/client/connect.c | 66 | ||||
-rw-r--r-- | fs/smb/client/dfs.c | 193 | ||||
-rw-r--r-- | fs/smb/client/dfs_cache.c | 4 | ||||
-rw-r--r-- | fs/smb/client/fs_context.c | 85 | ||||
-rw-r--r-- | fs/smb/client/fs_context.h | 1 | ||||
-rw-r--r-- | fs/smb/client/inode.c | 4 | ||||
-rw-r--r-- | fs/smb/client/reparse.c | 157 | ||||
-rw-r--r-- | fs/smb/client/reparse.h | 4 | ||||
-rw-r--r-- | fs/smb/client/sess.c | 5 | ||||
-rw-r--r-- | fs/smb/client/smb1ops.c | 4 | ||||
-rw-r--r-- | fs/smb/client/smb2file.c | 21 | ||||
-rw-r--r-- | fs/smb/client/smb2inode.c | 6 | ||||
-rw-r--r-- | fs/smb/client/smb2ops.c | 14 | ||||
-rw-r--r-- | fs/smb/client/smb2pdu.c | 20 | ||||
-rw-r--r-- | fs/smb/client/smb2proto.h | 9 | ||||
-rw-r--r-- | fs/smb/client/trace.h | 3 | ||||
-rw-r--r-- | fs/smb/common/smb2pdu.h | 9 |
24 files changed, 523 insertions, 297 deletions
diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index 06eb19dabb0e..fe738623cf1b 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -17,6 +17,11 @@ static void free_cached_dir(struct cached_fid *cfid); static void smb2_close_cached_fid(struct kref *ref); static void cfids_laundromat_worker(struct work_struct *work); +struct cached_dir_dentry { + struct list_head entry; + struct dentry *dentry; +}; + static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids, const char *path, bool lookup_only, @@ -157,15 +162,17 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, const char *npath; int retries = 0, cur_sleep = 1; - if (tcon == NULL || tcon->cfids == NULL || tcon->nohandlecache || - is_smb1_server(tcon->ses->server) || (dir_cache_timeout == 0)) + if (cifs_sb->root == NULL) + return -ENOENT; + + if (tcon == NULL) return -EOPNOTSUPP; ses = tcon->ses; cfids = tcon->cfids; - if (cifs_sb->root == NULL) - return -ENOENT; + if (cfids == NULL) + return -EOPNOTSUPP; replay_again: /* reinitialize for possible replay */ @@ -222,6 +229,7 @@ replay_again: } } cfid->dentry = dentry; + cfid->tcon = tcon; /* * We do not hold the lock for the open because in case @@ -293,7 +301,6 @@ replay_again: } goto oshr_free; } - cfid->tcon = tcon; cfid->is_open = true; spin_lock(&cfids->cfid_list_lock); @@ -389,7 +396,7 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon, struct cached_fids *cfids = tcon->cfids; if (cfids == NULL) - return -ENOENT; + return -EOPNOTSUPP; spin_lock(&cfids->cfid_list_lock); list_for_each_entry(cfid, &cfids->entries, entry) { @@ -470,7 +477,10 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb) struct cifs_tcon *tcon; struct tcon_link *tlink; struct cached_fids *cfids; + struct cached_dir_dentry *tmp_list, *q; + LIST_HEAD(entry); + spin_lock(&cifs_sb->tlink_tree_lock); for (node = rb_first(root); node; node = rb_next(node)) { tlink = rb_entry(node, struct tcon_link, tl_rbnode); tcon = tlink_tcon(tlink); @@ -479,11 +489,30 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb) cfids = tcon->cfids; if (cfids == NULL) continue; + spin_lock(&cfids->cfid_list_lock); list_for_each_entry(cfid, &cfids->entries, entry) { - dput(cfid->dentry); + tmp_list = kmalloc(sizeof(*tmp_list), GFP_ATOMIC); + if (tmp_list == NULL) + break; + spin_lock(&cfid->fid_lock); + tmp_list->dentry = cfid->dentry; cfid->dentry = NULL; + spin_unlock(&cfid->fid_lock); + + list_add_tail(&tmp_list->entry, &entry); } + spin_unlock(&cfids->cfid_list_lock); } + spin_unlock(&cifs_sb->tlink_tree_lock); + + list_for_each_entry_safe(tmp_list, q, &entry, entry) { + list_del(&tmp_list->entry); + dput(tmp_list->dentry); + kfree(tmp_list); + } + + /* Flush any pending work that will drop dentries */ + flush_workqueue(cfid_put_wq); } /* @@ -494,14 +523,18 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon) { struct cached_fids *cfids = tcon->cfids; struct cached_fid *cfid, *q; - LIST_HEAD(entry); if (cfids == NULL) return; + /* + * Mark all the cfids as closed, and move them to the cfids->dying list. + * They'll be cleaned up later by cfids_invalidation_worker. Take + * a reference to each cfid during this process. + */ spin_lock(&cfids->cfid_list_lock); list_for_each_entry_safe(cfid, q, &cfids->entries, entry) { - list_move(&cfid->entry, &entry); + list_move(&cfid->entry, &cfids->dying); cfids->num_entries--; cfid->is_open = false; cfid->on_list = false; @@ -514,26 +547,47 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon) } else kref_get(&cfid->refcount); } + /* + * Queue dropping of the dentries once locks have been dropped + */ + if (!list_empty(&cfids->dying)) + queue_work(cfid_put_wq, &cfids->invalidation_work); spin_unlock(&cfids->cfid_list_lock); - - list_for_each_entry_safe(cfid, q, &entry, entry) { - list_del(&cfid->entry); - cancel_work_sync(&cfid->lease_break); - /* - * Drop the ref-count from above, either the lease-ref (if there - * was one) or the extra one acquired. - */ - kref_put(&cfid->refcount, smb2_close_cached_fid); - } } static void -smb2_cached_lease_break(struct work_struct *work) +cached_dir_offload_close(struct work_struct *work) { struct cached_fid *cfid = container_of(work, - struct cached_fid, lease_break); + struct cached_fid, close_work); + struct cifs_tcon *tcon = cfid->tcon; + + WARN_ON(cfid->on_list); kref_put(&cfid->refcount, smb2_close_cached_fid); + cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close); +} + +/* + * Release the cached directory's dentry, and then queue work to drop cached + * directory itself (closing on server if needed). + * + * Must be called with a reference to the cached_fid and a reference to the + * tcon. + */ +static void cached_dir_put_work(struct work_struct *work) +{ + struct cached_fid *cfid = container_of(work, struct cached_fid, + put_work); + struct dentry *dentry; + + spin_lock(&cfid->fid_lock); + dentry = cfid->dentry; + cfid->dentry = NULL; + spin_unlock(&cfid->fid_lock); + + dput(dentry); + queue_work(serverclose_wq, &cfid->close_work); } int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]) @@ -560,8 +614,10 @@ int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]) cfid->on_list = false; cfids->num_entries--; - queue_work(cifsiod_wq, - &cfid->lease_break); + ++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; } @@ -583,7 +639,8 @@ static struct cached_fid *init_cached_dir(const char *path) return NULL; } - INIT_WORK(&cfid->lease_break, smb2_cached_lease_break); + INIT_WORK(&cfid->close_work, cached_dir_offload_close); + INIT_WORK(&cfid->put_work, cached_dir_put_work); INIT_LIST_HEAD(&cfid->entry); INIT_LIST_HEAD(&cfid->dirents.entries); mutex_init(&cfid->dirents.de_mutex); @@ -596,6 +653,9 @@ static void free_cached_dir(struct cached_fid *cfid) { struct cached_dirent *dirent, *q; + WARN_ON(work_pending(&cfid->close_work)); + WARN_ON(work_pending(&cfid->put_work)); + dput(cfid->dentry); cfid->dentry = NULL; @@ -613,10 +673,30 @@ static void free_cached_dir(struct cached_fid *cfid) kfree(cfid); } +static void cfids_invalidation_worker(struct work_struct *work) +{ + struct cached_fids *cfids = container_of(work, struct cached_fids, + invalidation_work); + struct cached_fid *cfid, *q; + LIST_HEAD(entry); + + spin_lock(&cfids->cfid_list_lock); + /* move cfids->dying to the local list */ + list_cut_before(&entry, &cfids->dying, &cfids->dying); + spin_unlock(&cfids->cfid_list_lock); + + list_for_each_entry_safe(cfid, q, &entry, entry) { + list_del(&cfid->entry); + /* Drop the ref-count acquired in invalidate_all_cached_dirs */ + kref_put(&cfid->refcount, smb2_close_cached_fid); + } +} + static void cfids_laundromat_worker(struct work_struct *work) { struct cached_fids *cfids; struct cached_fid *cfid, *q; + struct dentry *dentry; LIST_HEAD(entry); cfids = container_of(work, struct cached_fids, laundromat_work.work); @@ -642,18 +722,28 @@ static void cfids_laundromat_worker(struct work_struct *work) list_for_each_entry_safe(cfid, q, &entry, entry) { list_del(&cfid->entry); - /* - * Cancel and wait for the work to finish in case we are racing - * with it. - */ - cancel_work_sync(&cfid->lease_break); - /* - * Drop the ref-count from above, either the lease-ref (if there - * was one) or the extra one acquired. - */ - kref_put(&cfid->refcount, smb2_close_cached_fid); + + spin_lock(&cfid->fid_lock); + dentry = cfid->dentry; + cfid->dentry = NULL; + spin_unlock(&cfid->fid_lock); + + dput(dentry); + if (cfid->is_open) { + spin_lock(&cifs_tcp_ses_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(&cifs_tcp_ses_lock); + queue_work(serverclose_wq, &cfid->close_work); + } else + /* + * Drop the ref-count from above, either the lease-ref (if there + * was one) or the extra one acquired. + */ + kref_put(&cfid->refcount, smb2_close_cached_fid); } - queue_delayed_work(cifsiod_wq, &cfids->laundromat_work, + queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, dir_cache_timeout * HZ); } @@ -666,9 +756,11 @@ struct cached_fids *init_cached_dirs(void) return NULL; spin_lock_init(&cfids->cfid_list_lock); INIT_LIST_HEAD(&cfids->entries); + INIT_LIST_HEAD(&cfids->dying); + INIT_WORK(&cfids->invalidation_work, cfids_invalidation_worker); INIT_DELAYED_WORK(&cfids->laundromat_work, cfids_laundromat_worker); - queue_delayed_work(cifsiod_wq, &cfids->laundromat_work, + queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, dir_cache_timeout * HZ); return cfids; @@ -687,6 +779,7 @@ void free_cached_dirs(struct cached_fids *cfids) return; cancel_delayed_work_sync(&cfids->laundromat_work); + cancel_work_sync(&cfids->invalidation_work); spin_lock(&cfids->cfid_list_lock); list_for_each_entry_safe(cfid, q, &cfids->entries, entry) { @@ -694,6 +787,11 @@ void free_cached_dirs(struct cached_fids *cfids) cfid->is_open = false; list_move(&cfid->entry, &entry); } + list_for_each_entry_safe(cfid, q, &cfids->dying, entry) { + cfid->on_list = false; + cfid->is_open = false; + list_move(&cfid->entry, &entry); + } spin_unlock(&cfids->cfid_list_lock); list_for_each_entry_safe(cfid, q, &entry, entry) { diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h index 81ba0fd5cc16..1dfe79d947a6 100644 --- a/fs/smb/client/cached_dir.h +++ b/fs/smb/client/cached_dir.h @@ -44,7 +44,8 @@ struct cached_fid { spinlock_t fid_lock; struct cifs_tcon *tcon; struct dentry *dentry; - struct work_struct lease_break; + struct work_struct put_work; + struct work_struct close_work; struct smb2_file_all_info file_all_info; struct cached_dirents dirents; }; @@ -53,10 +54,13 @@ struct cached_fid { struct cached_fids { /* Must be held when: * - accessing the cfids->entries list + * - accessing the cfids->dying list */ spinlock_t cfid_list_lock; int num_entries; struct list_head entries; + struct list_head dying; + struct work_struct invalidation_work; struct delayed_work laundromat_work; }; diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 979853471027..c9f9b6e97964 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -157,6 +157,7 @@ struct workqueue_struct *fileinfo_put_wq; struct workqueue_struct *cifsoplockd_wq; struct workqueue_struct *deferredclose_wq; struct workqueue_struct *serverclose_wq; +struct workqueue_struct *cfid_put_wq; __u32 cifs_lock_secret; /* @@ -1920,9 +1921,16 @@ init_cifs(void) goto out_destroy_deferredclose_wq; } + cfid_put_wq = alloc_workqueue("cfid_put_wq", + WQ_FREEZABLE|WQ_MEM_RECLAIM, 0); + if (!cfid_put_wq) { + rc = -ENOMEM; + goto out_destroy_serverclose_wq; + } + rc = cifs_init_inodecache(); if (rc) - goto out_destroy_serverclose_wq; + goto out_destroy_cfid_put_wq; rc = cifs_init_netfs(); if (rc) @@ -1990,6 +1998,8 @@ out_destroy_netfs: cifs_destroy_netfs(); out_destroy_inodecache: cifs_destroy_inodecache(); +out_destroy_cfid_put_wq: + destroy_workqueue(cfid_put_wq); out_destroy_serverclose_wq: destroy_workqueue(serverclose_wq); out_destroy_deferredclose_wq: diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 71b720dbb2ce..a762dbbbd959 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -146,6 +146,6 @@ extern const struct export_operations cifs_export_ops; #endif /* CONFIG_CIFS_NFSD_EXPORT */ /* when changing internal version - update following two lines at same time */ -#define SMB3_PRODUCT_BUILD 51 -#define CIFS_VERSION "2.51" +#define SMB3_PRODUCT_BUILD 52 +#define CIFS_VERSION "2.52" #endif /* _CIFSFS_H */ diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index fc33dfe7e925..6e63abe461fd 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -594,6 +594,7 @@ struct smb_version_operations { /* Check for STATUS_NETWORK_NAME_DELETED */ bool (*is_network_name_deleted)(char *buf, struct TCP_Server_Info *srv); int (*parse_reparse_point)(struct cifs_sb_info *cifs_sb, + const char *full_path, struct kvec *rsp_iov, struct cifs_open_info_data *data); int (*create_reparse_symlink)(const unsigned int xid, @@ -1990,7 +1991,7 @@ require use of the stronger protocol */ * cifsInodeInfo->lock_sem cifsInodeInfo->llist cifs_init_once * ->can_cache_brlcks * cifsInodeInfo->deferred_lock cifsInodeInfo->deferred_closes cifsInodeInfo_alloc - * cached_fid->fid_mutex cifs_tcon->crfid tcon_info_alloc + * cached_fids->cfid_list_lock cifs_tcon->cfids->entries init_cached_dirs * cifsFileInfo->fh_mutex cifsFileInfo cifs_new_fileinfo * cifsFileInfo->file_info_lock cifsFileInfo->count cifs_new_fileinfo * ->invalidHandle initiate_cifs_search @@ -2078,6 +2079,7 @@ extern struct workqueue_struct *fileinfo_put_wq; extern struct workqueue_struct *cifsoplockd_wq; extern struct workqueue_struct *deferredclose_wq; extern struct workqueue_struct *serverclose_wq; +extern struct workqueue_struct *cfid_put_wq; extern __u32 cifs_lock_secret; extern mempool_t *cifs_sm_req_poolp; diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 075985bfb13a..bbaaf16af20f 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -314,8 +314,7 @@ extern void cifs_move_llist(struct list_head *source, struct list_head *dest); extern void cifs_free_llist(struct list_head *llist); extern void cifs_del_lock_waiters(struct cifsLockInfo *lock); -extern int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, - const struct nls_table *nlsc); +int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon); extern int cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses, @@ -661,6 +660,7 @@ char *extract_hostname(const char *unc); char *extract_sharename(const char *unc); int parse_reparse_point(struct reparse_data_buffer *buf, u32 plen, struct cifs_sb_info *cifs_sb, + const char *full_path, bool unicode, struct cifs_open_info_data *data); int __cifs_sfu_make_node(unsigned int xid, struct inode *inode, struct dentry *dentry, struct cifs_tcon *tcon, diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 4858331ee918..bd42a419458e 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -70,10 +70,9 @@ static struct { static int cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) { - int rc; - struct cifs_ses *ses; struct TCP_Server_Info *server; - struct nls_table *nls_codepage = NULL; + struct cifs_ses *ses; + int rc; /* * SMBs NegProt, SessSetup, uLogoff do not have tcon yet so check for @@ -131,8 +130,6 @@ again: } spin_unlock(&server->srv_lock); - nls_codepage = ses->local_nls; - /* * need to prevent multiple threads trying to simultaneously * reconnect the same SMB session @@ -156,7 +153,7 @@ again: rc = cifs_negotiate_protocol(0, ses, server); if (!rc) - rc = cifs_setup_session(0, ses, server, nls_codepage); + rc = cifs_setup_session(0, ses, server, ses->local_nls); /* do we need to reconnect tcon? */ if (rc || !tcon->need_reconnect) { @@ -166,7 +163,7 @@ again: skip_sess_setup: cifs_mark_open_files_invalid(tcon); - rc = cifs_tree_connect(0, tcon, nls_codepage); + rc = cifs_tree_connect(0, tcon); mutex_unlock(&ses->session_mutex); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); @@ -4320,8 +4317,8 @@ getDFSRetry: * CIFSGetDFSRefer() may be called from cifs_reconnect_tcon() and thus * causing an infinite recursion. */ - rc = smb_init_no_reconnect(SMB_COM_TRANSACTION2, 15, ses->tcon_ipc, - (void **)&pSMB, (void **)&pSMBr); + rc = smb_init(SMB_COM_TRANSACTION2, 15, ses->tcon_ipc, + (void **)&pSMB, (void **)&pSMBr); if (rc) return rc; diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 0a97228c06b1..56b3a9eb9b05 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1897,11 +1897,35 @@ static int match_session(struct cifs_ses *ses, CIFS_MAX_USERNAME_LEN)) return 0; if ((ctx->username && strlen(ctx->username) != 0) && - ses->password != NULL && - strncmp(ses->password, - ctx->password ? ctx->password : "", - CIFS_MAX_PASSWORD_LEN)) - return 0; + ses->password != NULL) { + + /* New mount can only share sessions with an existing mount if: + * 1. Both password and password2 match, or + * 2. password2 of the old mount matches password of the new mount + * and password of the old mount matches password2 of the new + * mount + */ + if (ses->password2 != NULL && ctx->password2 != NULL) { + if (!((strncmp(ses->password, ctx->password ? + ctx->password : "", CIFS_MAX_PASSWORD_LEN) == 0 && + strncmp(ses->password2, ctx->password2, + CIFS_MAX_PASSWORD_LEN) == 0) || + (strncmp(ses->password, ctx->password2, + CIFS_MAX_PASSWORD_LEN) == 0 && + strncmp(ses->password2, ctx->password ? + ctx->password : "", CIFS_MAX_PASSWORD_LEN) == 0))) + return 0; + + } else if ((ses->password2 == NULL && ctx->password2 != NULL) || + (ses->password2 != NULL && ctx->password2 == NULL)) { + return 0; + + } else { + if (strncmp(ses->password, ctx->password ? + ctx->password : "", CIFS_MAX_PASSWORD_LEN)) + return 0; + } + } } if (strcmp(ctx->local_nls->charset, ses->local_nls->charset)) @@ -2244,6 +2268,7 @@ struct cifs_ses * cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) { int rc = 0; + int retries = 0; unsigned int xid; struct cifs_ses *ses; struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr; @@ -2262,6 +2287,8 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) cifs_dbg(FYI, "Session needs reconnect\n"); mutex_lock(&ses->session_mutex); + +retry_old_session: rc = cifs_negotiate_protocol(xid, ses, server); if (rc) { mutex_unlock(&ses->session_mutex); @@ -2274,6 +2301,13 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) rc = cifs_setup_session(xid, ses, server, ctx->local_nls); if (rc) { + if (((rc == -EACCES) || (rc == -EKEYEXPIRED) || + (rc == -EKEYREVOKED)) && !retries && ses->password2) { + retries++; + cifs_dbg(FYI, "Session reconnect failed, retrying with alternate password\n"); + swap(ses->password, ses->password2); + goto retry_old_session; + } mutex_unlock(&ses->session_mutex); /* problem -- put our reference */ cifs_put_smb_ses(ses); @@ -2369,6 +2403,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) ses->chans_need_reconnect = 1; spin_unlock(&ses->chan_lock); +retry_new_session: mutex_lock(&ses->session_mutex); rc = cifs_negotiate_protocol(xid, ses, server); if (!rc) @@ -2381,8 +2416,16 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) sizeof(ses->smb3signingkey)); spin_unlock(&ses->chan_lock); - if (rc) - goto get_ses_fail; + if (rc) { + if (((rc == -EACCES) || (rc == -EKEYEXPIRED) || + (rc == -EKEYREVOKED)) && !retries && ses->password2) { + retries++; + cifs_dbg(FYI, "Session setup failed, retrying with alternate password\n"); + swap(ses->password, ses->password2); + goto retry_new_session; + } else + goto get_ses_fail; + } /* * success, put it on the list and add it as first channel @@ -2571,7 +2614,7 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) if (ses->server->dialect >= SMB20_PROT_ID && (ses->server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING)) - nohandlecache = ctx->nohandlecache; + nohandlecache = ctx->nohandlecache || !dir_cache_timeout; else nohandlecache = true; tcon = tcon_info_alloc(!nohandlecache, netfs_trace_tcon_ref_new); @@ -4344,10 +4387,10 @@ cifs_prune_tlinks(struct work_struct *work) } #ifndef CONFIG_CIFS_DFS_UPCALL -int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc) +int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon) { - int rc; const struct smb_version_operations *ops = tcon->ses->server->ops; + int rc; /* only send once per connect */ spin_lock(&tcon->tc_lock); @@ -4370,7 +4413,8 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru tcon->status = TID_IN_TCON; spin_unlock(&tcon->tc_lock); - rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, nlsc); + rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, + tcon, tcon->ses->local_nls); if (rc) { spin_lock(&tcon->tc_lock); if (tcon->status == TID_IN_TCON) diff --git a/fs/smb/client/dfs.c b/fs/smb/client/dfs.c index 3f6077c68d68..4647df9e1e3b 100644 --- a/fs/smb/client/dfs.c +++ b/fs/smb/client/dfs.c @@ -321,49 +321,6 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx) return rc; } -/* Update dfs referral path of superblock */ -static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb, - const char *target) -{ - int rc = 0; - size_t len = strlen(target); - char *refpath, *npath; - - if (unlikely(len < 2 || *target != '\\')) - return -EINVAL; - - if (target[1] == '\\') { - len += 1; - refpath = kmalloc(len, GFP_KERNEL); - if (!refpath) - return -ENOMEM; - - scnprintf(refpath, len, "%s", target); - } else { - len += sizeof("\\"); - refpath = kmalloc(len, GFP_KERNEL); - if (!refpath) - return -ENOMEM; - - scnprintf(refpath, len, "\\%s", target); - } - - npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb)); - kfree(refpath); - - if (IS_ERR(npath)) { - rc = PTR_ERR(npath); - } else { - mutex_lock(&server->refpath_lock); - spin_lock(&server->srv_lock); - kfree(server->leaf_fullpath); - server->leaf_fullpath = npath; - spin_unlock(&server->srv_lock); - mutex_unlock(&server->refpath_lock); - } - return rc; -} - static int target_share_matches_server(struct TCP_Server_Info *server, char *share, bool *target_match) { @@ -388,77 +345,22 @@ static int target_share_matches_server(struct TCP_Server_Info *server, char *sha return rc; } -static void __tree_connect_ipc(const unsigned int xid, char *tree, - struct cifs_sb_info *cifs_sb, - struct cifs_ses *ses) -{ - struct TCP_Server_Info *server = ses->server; - struct cifs_tcon *tcon = ses->tcon_ipc; - int rc; - - spin_lock(&ses->ses_lock); - spin_lock(&ses->chan_lock); - if (cifs_chan_needs_reconnect(ses, server) || - ses->ses_status != SES_GOOD) { - spin_unlock(&ses->chan_lock); - spin_unlock(&ses->ses_lock); - cifs_server_dbg(FYI, "%s: skipping ipc reconnect due to disconnected ses\n", - __func__); - return; - } - spin_unlock(&ses->chan_lock); - spin_unlock(&ses->ses_lock); - - cifs_server_lock(server); - scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname); - cifs_server_unlock(server); - - rc = server->ops->tree_connect(xid, ses, tree, tcon, - cifs_sb->local_nls); - cifs_server_dbg(FYI, "%s: tree_reconnect %s: %d\n", __func__, tree, rc); - spin_lock(&tcon->tc_lock); - if (rc) { - tcon->status = TID_NEED_TCON; - } else { - tcon->status = TID_GOOD; - tcon->need_reconnect = false; - } - spin_unlock(&tcon->tc_lock); -} - -static void tree_connect_ipc(const unsigned int xid, char *tree, - struct cifs_sb_info *cifs_sb, - struct cifs_tcon *tcon) -{ - struct cifs_ses *ses = tcon->ses; - - __tree_connect_ipc(xid, tree, cifs_sb, ses); - __tree_connect_ipc(xid, tree, cifs_sb, CIFS_DFS_ROOT_SES(ses)); -} - -static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, char *tree, bool islink, - struct dfs_cache_tgt_list *tl) +static int tree_connect_dfs_target(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + char *tree, bool islink, + struct dfs_cache_tgt_list *tl) { - int rc; + const struct smb_version_operations *ops = tcon->ses->server->ops; struct TCP_Server_Info *server = tcon->ses->server; - const struct smb_version_operations *ops = server->ops; - struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses); - char *share = NULL, *prefix = NULL; struct dfs_cache_tgt_iterator *tit; + char *share = NULL, *prefix = NULL; bool target_match; - - tit = dfs_cache_get_tgt_iterator(tl); - if (!tit) { - rc = -ENOENT; - goto out; - } + int rc = -ENOENT; /* Try to tree connect to all dfs targets */ - for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) { - const char *target = dfs_cache_get_tgt_name(tit); - DFS_CACHE_TGT_LIST(ntl); - + for (tit = dfs_cache_get_tgt_iterator(tl); + tit; tit = dfs_cache_get_next_tgt(tl, tit)) { kfree(share); kfree(prefix); share = prefix = NULL; @@ -479,74 +381,21 @@ static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *t } dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit); - tree_connect_ipc(xid, tree, cifs_sb, tcon); - scnprintf(tree, MAX_TREE_SIZE, "\\%s", share); - if (!islink) { - rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls); - break; - } - - /* - * If no dfs referrals were returned from link target, then just do a TREE_CONNECT - * to it. Otherwise, cache the dfs referral and then mark current tcp ses for - * reconnect so either the demultiplex thread or the echo worker will reconnect to - * newly resolved target. - */ - if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target, - NULL, &ntl)) { - rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls); - if (rc) - continue; - + rc = ops->tree_connect(xid, tcon->ses, tree, + tcon, tcon->ses->local_nls); + if (islink && !rc && cifs_sb) rc = cifs_update_super_prepath(cifs_sb, prefix); - } else { - /* Target is another dfs share */ - rc = update_server_fullpath(server, cifs_sb, target); - dfs_cache_free_tgts(tl); - - if (!rc) { - rc = -EREMOTE; - list_replace_init(&ntl.tl_list, &tl->tl_list); - } else - dfs_cache_free_tgts(&ntl); - } break; } -out: kfree(share); kfree(prefix); - - return rc; -} - -static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, char *tree, bool islink, - struct dfs_cache_tgt_list *tl) -{ - int rc; - int num_links = 0; - struct TCP_Server_Info *server = tcon->ses->server; - char *old_fullpath = server->leaf_fullpath; - - do { - rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl); - if (!rc || rc != -EREMOTE) - break; - } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS); - /* - * If we couldn't tree connect to any targets from last referral path, then - * retry it from newly resolved dfs referral. - */ - if (rc && server->leaf_fullpath != old_fullpath) - cifs_signal_cifsd_for_reconnect(server, true); - dfs_cache_free_tgts(tl); return rc; } -int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc) +int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon) { int rc; struct TCP_Server_Info *server = tcon->ses->server; @@ -588,7 +437,8 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru cifs_server_lock(server); scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname); cifs_server_unlock(server); - rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); + rc = ops->tree_connect(xid, tcon->ses, tree, + tcon, tcon->ses->local_nls); goto out; } @@ -596,14 +446,11 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru if (!IS_ERR(sb)) cifs_sb = CIFS_SB(sb); - /* - * Tree connect to last share in @tcon->tree_name whether dfs super or - * cached dfs referral was not found. - */ - if (!cifs_sb || !server->leaf_fullpath || + /* Tree connect to last share in @tcon->tree_name if no DFS referral */ + if (!server->leaf_fullpath || dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) { - rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, - cifs_sb ? cifs_sb->local_nls : nlsc); + rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, + tcon, tcon->ses->local_nls); goto out; } diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 00820f57b434..541608b0267e 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -24,8 +24,8 @@ #include "dfs_cache.h" -#define CACHE_HTABLE_SIZE 32 -#define CACHE_MAX_ENTRIES 64 +#define CACHE_HTABLE_SIZE 512 +#define CACHE_MAX_ENTRIES 1024 #define CACHE_MIN_TTL 120 /* 2 minutes */ #define CACHE_DEFAULT_TTL 300 /* 5 minutes */ diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c index c87879e4739b..49123f458d0c 100644 --- a/fs/smb/client/fs_context.c +++ b/fs/smb/client/fs_context.c @@ -920,12 +920,37 @@ do { \ cifs_sb->ctx->field = NULL; \ } while (0) +int smb3_sync_session_ctx_passwords(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) +{ + if (ses->password && + cifs_sb->ctx->password && + strcmp(ses->password, cifs_sb->ctx->password)) { + kfree_sensitive(cifs_sb->ctx->password); + cifs_sb->ctx->password = kstrdup(ses->password, GFP_KERNEL); + if (!cifs_sb->ctx->password) + return -ENOMEM; + } + if (ses->password2 && + cifs_sb->ctx->password2 && + strcmp(ses->password2, cifs_sb->ctx->password2)) { + kfree_sensitive(cifs_sb->ctx->password2); + cifs_sb->ctx->password2 = kstrdup(ses->password2, GFP_KERNEL); + if (!cifs_sb->ctx->password2) { + kfree_sensitive(cifs_sb->ctx->password); + cifs_sb->ctx->password = NULL; + return -ENOMEM; + } + } + return 0; +} + static int smb3_reconfigure(struct fs_context *fc) { struct smb3_fs_context *ctx = smb3_fc2context(fc); struct dentry *root = fc->root; struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb); struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses; + char *new_password = NULL, *new_password2 = NULL; bool need_recon = false; int rc; @@ -945,21 +970,63 @@ static int smb3_reconfigure(struct fs_context *fc) STEAL_STRING(cifs_sb, ctx, UNC); STEAL_STRING(cifs_sb, ctx, source); STEAL_STRING(cifs_sb, ctx, username); + if (need_recon == false) STEAL_STRING_SENSITIVE(cifs_sb, ctx, password); else { - kfree_sensitive(ses->password); - ses->password = kstrdup(ctx->password, GFP_KERNEL); - if (!ses->password) - return -ENOMEM; - kfree_sensitive(ses->password2); - ses->password2 = kstrdup(ctx->password2, GFP_KERNEL); - if (!ses->password2) { - kfree_sensitive(ses->password); - ses->password = NULL; + if (ctx->password) { + new_password = kstrdup(ctx->password, GFP_KERNEL); + if (!new_password) + return -ENOMEM; + } else + STEAL_STRING_SENSITIVE(cifs_sb, ctx, password); + } + + /* + * if a new password2 has been specified, then reset it's value + * inside the ses struct + */ + if (ctx->password2) { + new_password2 = kstrdup(ctx->password2, GFP_KERNEL); + if (!new_password2) { + kfree_sensitive(new_password); return -ENOMEM; } + } else + STEAL_STRING_SENSITIVE(cifs_sb, ctx, password2); + + /* + * we may update the passwords in the ses struct below. Make sure we do + * not race with smb2_reconnect + */ + mutex_lock(&ses->session_mutex); + + /* + * smb2_reconnect may swap password and password2 in case session setup + * failed. First get ctx passwords in sync with ses passwords. It should + * be okay to do this even if this function were to return an error at a + * later stage + */ + rc = smb3_sync_session_ctx_passwords(cifs_sb, ses); + if (rc) { + mutex_unlock(&ses->session_mutex); + return rc; } + + /* + * now that allocations for passwords are done, commit them + */ + if (new_password) { + kfree_sensitive(ses->password); + ses->password = new_password; + } + if (new_password2) { + kfree_sensitive(ses->password2); + ses->password2 = new_password2; + } + + mutex_unlock(&ses->session_mutex); + STEAL_STRING(cifs_sb, ctx, domainname); STEAL_STRING(cifs_sb, ctx, nodename); STEAL_STRING(cifs_sb, ctx, iocharset); diff --git a/fs/smb/client/fs_context.h b/fs/smb/client/fs_context.h index 67b7fc48ac58..ac6baa774ad3 100644 --- a/fs/smb/client/fs_context.h +++ b/fs/smb/client/fs_context.h @@ -309,6 +309,7 @@ static inline struct smb3_fs_context *smb3_fc2context(const struct fs_context *f } extern int smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx); +extern int smb3_sync_session_ctx_passwords(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses); extern void smb3_update_mnt_flags(struct cifs_sb_info *cifs_sb); /* diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index de8063b44072..42c030687918 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -1137,6 +1137,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, rc = 0; } else if (iov && server->ops->parse_reparse_point) { rc = server->ops->parse_reparse_point(cifs_sb, + full_path, iov, data); } break; @@ -2495,13 +2496,10 @@ cifs_dentry_needs_reval(struct dentry *dentry) return true; if (!open_cached_dir_by_dentry(tcon, dentry->d_parent, &cfid)) { - spin_lock(&cfid->fid_lock); if (cfid->time && cifs_i->time > cfid->time) { - spin_unlock(&cfid->fid_lock); close_cached_dir(cfid); return false; } - spin_unlock(&cfid->fid_lock); close_cached_dir(cfid); } /* diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 90da1e2b6217..e81d2d78ddb7 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -378,8 +378,8 @@ static int wsl_set_xattrs(struct inode *inode, umode_t _mode, memset(iov, 0, sizeof(*iov)); - /* Exclude $LXDEV xattr for sockets and fifos */ - if (S_ISSOCK(_mode) || S_ISFIFO(_mode)) + /* Exclude $LXDEV xattr for non-device files */ + if (!S_ISBLK(_mode) && !S_ISCHR(_mode)) num_xattrs = ARRAY_SIZE(xattrs) - 1; else num_xattrs = ARRAY_SIZE(xattrs); @@ -535,9 +535,95 @@ static int parse_reparse_posix(struct reparse_posix_data *buf, return 0; } +int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, + bool unicode, bool relative, + const char *full_path, + struct cifs_sb_info *cifs_sb) +{ + char sep = CIFS_DIR_SEP(cifs_sb); + char *linux_target = NULL; + char *smb_target = NULL; + int levels; + int rc; + int i; + + /* Check that length it valid for unicode/non-unicode mode */ + if (!len || (unicode && (len % 2))) { + cifs_dbg(VFS, "srv returned malformed symlink buffer\n"); + rc = -EIO; + goto out; + } + + /* + * Check that buffer does not contain UTF-16 null codepoint in unicode + * mode or null byte in non-unicode mode because Linux cannot process + * symlink with null byte. + */ + if ((unicode && UniStrnlen((wchar_t *)buf, len/2) != len/2) || + (!unicode && strnlen(buf, len) != len)) { + cifs_dbg(VFS, "srv returned null byte in native symlink target location\n"); + rc = -EIO; + goto out; + } + + smb_target = cifs_strndup_from_utf16(buf, len, unicode, cifs_sb->local_nls); + if (!smb_target) { + rc = -ENOMEM; + goto out; + } + + if (smb_target[0] == sep && relative) { + /* + * This is a relative SMB symlink from the top of the share, + * which is the top level directory of the Linux mount point. + * Linux does not support such relative symlinks, so convert + * it to the relative symlink from the current directory. + * full_path is the SMB path to the symlink (from which is + * extracted current directory) and smb_target is the SMB path + * where symlink points, therefore full_path must always be on + * the SMB share. + */ + int smb_target_len = strlen(smb_target)+1; + levels = 0; + for (i = 1; full_path[i]; i++) { /* i=1 to skip leading sep */ + if (full_path[i] == sep) + levels++; + } + linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL); + if (!linux_target) { + rc = -ENOMEM; + goto out; + } + for (i = 0; i < levels; i++) { + linux_target[i*3 + 0] = '.'; + linux_target[i*3 + 1] = '.'; + linux_target[i*3 + 2] = sep; + } + memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ + } else { + linux_target = smb_target; + smb_target = NULL; + } + + if (sep == '\\') + convert_delimiter(linux_target, '/'); + + rc = 0; + *target = linux_target; + + cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, *target); + +out: + if (rc != 0) + kfree(linux_target); + kfree(smb_target); + return rc; +} + static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym, u32 plen, bool unicode, struct cifs_sb_info *cifs_sb, + const char *full_path, struct cifs_open_info_data *data) { unsigned int len; @@ -552,20 +638,64 @@ static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym, return -EIO; } - data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs, - len, unicode, + return smb2_parse_native_symlink(&data->symlink_target, + sym->PathBuffer + offs, + len, + unicode, + le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE, + full_path, + cifs_sb); +} + +static int parse_reparse_wsl_symlink(struct reparse_wsl_symlink_data_buffer *buf, + struct cifs_sb_info *cifs_sb, + struct cifs_open_info_data *data) +{ + int len = le16_to_cpu(buf->ReparseDataLength); + int symname_utf8_len; + __le16 *symname_utf16; + int symname_utf16_len; + + if (len <= sizeof(buf->Flags)) { + cifs_dbg(VFS, "srv returned malformed wsl symlink buffer\n"); + return -EIO; + } + + /* PathBuffer is in UTF-8 but without trailing null-term byte */ + symname_utf8_len = len - sizeof(buf->Flags); + /* + * Check that buffer does not contain null byte + * because Linux cannot process symlink with null byte. + */ + if (strnlen(buf->PathBuffer, symname_utf8_len) != symname_utf8_len) { + cifs_dbg(VFS, "srv returned null byte in wsl symlink target location\n"); + return -EIO; + } + symname_utf16 = kzalloc(symname_utf8_len * 2, GFP_KERNEL); + if (!symname_utf16) + return -ENOMEM; + symname_utf16_len = utf8s_to_utf16s(buf->PathBuffer, symname_utf8_len, + UTF16_LITTLE_ENDIAN, + symname_utf16, symname_utf8_len * 2); + if (symname_utf16_len < 0) { + kfree(symname_utf16); + return symname_utf16_len; + } + symname_utf16_len *= 2; /* utf8s_to_utf16s() returns number of u16 items, not byte length */ + + data->symlink_target = cifs_strndup_from_utf16((u8 *)symname_utf16, + symname_utf16_len, true, cifs_sb->local_nls); + kfree(symname_utf16); if (!data->symlink_target) return -ENOMEM; - convert_delimiter(data->symlink_target, '/'); - cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target); - return 0; } int parse_reparse_point(struct reparse_data_buffer *buf, u32 plen, struct cifs_sb_info *cifs_sb, + const char *full_path, bool unicode, struct cifs_open_info_data *data) { struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); @@ -580,12 +710,20 @@ int parse_reparse_point(struct reparse_data_buffer *buf, case IO_REPARSE_TAG_SYMLINK: return parse_reparse_symlink( (struct reparse_symlink_data_buffer *)buf, - plen, unicode, cifs_sb, data); + plen, unicode, cifs_sb, full_path, data); case IO_REPARSE_TAG_LX_SYMLINK: + return parse_reparse_wsl_symlink( + (struct reparse_wsl_symlink_data_buffer *)buf, + cifs_sb, data); case IO_REPARSE_TAG_AF_UNIX: case IO_REPARSE_TAG_LX_FIFO: case IO_REPARSE_TAG_LX_CHR: case IO_REPARSE_TAG_LX_BLK: + if (le16_to_cpu(buf->ReparseDataLength) != 0) { + cifs_dbg(VFS, "srv returned malformed buffer for reparse point: 0x%08x\n", + le32_to_cpu(buf->ReparseTag)); + return -EIO; + } break; default: cifs_tcon_dbg(VFS | ONCE, "unhandled reparse tag: 0x%08x\n", @@ -596,6 +734,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf, } int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, + const char *full_path, struct kvec *rsp_iov, struct cifs_open_info_data *data) { @@ -605,7 +744,7 @@ int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, buf = (struct reparse_data_buffer *)((u8 *)io + le32_to_cpu(io->OutputOffset)); - return parse_reparse_point(buf, plen, cifs_sb, true, data); + return parse_reparse_point(buf, plen, cifs_sb, full_path, true, data); } static void wsl_to_fattr(struct cifs_open_info_data *data, diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h index 2a9f4f9f79de..ff05b0e75c92 100644 --- a/fs/smb/client/reparse.h +++ b/fs/smb/client/reparse.h @@ -117,7 +117,9 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, int smb2_mknod_reparse(unsigned int xid, struct inode *inode, struct dentry *dentry, struct cifs_tcon *tcon, const char *full_path, umode_t mode, dev_t dev); -int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, struct kvec *rsp_iov, +int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, + const char *full_path, + struct kvec *rsp_iov, struct cifs_open_info_data *data); #endif /* _CIFS_REPARSE_H */ diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index c88e9657f47a..0bb77f9ec686 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -347,10 +347,7 @@ done: spin_unlock(&ses->chan_lock); } -/* - * update the iface for the channel if necessary. - * Must be called with chan_lock held. - */ +/* update the iface for the channel if necessary. */ void cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server) { diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 9a6ece66c4d3..db3695eddcf9 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -994,17 +994,17 @@ static int cifs_query_symlink(const unsigned int xid, } static int cifs_parse_reparse_point(struct cifs_sb_info *cifs_sb, + const char *full_path, struct kvec *rsp_iov, struct cifs_open_info_data *data) { struct reparse_data_buffer *buf; TRANSACT_IOCTL_RSP *io = rsp_iov->iov_base; - bool unicode = !!(io->hdr.Flags2 & SMBFLG2_UNICODE); u32 plen = le16_to_cpu(io->ByteCount); buf = (struct reparse_data_buffer *)((__u8 *)&io->hdr.Protocol + le32_to_cpu(io->DataOffset)); - return parse_reparse_point(buf, plen, cifs_sb, unicode, data); + return parse_reparse_point(buf, plen, cifs_sb, full_path, true, data); } static bool diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index e301349b0078..e836bc2193dd 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -63,12 +63,12 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov) return sym; } -int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path) +int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, + const char *full_path, char **path) { struct smb2_symlink_err_rsp *sym; unsigned int sub_offs, sub_len; unsigned int print_offs, print_len; - char *s; if (!cifs_sb || !iov || !iov->iov_base || !iov->iov_len || !path) return -EINVAL; @@ -86,15 +86,13 @@ int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + print_offs + print_len) return -EINVAL; - s = cifs_strndup_from_utf16((char *)sym->PathBuffer + sub_offs, sub_len, true, - cifs_sb->local_nls); - if (!s) - return -ENOMEM; - convert_delimiter(s, '/'); - cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, s); - - *path = s; - return 0; + return smb2_parse_native_symlink(path, + (char *)sym->PathBuffer + sub_offs, + sub_len, + true, + le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE, + full_path, + cifs_sb); } int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock, void *buf) @@ -126,6 +124,7 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 goto out; if (hdr->Status == STATUS_STOPPED_ON_SYMLINK) { rc = smb2_parse_symlink_response(oparms->cifs_sb, &err_iov, + oparms->path, &data->symlink_target); if (!rc) { memset(smb2_data, 0, sizeof(*smb2_data)); diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index e49d0c25eb03..a188908914fe 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -828,6 +828,7 @@ finished: static int parse_create_response(struct cifs_open_info_data *data, struct cifs_sb_info *cifs_sb, + const char *full_path, const struct kvec *iov) { struct smb2_create_rsp *rsp = iov->iov_base; @@ -841,6 +842,7 @@ static int parse_create_response(struct cifs_open_info_data *data, break; case STATUS_STOPPED_ON_SYMLINK: rc = smb2_parse_symlink_response(cifs_sb, iov, + full_path, &data->symlink_target); if (rc) return rc; @@ -930,14 +932,14 @@ int smb2_query_path_info(const unsigned int xid, switch (rc) { case 0: - rc = parse_create_response(data, cifs_sb, &out_iov[0]); + rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]); break; case -EOPNOTSUPP: /* * BB TODO: When support for special files added to Samba * re-verify this path. */ - rc = parse_create_response(data, cifs_sb, &out_iov[0]); + rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]); if (rc || !data->reparse_point) goto out; diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index fa96ebed8310..87cb1872db28 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2932,7 +2932,7 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, struct fsctl_get_dfs_referral_req *dfs_req = NULL; struct get_dfs_referral_rsp *dfs_rsp = NULL; u32 dfs_req_size = 0, dfs_rsp_size = 0; - int retry_count = 0; + int retry_once = 0; cifs_dbg(FYI, "%s: path: %s\n", __func__, search_name); @@ -2981,21 +2981,25 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, /* Path to resolve in an UTF-16 null-terminated string */ memcpy(dfs_req->RequestFileName, utf16_path, utf16_path_len); - do { + for (;;) { rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID, FSCTL_DFS_GET_REFERRALS, (char *)dfs_req, dfs_req_size, CIFSMaxBufSize, (char **)&dfs_rsp, &dfs_rsp_size); - if (!is_retryable_error(rc)) + if (fatal_signal_pending(current)) { + rc = -EINTR; + break; + } + if (!is_retryable_error(rc) || retry_once++) break; usleep_range(512, 2048); - } while (++retry_count < 5); + } if (!rc && !dfs_rsp) rc = -EIO; if (rc) { if (!is_retryable_error(rc) && rc != -ENOENT && rc != -EOPNOTSUPP) - cifs_tcon_dbg(VFS, "%s: ioctl error: rc=%d\n", __func__, rc); + cifs_tcon_dbg(FYI, "%s: ioctl error: rc=%d\n", __func__, rc); goto out; } diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 055236835537..010eae9d6c47 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -216,10 +216,9 @@ static int smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, struct TCP_Server_Info *server, bool from_reconnect) { - int rc = 0; - struct nls_table *nls_codepage = NULL; struct cifs_ses *ses; int xid; + int rc = 0; /* * SMB2s NegProt, SessSetup, Logoff do not have tcon yet so @@ -229,11 +228,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, if (tcon == NULL) return 0; - /* - * Need to also skip SMB2_IOCTL because it is used for checking nested dfs links in - * cifs_tree_connect(). - */ - if (smb2_command == SMB2_TREE_CONNECT || smb2_command == SMB2_IOCTL) + if (smb2_command == SMB2_TREE_CONNECT) return 0; spin_lock(&tcon->tc_lock); @@ -334,8 +329,6 @@ again: } spin_unlock(&server->srv_lock); - nls_codepage = ses->local_nls; - /* * need to prevent multiple threads trying to simultaneously * reconnect the same SMB session @@ -372,7 +365,7 @@ again: } } - rc = cifs_setup_session(0, ses, server, nls_codepage); + rc = cifs_setup_session(0, ses, server, ses->local_nls); if ((rc == -EACCES) || (rc == -EKEYEXPIRED) || (rc == -EKEYREVOKED)) { /* * Try alternate password for next reconnect (key rotation @@ -406,7 +399,7 @@ skip_sess_setup: if (tcon->use_persistent) tcon->need_reopen_files = true; - rc = cifs_tree_connect(0, tcon, nls_codepage); + rc = cifs_tree_connect(0, tcon); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); if (rc) { @@ -494,6 +487,7 @@ out: case SMB2_CHANGE_NOTIFY: case SMB2_QUERY_INFO: case SMB2_SET_INFO: + case SMB2_IOCTL: rc = -EAGAIN; } failed: @@ -1231,7 +1225,9 @@ SMB2_negotiate(const unsigned int xid, * SMB3.0 supports only 1 cipher and doesn't have a encryption neg context * Set the cipher type manually. */ - if (server->dialect == SMB30_PROT_ID && (server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION)) + if ((server->dialect == SMB30_PROT_ID || + server->dialect == SMB302_PROT_ID) && + (server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION)) server->cipher_type = SMB2_ENCRYPTION_AES128_CCM; security_blob = smb2_get_data_area_len(&blob_offset, &blob_length, diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index 71504b30909e..09349fa8da03 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -111,7 +111,14 @@ extern int smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const unsigned char *path, char *pbuf, unsigned int *pbytes_read); -int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path); +int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, + bool unicode, bool relative, + const char *full_path, + struct cifs_sb_info *cifs_sb); +int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, + const struct kvec *iov, + const char *full_path, + char **path); int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock, void *buf); extern int smb2_unlock_range(struct cifsFileInfo *cfile, diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index 0b52d22a91a0..12cbd3428a6d 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -44,6 +44,8 @@ EM(netfs_trace_tcon_ref_free_ipc, "FRE Ipc ") \ EM(netfs_trace_tcon_ref_free_ipc_fail, "FRE Ipc-F ") \ EM(netfs_trace_tcon_ref_free_reconnect_server, "FRE Reconn") \ + EM(netfs_trace_tcon_ref_get_cached_laundromat, "GET Ch-Lau") \ + EM(netfs_trace_tcon_ref_get_cached_lease_break, "GET Ch-Lea") \ EM(netfs_trace_tcon_ref_get_cancelled_close, "GET Cn-Cls") \ EM(netfs_trace_tcon_ref_get_dfs_refer, "GET DfsRef") \ EM(netfs_trace_tcon_ref_get_find, "GET Find ") \ @@ -52,6 +54,7 @@ EM(netfs_trace_tcon_ref_new, "NEW ") \ EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \ EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \ + EM(netfs_trace_tcon_ref_put_cached_close, "PUT Ch-Cls") \ EM(netfs_trace_tcon_ref_put_cancelled_close, "PUT Cn-Cls") \ EM(netfs_trace_tcon_ref_put_cancelled_close_fid, "PUT Cn-Fid") \ EM(netfs_trace_tcon_ref_put_cancelled_mid, "PUT Cn-Mid") \ diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h index 9f272cc8f566..3c7c706c797d 100644 --- a/fs/smb/common/smb2pdu.h +++ b/fs/smb/common/smb2pdu.h @@ -1552,6 +1552,15 @@ struct reparse_symlink_data_buffer { /* See MS-FSCC 2.1.2.6 and cifspdu.h for struct reparse_posix_data */ +/* For IO_REPARSE_TAG_LX_SYMLINK */ +struct reparse_wsl_symlink_data_buffer { + __le32 ReparseTag; + __le16 ReparseDataLength; + __u16 Reserved; + __le32 Flags; + __u8 PathBuffer[]; /* Variable Length UTF-8 string without nul-term */ +} __packed; + struct validate_negotiate_info_req { __le32 Capabilities; __u8 Guid[SMB2_CLIENT_GUID_SIZE]; |