aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPavel Shilovsky <pshilov@microsoft.com>2016-11-04 14:50:31 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-01-06 04:40:16 -0500
commit48f9526f4dcb4b132fe0dc2450835311e3b013a6 (patch)
tree6cf318cad0862737ad185b101f0fbf1f62b432b8
parent7aa58e7ad53bd9536aa49a18ccd0778c728bf57d (diff)
CIFS: Fix a possible memory corruption during reconnect
commit 53e0e11efe9289535b060a51d4cf37c25e0d0f2b upstream. We can not unlock/lock cifs_tcp_ses_lock while walking through ses and tcon lists because it can corrupt list iterator pointers and a tcon structure can be released if we don't hold an extra reference. Fix it by moving a reconnect process to a separate delayed work and acquiring a reference to every tcon that needs to be reconnected. Also do not send an echo request on newly established connections. Signed-off-by: Pavel Shilovsky <pshilov@microsoft.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--fs/cifs/cifsglob.h3
-rw-r--r--fs/cifs/cifsproto.h3
-rw-r--r--fs/cifs/connect.c34
-rw-r--r--fs/cifs/smb2pdu.c75
-rw-r--r--fs/cifs/smb2proto.h1
5 files changed, 85 insertions, 31 deletions
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 1f17f6bd7a60..64f5d8754fb5 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -646,6 +646,8 @@ struct TCP_Server_Info {
646 unsigned int max_read; 646 unsigned int max_read;
647 unsigned int max_write; 647 unsigned int max_write;
648 __u8 preauth_hash[512]; 648 __u8 preauth_hash[512];
649 struct delayed_work reconnect; /* reconnect workqueue job */
650 struct mutex reconnect_mutex; /* prevent simultaneous reconnects */
649#endif /* CONFIG_CIFS_SMB2 */ 651#endif /* CONFIG_CIFS_SMB2 */
650 unsigned long echo_interval; 652 unsigned long echo_interval;
651}; 653};
@@ -849,6 +851,7 @@ cap_unix(struct cifs_ses *ses)
849struct cifs_tcon { 851struct cifs_tcon {
850 struct list_head tcon_list; 852 struct list_head tcon_list;
851 int tc_count; 853 int tc_count;
854 struct list_head rlist; /* reconnect list */
852 struct list_head openFileList; 855 struct list_head openFileList;
853 spinlock_t open_file_lock; /* protects list above */ 856 spinlock_t open_file_lock; /* protects list above */
854 struct cifs_ses *ses; /* pointer to session associated with */ 857 struct cifs_ses *ses; /* pointer to session associated with */
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index ced0e42ce460..cd8025a249bb 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -206,6 +206,9 @@ extern void cifs_add_pending_open_locked(struct cifs_fid *fid,
206 struct tcon_link *tlink, 206 struct tcon_link *tlink,
207 struct cifs_pending_open *open); 207 struct cifs_pending_open *open);
208extern void cifs_del_pending_open(struct cifs_pending_open *open); 208extern void cifs_del_pending_open(struct cifs_pending_open *open);
209extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
210 int from_reconnect);
211extern void cifs_put_tcon(struct cifs_tcon *tcon);
209 212
210#if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) 213#if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL)
211extern void cifs_dfs_release_automount_timer(void); 214extern void cifs_dfs_release_automount_timer(void);
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 4547aeddd12b..893be0722643 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -52,6 +52,9 @@
52#include "nterr.h" 52#include "nterr.h"
53#include "rfc1002pdu.h" 53#include "rfc1002pdu.h"
54#include "fscache.h" 54#include "fscache.h"
55#ifdef CONFIG_CIFS_SMB2
56#include "smb2proto.h"
57#endif
55 58
56#define CIFS_PORT 445 59#define CIFS_PORT 445
57#define RFC1001_PORT 139 60#define RFC1001_PORT 139
@@ -2100,8 +2103,8 @@ cifs_find_tcp_session(struct smb_vol *vol)
2100 return NULL; 2103 return NULL;
2101} 2104}
2102 2105
2103static void 2106void
2104cifs_put_tcp_session(struct TCP_Server_Info *server) 2107cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
2105{ 2108{
2106 struct task_struct *task; 2109 struct task_struct *task;
2107 2110
@@ -2118,6 +2121,19 @@ cifs_put_tcp_session(struct TCP_Server_Info *server)
2118 2121
2119 cancel_delayed_work_sync(&server->echo); 2122 cancel_delayed_work_sync(&server->echo);
2120 2123
2124#ifdef CONFIG_CIFS_SMB2
2125 if (from_reconnect)
2126 /*
2127 * Avoid deadlock here: reconnect work calls
2128 * cifs_put_tcp_session() at its end. Need to be sure
2129 * that reconnect work does nothing with server pointer after
2130 * that step.
2131 */
2132 cancel_delayed_work(&server->reconnect);
2133 else
2134 cancel_delayed_work_sync(&server->reconnect);
2135#endif
2136
2121 spin_lock(&GlobalMid_Lock); 2137 spin_lock(&GlobalMid_Lock);
2122 server->tcpStatus = CifsExiting; 2138 server->tcpStatus = CifsExiting;
2123 spin_unlock(&GlobalMid_Lock); 2139 spin_unlock(&GlobalMid_Lock);
@@ -2182,6 +2198,10 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
2182 INIT_LIST_HEAD(&tcp_ses->tcp_ses_list); 2198 INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
2183 INIT_LIST_HEAD(&tcp_ses->smb_ses_list); 2199 INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
2184 INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request); 2200 INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
2201#ifdef CONFIG_CIFS_SMB2
2202 INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
2203 mutex_init(&tcp_ses->reconnect_mutex);
2204#endif
2185 memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr, 2205 memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr,
2186 sizeof(tcp_ses->srcaddr)); 2206 sizeof(tcp_ses->srcaddr));
2187 memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr, 2207 memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr,
@@ -2340,7 +2360,7 @@ cifs_put_smb_ses(struct cifs_ses *ses)
2340 spin_unlock(&cifs_tcp_ses_lock); 2360 spin_unlock(&cifs_tcp_ses_lock);
2341 2361
2342 sesInfoFree(ses); 2362 sesInfoFree(ses);
2343 cifs_put_tcp_session(server); 2363 cifs_put_tcp_session(server, 0);
2344} 2364}
2345 2365
2346#ifdef CONFIG_KEYS 2366#ifdef CONFIG_KEYS
@@ -2514,7 +2534,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
2514 mutex_unlock(&ses->session_mutex); 2534 mutex_unlock(&ses->session_mutex);
2515 2535
2516 /* existing SMB ses has a server reference already */ 2536 /* existing SMB ses has a server reference already */
2517 cifs_put_tcp_session(server); 2537 cifs_put_tcp_session(server, 0);
2518 free_xid(xid); 2538 free_xid(xid);
2519 return ses; 2539 return ses;
2520 } 2540 }
@@ -2604,7 +2624,7 @@ cifs_find_tcon(struct cifs_ses *ses, const char *unc)
2604 return NULL; 2624 return NULL;
2605} 2625}
2606 2626
2607static void 2627void
2608cifs_put_tcon(struct cifs_tcon *tcon) 2628cifs_put_tcon(struct cifs_tcon *tcon)
2609{ 2629{
2610 unsigned int xid; 2630 unsigned int xid;
@@ -3792,7 +3812,7 @@ mount_fail_check:
3792 else if (ses) 3812 else if (ses)
3793 cifs_put_smb_ses(ses); 3813 cifs_put_smb_ses(ses);
3794 else 3814 else
3795 cifs_put_tcp_session(server); 3815 cifs_put_tcp_session(server, 0);
3796 bdi_destroy(&cifs_sb->bdi); 3816 bdi_destroy(&cifs_sb->bdi);
3797 } 3817 }
3798 3818
@@ -4103,7 +4123,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
4103 ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info); 4123 ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);
4104 if (IS_ERR(ses)) { 4124 if (IS_ERR(ses)) {
4105 tcon = (struct cifs_tcon *)ses; 4125 tcon = (struct cifs_tcon *)ses;
4106 cifs_put_tcp_session(master_tcon->ses->server); 4126 cifs_put_tcp_session(master_tcon->ses->server, 0);
4107 goto out; 4127 goto out;
4108 } 4128 }
4109 4129
diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
index 5ca5ea4668a1..eb3ac14ed466 100644
--- a/fs/cifs/smb2pdu.c
+++ b/fs/cifs/smb2pdu.c
@@ -1972,6 +1972,54 @@ smb2_echo_callback(struct mid_q_entry *mid)
1972 add_credits(server, credits_received, CIFS_ECHO_OP); 1972 add_credits(server, credits_received, CIFS_ECHO_OP);
1973} 1973}
1974 1974
1975void smb2_reconnect_server(struct work_struct *work)
1976{
1977 struct TCP_Server_Info *server = container_of(work,
1978 struct TCP_Server_Info, reconnect.work);
1979 struct cifs_ses *ses;
1980 struct cifs_tcon *tcon, *tcon2;
1981 struct list_head tmp_list;
1982 int tcon_exist = false;
1983
1984 /* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
1985 mutex_lock(&server->reconnect_mutex);
1986
1987 INIT_LIST_HEAD(&tmp_list);
1988 cifs_dbg(FYI, "Need negotiate, reconnecting tcons\n");
1989
1990 spin_lock(&cifs_tcp_ses_lock);
1991 list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
1992 list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
1993 if (tcon->need_reconnect) {
1994 tcon->tc_count++;
1995 list_add_tail(&tcon->rlist, &tmp_list);
1996 tcon_exist = true;
1997 }
1998 }
1999 }
2000 /*
2001 * Get the reference to server struct to be sure that the last call of
2002 * cifs_put_tcon() in the loop below won't release the server pointer.
2003 */
2004 if (tcon_exist)
2005 server->srv_count++;
2006
2007 spin_unlock(&cifs_tcp_ses_lock);
2008
2009 list_for_each_entry_safe(tcon, tcon2, &tmp_list, rlist) {
2010 smb2_reconnect(SMB2_ECHO, tcon);
2011 list_del_init(&tcon->rlist);
2012 cifs_put_tcon(tcon);
2013 }
2014
2015 cifs_dbg(FYI, "Reconnecting tcons finished\n");
2016 mutex_unlock(&server->reconnect_mutex);
2017
2018 /* now we can safely release srv struct */
2019 if (tcon_exist)
2020 cifs_put_tcp_session(server, 1);
2021}
2022
1975int 2023int
1976SMB2_echo(struct TCP_Server_Info *server) 2024SMB2_echo(struct TCP_Server_Info *server)
1977{ 2025{
@@ -1984,32 +2032,11 @@ SMB2_echo(struct TCP_Server_Info *server)
1984 cifs_dbg(FYI, "In echo request\n"); 2032 cifs_dbg(FYI, "In echo request\n");
1985 2033
1986 if (server->tcpStatus == CifsNeedNegotiate) { 2034 if (server->tcpStatus == CifsNeedNegotiate) {
1987 struct list_head *tmp, *tmp2; 2035 /* No need to send echo on newly established connections */
1988 struct cifs_ses *ses; 2036 queue_delayed_work(cifsiod_wq, &server->reconnect, 0);
1989 struct cifs_tcon *tcon; 2037 return rc;
1990
1991 cifs_dbg(FYI, "Need negotiate, reconnecting tcons\n");
1992 spin_lock(&cifs_tcp_ses_lock);
1993 list_for_each(tmp, &server->smb_ses_list) {
1994 ses = list_entry(tmp, struct cifs_ses, smb_ses_list);
1995 list_for_each(tmp2, &ses->tcon_list) {
1996 tcon = list_entry(tmp2, struct cifs_tcon,
1997 tcon_list);
1998 /* add check for persistent handle reconnect */
1999 if (tcon && tcon->need_reconnect) {
2000 spin_unlock(&cifs_tcp_ses_lock);
2001 rc = smb2_reconnect(SMB2_ECHO, tcon);
2002 spin_lock(&cifs_tcp_ses_lock);
2003 }
2004 }
2005 }
2006 spin_unlock(&cifs_tcp_ses_lock);
2007 } 2038 }
2008 2039
2009 /* if no session, renegotiate failed above */
2010 if (server->tcpStatus == CifsNeedNegotiate)
2011 return -EIO;
2012
2013 rc = small_smb2_init(SMB2_ECHO, NULL, (void **)&req); 2040 rc = small_smb2_init(SMB2_ECHO, NULL, (void **)&req);
2014 if (rc) 2041 if (rc)
2015 return rc; 2042 return rc;
diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
index eb2cde2f64ba..f2d511a6971b 100644
--- a/fs/cifs/smb2proto.h
+++ b/fs/cifs/smb2proto.h
@@ -96,6 +96,7 @@ extern int smb2_open_file(const unsigned int xid,
96extern int smb2_unlock_range(struct cifsFileInfo *cfile, 96extern int smb2_unlock_range(struct cifsFileInfo *cfile,
97 struct file_lock *flock, const unsigned int xid); 97 struct file_lock *flock, const unsigned int xid);
98extern int smb2_push_mandatory_locks(struct cifsFileInfo *cfile); 98extern int smb2_push_mandatory_locks(struct cifsFileInfo *cfile);
99extern void smb2_reconnect_server(struct work_struct *work);
99 100
100/* 101/*
101 * SMB2 Worker functions - most of protocol specific implementation details 102 * SMB2 Worker functions - most of protocol specific implementation details