diff options
author | Pavel Shilovsky <pshilov@microsoft.com> | 2016-11-04 14:50:31 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-01-06 04:40:16 -0500 |
commit | 48f9526f4dcb4b132fe0dc2450835311e3b013a6 (patch) | |
tree | 6cf318cad0862737ad185b101f0fbf1f62b432b8 | |
parent | 7aa58e7ad53bd9536aa49a18ccd0778c728bf57d (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.h | 3 | ||||
-rw-r--r-- | fs/cifs/cifsproto.h | 3 | ||||
-rw-r--r-- | fs/cifs/connect.c | 34 | ||||
-rw-r--r-- | fs/cifs/smb2pdu.c | 75 | ||||
-rw-r--r-- | fs/cifs/smb2proto.h | 1 |
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) | |||
849 | struct cifs_tcon { | 851 | struct 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); |
208 | extern void cifs_del_pending_open(struct cifs_pending_open *open); | 208 | extern void cifs_del_pending_open(struct cifs_pending_open *open); |
209 | extern void cifs_put_tcp_session(struct TCP_Server_Info *server, | ||
210 | int from_reconnect); | ||
211 | extern 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) |
211 | extern void cifs_dfs_release_automount_timer(void); | 214 | extern 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 | ||
2103 | static void | 2106 | void |
2104 | cifs_put_tcp_session(struct TCP_Server_Info *server) | 2107 | cifs_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 | ||
2607 | static void | 2627 | void |
2608 | cifs_put_tcon(struct cifs_tcon *tcon) | 2628 | cifs_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 | ||
1975 | void 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 | |||
1975 | int | 2023 | int |
1976 | SMB2_echo(struct TCP_Server_Info *server) | 2024 | SMB2_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, | |||
96 | extern int smb2_unlock_range(struct cifsFileInfo *cfile, | 96 | extern 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); |
98 | extern int smb2_push_mandatory_locks(struct cifsFileInfo *cfile); | 98 | extern int smb2_push_mandatory_locks(struct cifsFileInfo *cfile); |
99 | extern 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 |