* [PATCH v2] CIFS: Fix a possible memory corruption during reconnect
@ 2016-11-10 23:31 Pavel Shilovsky
[not found] ` <1478820683-2954-1-git-send-email-pshilov-0li6OtcxBFHby3iVrkZq2A@public.gmane.org>
0 siblings, 1 reply; 8+ messages in thread
From: Pavel Shilovsky @ 2016-11-10 23:31 UTC (permalink / raw)
To: linux-cifs-u79uwXL29TY76Z2rM5mHXA
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.
CC: Stable <stable-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
Signed-off-by: Pavel Shilovsky <pshilov-0li6OtcxBFHby3iVrkZq2A@public.gmane.org>
---
fs/cifs/cifsglob.h | 3 +++
fs/cifs/cifsproto.h | 3 +++
fs/cifs/connect.c | 34 +++++++++++++++++++-----
fs/cifs/smb2pdu.c | 75 ++++++++++++++++++++++++++++++++++++-----------------
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 1f17f6b..6dcefc8 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -632,6 +632,7 @@ struct TCP_Server_Info {
bool sec_mskerberos; /* supports legacy MS Kerberos */
bool large_buf; /* is current buffer large? */
struct delayed_work echo; /* echo ping workqueue job */
+ struct delayed_work reconnect; /* reconnect workqueue job */
char *smallbuf; /* pointer to current "small" buffer */
char *bigbuf; /* pointer to current "big" buffer */
unsigned int total_read; /* total amount of data read in this pass */
@@ -648,6 +649,7 @@ struct TCP_Server_Info {
__u8 preauth_hash[512];
#endif /* CONFIG_CIFS_SMB2 */
unsigned long echo_interval;
+ struct mutex reconnect_mutex; /* prevent simultaneous reconnects */
};
static inline unsigned int
@@ -850,6 +852,7 @@ struct cifs_tcon {
struct list_head tcon_list;
int tc_count;
struct list_head openFileList;
+ struct list_head rlist; /* reconnect list */
spinlock_t open_file_lock; /* protects list above */
struct cifs_ses *ses; /* pointer to session associated with */
char treeName[MAX_TREE_SIZE + 1]; /* UNC name of resource in ASCII */
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index ced0e42..cd8025a 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,
struct tcon_link *tlink,
struct cifs_pending_open *open);
extern void cifs_del_pending_open(struct cifs_pending_open *open);
+extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
+ int from_reconnect);
+extern void cifs_put_tcon(struct cifs_tcon *tcon);
#if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL)
extern void cifs_dfs_release_automount_timer(void);
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 4547aed..75503af 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -52,6 +52,9 @@
#include "nterr.h"
#include "rfc1002pdu.h"
#include "fscache.h"
+#ifdef CONFIG_CIFS_SMB2
+#include "smb2proto.h"
+#endif
#define CIFS_PORT 445
#define RFC1001_PORT 139
@@ -2100,8 +2103,8 @@ cifs_find_tcp_session(struct smb_vol *vol)
return NULL;
}
-static void
-cifs_put_tcp_session(struct TCP_Server_Info *server)
+void
+cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
{
struct task_struct *task;
@@ -2118,6 +2121,19 @@ cifs_put_tcp_session(struct TCP_Server_Info *server)
cancel_delayed_work_sync(&server->echo);
+#ifdef CONFIG_CIFS_SMB2
+ if (from_reconnect)
+ /*
+ * Avoid deadlock here: reconnect work calls
+ * cifs_put_tcp_session() at its end. Need to be sure
+ * that reconnect work does nothing with server pointer after
+ * that step.
+ */
+ cancel_delayed_work(&server->reconnect);
+ else
+ cancel_delayed_work_sync(&server->reconnect);
+#endif
+
spin_lock(&GlobalMid_Lock);
server->tcpStatus = CifsExiting;
spin_unlock(&GlobalMid_Lock);
@@ -2171,6 +2187,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
init_waitqueue_head(&tcp_ses->request_q);
INIT_LIST_HEAD(&tcp_ses->pending_mid_q);
mutex_init(&tcp_ses->srv_mutex);
+ mutex_init(&tcp_ses->reconnect_mutex);
memcpy(tcp_ses->workstation_RFC1001_name,
volume_info->source_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL);
memcpy(tcp_ses->server_RFC1001_name,
@@ -2182,6 +2199,9 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
+#ifdef CONFIG_CIFS_SMB2
+ INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
+#endif
memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr,
sizeof(tcp_ses->srcaddr));
memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr,
@@ -2340,7 +2360,7 @@ cifs_put_smb_ses(struct cifs_ses *ses)
spin_unlock(&cifs_tcp_ses_lock);
sesInfoFree(ses);
- cifs_put_tcp_session(server);
+ cifs_put_tcp_session(server, 0);
}
#ifdef CONFIG_KEYS
@@ -2514,7 +2534,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
mutex_unlock(&ses->session_mutex);
/* existing SMB ses has a server reference already */
- cifs_put_tcp_session(server);
+ cifs_put_tcp_session(server, 0);
free_xid(xid);
return ses;
}
@@ -2604,7 +2624,7 @@ cifs_find_tcon(struct cifs_ses *ses, const char *unc)
return NULL;
}
-static void
+void
cifs_put_tcon(struct cifs_tcon *tcon)
{
unsigned int xid;
@@ -3792,7 +3812,7 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
else if (ses)
cifs_put_smb_ses(ses);
else
- cifs_put_tcp_session(server);
+ cifs_put_tcp_session(server, 0);
bdi_destroy(&cifs_sb->bdi);
}
@@ -4103,7 +4123,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);
if (IS_ERR(ses)) {
tcon = (struct cifs_tcon *)ses;
- cifs_put_tcp_session(master_tcon->ses->server);
+ cifs_put_tcp_session(master_tcon->ses->server, 0);
goto out;
}
diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
index 5ca5ea46..950dbf7 100644
--- a/fs/cifs/smb2pdu.c
+++ b/fs/cifs/smb2pdu.c
@@ -1972,6 +1972,54 @@ smb2_echo_callback(struct mid_q_entry *mid)
add_credits(server, credits_received, CIFS_ECHO_OP);
}
+void smb2_reconnect_server(struct work_struct *work)
+{
+ struct TCP_Server_Info *server = container_of(work,
+ struct TCP_Server_Info, reconnect.work);
+ struct cifs_ses *ses;
+ struct cifs_tcon *tcon, *tcon2;
+ struct list_head tmp_list;
+ int tcon_exist = false;
+
+ /* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
+ mutex_lock(&server->reconnect_mutex);
+
+ INIT_LIST_HEAD(&tmp_list);
+ cifs_dbg(FYI, "Need negotiate, reconnecting tcons\n");
+
+ spin_lock(&cifs_tcp_ses_lock);
+ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+ list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
+ if (tcon && tcon->need_reconnect) {
+ tcon->tc_count++;
+ list_add_tail(&tcon->rlist, &tmp_list);
+ tcon_exist = true;
+ }
+ }
+ }
+ /*
+ * Get the reference to server struct to be sure that the last call of
+ * cifs_put_tcon() in the loop below won't release the server pointer.
+ */
+ if (tcon_exist)
+ server->srv_count++;
+
+ spin_unlock(&cifs_tcp_ses_lock);
+
+ list_for_each_entry_safe(tcon, tcon2, &tmp_list, rlist) {
+ smb2_reconnect(SMB2_ECHO, tcon);
+ list_del_init(&tcon->rlist);
+ cifs_put_tcon(tcon);
+ }
+
+ cifs_dbg(FYI, "Reconnecting tcons finished\n");
+ mutex_unlock(&server->reconnect_mutex);
+
+ /* now we can safely release srv struct */
+ if (tcon_exist)
+ cifs_put_tcp_session(server, 1);
+}
+
int
SMB2_echo(struct TCP_Server_Info *server)
{
@@ -1984,32 +2032,11 @@ SMB2_echo(struct TCP_Server_Info *server)
cifs_dbg(FYI, "In echo request\n");
if (server->tcpStatus == CifsNeedNegotiate) {
- struct list_head *tmp, *tmp2;
- struct cifs_ses *ses;
- struct cifs_tcon *tcon;
-
- cifs_dbg(FYI, "Need negotiate, reconnecting tcons\n");
- spin_lock(&cifs_tcp_ses_lock);
- list_for_each(tmp, &server->smb_ses_list) {
- ses = list_entry(tmp, struct cifs_ses, smb_ses_list);
- list_for_each(tmp2, &ses->tcon_list) {
- tcon = list_entry(tmp2, struct cifs_tcon,
- tcon_list);
- /* add check for persistent handle reconnect */
- if (tcon && tcon->need_reconnect) {
- spin_unlock(&cifs_tcp_ses_lock);
- rc = smb2_reconnect(SMB2_ECHO, tcon);
- spin_lock(&cifs_tcp_ses_lock);
- }
- }
- }
- spin_unlock(&cifs_tcp_ses_lock);
+ /* No need to send echo on newly established connections */
+ mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
+ return rc;
}
- /* if no session, renegotiate failed above */
- if (server->tcpStatus == CifsNeedNegotiate)
- return -EIO;
-
rc = small_smb2_init(SMB2_ECHO, NULL, (void **)&req);
if (rc)
return rc;
diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
index eb2cde2..f2d511a 100644
--- a/fs/cifs/smb2proto.h
+++ b/fs/cifs/smb2proto.h
@@ -96,6 +96,7 @@ extern int smb2_open_file(const unsigned int xid,
extern int smb2_unlock_range(struct cifsFileInfo *cfile,
struct file_lock *flock, const unsigned int xid);
extern int smb2_push_mandatory_locks(struct cifsFileInfo *cfile);
+extern void smb2_reconnect_server(struct work_struct *work);
/*
* SMB2 Worker functions - most of protocol specific implementation details
--
2.7.4
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2] CIFS: Fix a possible memory corruption during reconnect
[not found] ` <1478820683-2954-1-git-send-email-pshilov-0li6OtcxBFHby3iVrkZq2A@public.gmane.org>
@ 2016-11-23 20:07 ` Pavel Shilovsky
[not found] ` <CAKywueR-0h7i2-8OVaC8+KKh1Yjq_otdpuFu582bW=0xQ=+E9Q-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2016-11-30 10:00 ` Sachin Prabhu
1 sibling, 1 reply; 8+ messages in thread
From: Pavel Shilovsky @ 2016-11-23 20:07 UTC (permalink / raw)
To: Sachin Prabhu, Aurélien Aptel, Jeff Layton; +Cc: linux-cifs
2016-11-10 15:31 GMT-08:00 Pavel Shilovsky <piastryyy-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>:
> 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.
>
> CC: Stable <stable-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
> Signed-off-by: Pavel Shilovsky <pshilov-0li6OtcxBFHby3iVrkZq2A@public.gmane.org>
> ---
> fs/cifs/cifsglob.h | 3 +++
> fs/cifs/cifsproto.h | 3 +++
> fs/cifs/connect.c | 34 +++++++++++++++++++-----
> fs/cifs/smb2pdu.c | 75 ++++++++++++++++++++++++++++++++++++-----------------
> 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 1f17f6b..6dcefc8 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -632,6 +632,7 @@ struct TCP_Server_Info {
> bool sec_mskerberos; /* supports legacy MS Kerberos */
> bool large_buf; /* is current buffer large? */
> struct delayed_work echo; /* echo ping workqueue job */
> + struct delayed_work reconnect; /* reconnect workqueue job */
> char *smallbuf; /* pointer to current "small" buffer */
> char *bigbuf; /* pointer to current "big" buffer */
> unsigned int total_read; /* total amount of data read in this pass */
> @@ -648,6 +649,7 @@ struct TCP_Server_Info {
> __u8 preauth_hash[512];
> #endif /* CONFIG_CIFS_SMB2 */
> unsigned long echo_interval;
> + struct mutex reconnect_mutex; /* prevent simultaneous reconnects */
> };
>
> static inline unsigned int
> @@ -850,6 +852,7 @@ struct cifs_tcon {
> struct list_head tcon_list;
> int tc_count;
> struct list_head openFileList;
> + struct list_head rlist; /* reconnect list */
> spinlock_t open_file_lock; /* protects list above */
> struct cifs_ses *ses; /* pointer to session associated with */
> char treeName[MAX_TREE_SIZE + 1]; /* UNC name of resource in ASCII */
> diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
> index ced0e42..cd8025a 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,
> struct tcon_link *tlink,
> struct cifs_pending_open *open);
> extern void cifs_del_pending_open(struct cifs_pending_open *open);
> +extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
> + int from_reconnect);
> +extern void cifs_put_tcon(struct cifs_tcon *tcon);
>
> #if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL)
> extern void cifs_dfs_release_automount_timer(void);
> diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
> index 4547aed..75503af 100644
> --- a/fs/cifs/connect.c
> +++ b/fs/cifs/connect.c
> @@ -52,6 +52,9 @@
> #include "nterr.h"
> #include "rfc1002pdu.h"
> #include "fscache.h"
> +#ifdef CONFIG_CIFS_SMB2
> +#include "smb2proto.h"
> +#endif
>
> #define CIFS_PORT 445
> #define RFC1001_PORT 139
> @@ -2100,8 +2103,8 @@ cifs_find_tcp_session(struct smb_vol *vol)
> return NULL;
> }
>
> -static void
> -cifs_put_tcp_session(struct TCP_Server_Info *server)
> +void
> +cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
> {
> struct task_struct *task;
>
> @@ -2118,6 +2121,19 @@ cifs_put_tcp_session(struct TCP_Server_Info *server)
>
> cancel_delayed_work_sync(&server->echo);
>
> +#ifdef CONFIG_CIFS_SMB2
> + if (from_reconnect)
> + /*
> + * Avoid deadlock here: reconnect work calls
> + * cifs_put_tcp_session() at its end. Need to be sure
> + * that reconnect work does nothing with server pointer after
> + * that step.
> + */
> + cancel_delayed_work(&server->reconnect);
> + else
> + cancel_delayed_work_sync(&server->reconnect);
> +#endif
> +
> spin_lock(&GlobalMid_Lock);
> server->tcpStatus = CifsExiting;
> spin_unlock(&GlobalMid_Lock);
> @@ -2171,6 +2187,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
> init_waitqueue_head(&tcp_ses->request_q);
> INIT_LIST_HEAD(&tcp_ses->pending_mid_q);
> mutex_init(&tcp_ses->srv_mutex);
> + mutex_init(&tcp_ses->reconnect_mutex);
> memcpy(tcp_ses->workstation_RFC1001_name,
> volume_info->source_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL);
> memcpy(tcp_ses->server_RFC1001_name,
> @@ -2182,6 +2199,9 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
> INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
> INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
> INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
> +#ifdef CONFIG_CIFS_SMB2
> + INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
> +#endif
> memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr,
> sizeof(tcp_ses->srcaddr));
> memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr,
> @@ -2340,7 +2360,7 @@ cifs_put_smb_ses(struct cifs_ses *ses)
> spin_unlock(&cifs_tcp_ses_lock);
>
> sesInfoFree(ses);
> - cifs_put_tcp_session(server);
> + cifs_put_tcp_session(server, 0);
> }
>
> #ifdef CONFIG_KEYS
> @@ -2514,7 +2534,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
> mutex_unlock(&ses->session_mutex);
>
> /* existing SMB ses has a server reference already */
> - cifs_put_tcp_session(server);
> + cifs_put_tcp_session(server, 0);
> free_xid(xid);
> return ses;
> }
> @@ -2604,7 +2624,7 @@ cifs_find_tcon(struct cifs_ses *ses, const char *unc)
> return NULL;
> }
>
> -static void
> +void
> cifs_put_tcon(struct cifs_tcon *tcon)
> {
> unsigned int xid;
> @@ -3792,7 +3812,7 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
> else if (ses)
> cifs_put_smb_ses(ses);
> else
> - cifs_put_tcp_session(server);
> + cifs_put_tcp_session(server, 0);
> bdi_destroy(&cifs_sb->bdi);
> }
>
> @@ -4103,7 +4123,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
> ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);
> if (IS_ERR(ses)) {
> tcon = (struct cifs_tcon *)ses;
> - cifs_put_tcp_session(master_tcon->ses->server);
> + cifs_put_tcp_session(master_tcon->ses->server, 0);
> goto out;
> }
>
> diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
> index 5ca5ea46..950dbf7 100644
> --- a/fs/cifs/smb2pdu.c
> +++ b/fs/cifs/smb2pdu.c
> @@ -1972,6 +1972,54 @@ smb2_echo_callback(struct mid_q_entry *mid)
> add_credits(server, credits_received, CIFS_ECHO_OP);
> }
>
> +void smb2_reconnect_server(struct work_struct *work)
> +{
> + struct TCP_Server_Info *server = container_of(work,
> + struct TCP_Server_Info, reconnect.work);
> + struct cifs_ses *ses;
> + struct cifs_tcon *tcon, *tcon2;
> + struct list_head tmp_list;
> + int tcon_exist = false;
> +
> + /* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
> + mutex_lock(&server->reconnect_mutex);
> +
> + INIT_LIST_HEAD(&tmp_list);
> + cifs_dbg(FYI, "Need negotiate, reconnecting tcons\n");
> +
> + spin_lock(&cifs_tcp_ses_lock);
> + list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
> + list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
> + if (tcon && tcon->need_reconnect) {
> + tcon->tc_count++;
> + list_add_tail(&tcon->rlist, &tmp_list);
> + tcon_exist = true;
> + }
> + }
> + }
> + /*
> + * Get the reference to server struct to be sure that the last call of
> + * cifs_put_tcon() in the loop below won't release the server pointer.
> + */
> + if (tcon_exist)
> + server->srv_count++;
> +
> + spin_unlock(&cifs_tcp_ses_lock);
> +
> + list_for_each_entry_safe(tcon, tcon2, &tmp_list, rlist) {
> + smb2_reconnect(SMB2_ECHO, tcon);
> + list_del_init(&tcon->rlist);
> + cifs_put_tcon(tcon);
> + }
> +
> + cifs_dbg(FYI, "Reconnecting tcons finished\n");
> + mutex_unlock(&server->reconnect_mutex);
> +
> + /* now we can safely release srv struct */
> + if (tcon_exist)
> + cifs_put_tcp_session(server, 1);
> +}
> +
> int
> SMB2_echo(struct TCP_Server_Info *server)
> {
> @@ -1984,32 +2032,11 @@ SMB2_echo(struct TCP_Server_Info *server)
> cifs_dbg(FYI, "In echo request\n");
>
> if (server->tcpStatus == CifsNeedNegotiate) {
> - struct list_head *tmp, *tmp2;
> - struct cifs_ses *ses;
> - struct cifs_tcon *tcon;
> -
> - cifs_dbg(FYI, "Need negotiate, reconnecting tcons\n");
> - spin_lock(&cifs_tcp_ses_lock);
> - list_for_each(tmp, &server->smb_ses_list) {
> - ses = list_entry(tmp, struct cifs_ses, smb_ses_list);
> - list_for_each(tmp2, &ses->tcon_list) {
> - tcon = list_entry(tmp2, struct cifs_tcon,
> - tcon_list);
> - /* add check for persistent handle reconnect */
> - if (tcon && tcon->need_reconnect) {
> - spin_unlock(&cifs_tcp_ses_lock);
> - rc = smb2_reconnect(SMB2_ECHO, tcon);
> - spin_lock(&cifs_tcp_ses_lock);
> - }
> - }
> - }
> - spin_unlock(&cifs_tcp_ses_lock);
> + /* No need to send echo on newly established connections */
> + mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
> + return rc;
> }
>
> - /* if no session, renegotiate failed above */
> - if (server->tcpStatus == CifsNeedNegotiate)
> - return -EIO;
> -
> rc = small_smb2_init(SMB2_ECHO, NULL, (void **)&req);
> if (rc)
> return rc;
> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
> index eb2cde2..f2d511a 100644
> --- a/fs/cifs/smb2proto.h
> +++ b/fs/cifs/smb2proto.h
> @@ -96,6 +96,7 @@ extern int smb2_open_file(const unsigned int xid,
> extern int smb2_unlock_range(struct cifsFileInfo *cfile,
> struct file_lock *flock, const unsigned int xid);
> extern int smb2_push_mandatory_locks(struct cifsFileInfo *cfile);
> +extern void smb2_reconnect_server(struct work_struct *work);
>
> /*
> * SMB2 Worker functions - most of protocol specific implementation details
> --
> 2.7.4
>
Any comments on this patch?
--
Best regards,
Pavel Shilovsky
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] CIFS: Fix a possible memory corruption during reconnect
[not found] ` <CAKywueR-0h7i2-8OVaC8+KKh1Yjq_otdpuFu582bW=0xQ=+E9Q-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2016-11-24 16:43 ` Aurélien Aptel
[not found] ` <mps8ts8u8u1.fsf-IBi9RG/b67k@public.gmane.org>
0 siblings, 1 reply; 8+ messages in thread
From: Aurélien Aptel @ 2016-11-24 16:43 UTC (permalink / raw)
To: Pavel Shilovsky, Sachin Prabhu, Jeff Layton; +Cc: linux-cifs
Hi Pavel,
Pavel Shilovsky <piastryyy-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> writes:
> 2016-11-10 15:31 GMT-08:00 Pavel Shilovsky <piastryyy-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>:
>> 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.
I don't fully understand what's going on here but I've successfully
tested your patch.
I've applied your patch and triggered a reconnexion on a smb2 mount by
virtually unplugging/waiting/replugging the network cable (via qemu
set_link <name> on/off). I did not notice any issues.
Let us know if you have a better scenario to test this or a way to
reproduce the previous issue.
Tested-by: Aurelien Aptel <aaptel-IBi9RG/b67k@public.gmane.org>
--
Aurélien Aptel / SUSE Labs Samba Team
GPG: 1839 CB5F 9F5B FB9B AA97 8C99 03C8 A49B 521B D5D3
SUSE Linux GmbH, Maxfeldstraße 5, 90409 Nürnberg, Germany
GF: Felix Imendörffer, Jane Smithard, Graham Norton, HRB 21284 (AG Nürnberg)
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] CIFS: Fix a possible memory corruption during reconnect
[not found] ` <mps8ts8u8u1.fsf-IBi9RG/b67k@public.gmane.org>
@ 2016-11-28 19:53 ` Pavel Shilovsky
0 siblings, 0 replies; 8+ messages in thread
From: Pavel Shilovsky @ 2016-11-28 19:53 UTC (permalink / raw)
To: Aurélien Aptel; +Cc: Sachin Prabhu, Jeff Layton, linux-cifs
2016-11-24 8:43 GMT-08:00 Aurélien Aptel <aaptel-IBi9RG/b67k@public.gmane.org>:
> Hi Pavel,
>
> Pavel Shilovsky <piastryyy-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> writes:
>> 2016-11-10 15:31 GMT-08:00 Pavel Shilovsky <piastryyy-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>:
>>> 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.
>
> I don't fully understand what's going on here but I've successfully
> tested your patch.
>
> I've applied your patch and triggered a reconnexion on a smb2 mount by
> virtually unplugging/waiting/replugging the network cable (via qemu
> set_link <name> on/off). I did not notice any issues.
>
> Let us know if you have a better scenario to test this or a way to
> reproduce the previous issue.
>
> Tested-by: Aurelien Aptel <aaptel-IBi9RG/b67k@public.gmane.org>
>
> --
> Aurélien Aptel / SUSE Labs Samba Team
> GPG: 1839 CB5F 9F5B FB9B AA97 8C99 03C8 A49B 521B D5D3
> SUSE Linux GmbH, Maxfeldstraße 5, 90409 Nürnberg, Germany
> GF: Felix Imendörffer, Jane Smithard, Graham Norton, HRB 21284 (AG Nürnberg)
Thank you for testing this!
I don't have a scripted reproducer but sending something wrong to the
server during mounting was triggering this issue. For example, if we
send a wrong SMB2 header on Create request the kernel crashes after
several mount attempts.
--
Best regards,
Pavel Shilovsky
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] CIFS: Fix a possible memory corruption during reconnect
[not found] ` <1478820683-2954-1-git-send-email-pshilov-0li6OtcxBFHby3iVrkZq2A@public.gmane.org>
2016-11-23 20:07 ` Pavel Shilovsky
@ 2016-11-30 10:00 ` Sachin Prabhu
[not found] ` <1480500055.3937.8.camel-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
1 sibling, 1 reply; 8+ messages in thread
From: Sachin Prabhu @ 2016-11-30 10:00 UTC (permalink / raw)
To: Pavel Shilovsky, linux-cifs-u79uwXL29TY76Z2rM5mHXA
On Thu, 2016-11-10 at 15:31 -0800, Pavel Shilovsky wrote:
> 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.
>
> CC: Stable <stable-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
> Signed-off-by: Pavel Shilovsky <pshilov-0li6OtcxBFHby3iVrkZq2A@public.gmane.org>
> ---
> fs/cifs/cifsglob.h | 3 +++
> fs/cifs/cifsproto.h | 3 +++
> fs/cifs/connect.c | 34 +++++++++++++++++++-----
> fs/cifs/smb2pdu.c | 75 ++++++++++++++++++++++++++++++++++++-------
> ----------
> 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 1f17f6b..6dcefc8 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -632,6 +632,7 @@ struct TCP_Server_Info {
> bool sec_mskerberos; /* supports
> legacy MS Kerberos */
> bool large_buf; /* is current buffer
> large? */
> struct delayed_work echo; /* echo ping workqueue job
> */
> + struct delayed_work reconnect; /* reconnect workqueue
> job */
> char *smallbuf; /* pointer to current "small"
> buffer */
> char *bigbuf; /* pointer to current "big"
> buffer */
> unsigned int total_read; /* total amount of data read in
> this pass */
> @@ -648,6 +649,7 @@ struct TCP_Server_Info {
> __u8 preauth_hash[512];
> #endif /* CONFIG_CIFS_SMB2 */
> unsigned long echo_interval;
> + struct mutex reconnect_mutex; /* prevent simultaneous
> reconnects */
> };
>
> static inline unsigned int
> @@ -850,6 +852,7 @@ struct cifs_tcon {
> struct list_head tcon_list;
> int tc_count;
> struct list_head openFileList;
> + struct list_head rlist; /* reconnect list */
> spinlock_t open_file_lock; /* protects list above */
> struct cifs_ses *ses; /* pointer to session
> associated with */
> char treeName[MAX_TREE_SIZE + 1]; /* UNC name of resource in
> ASCII */
> diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
> index ced0e42..cd8025a 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,
> struct tcon_link *tlink,
> struct cifs_pending_open
> *open);
> extern void cifs_del_pending_open(struct cifs_pending_open *open);
> +extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
> + int from_reconnect);
> +extern void cifs_put_tcon(struct cifs_tcon *tcon);
>
> #if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL)
> extern void cifs_dfs_release_automount_timer(void);
> diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
> index 4547aed..75503af 100644
> --- a/fs/cifs/connect.c
> +++ b/fs/cifs/connect.c
> @@ -52,6 +52,9 @@
> #include "nterr.h"
> #include "rfc1002pdu.h"
> #include "fscache.h"
> +#ifdef CONFIG_CIFS_SMB2
> +#include "smb2proto.h"
> +#endif
>
> #define CIFS_PORT 445
> #define RFC1001_PORT 139
> @@ -2100,8 +2103,8 @@ cifs_find_tcp_session(struct smb_vol *vol)
> return NULL;
> }
>
> -static void
> -cifs_put_tcp_session(struct TCP_Server_Info *server)
> +void
> +cifs_put_tcp_session(struct TCP_Server_Info *server, int
> from_reconnect)
> {
> struct task_struct *task;
>
> @@ -2118,6 +2121,19 @@ cifs_put_tcp_session(struct TCP_Server_Info
> *server)
>
> cancel_delayed_work_sync(&server->echo);
>
> +#ifdef CONFIG_CIFS_SMB2
> + if (from_reconnect)
> + /*
> + * Avoid deadlock here: reconnect work calls
> + * cifs_put_tcp_session() at its end. Need to be
> sure
> + * that reconnect work does nothing with server
> pointer after
> + * that step.
> + */
> + cancel_delayed_work(&server->reconnect);
> + else
> + cancel_delayed_work_sync(&server->reconnect);
> +#endif
Do you need the #ifdef CONFIG_CIFS_SMB2 here considering that there are
no such macro definitions around the declarations around delayed_work
reconnect in struct TCP_Server_Info. It would also lead to unused
variable in case the kernel is compiled without CONFIG_CIFS_SMB2.
> +
> spin_lock(&GlobalMid_Lock);
> server->tcpStatus = CifsExiting;
> spin_unlock(&GlobalMid_Lock);
> @@ -2171,6 +2187,7 @@ cifs_get_tcp_session(struct smb_vol
> *volume_info)
> init_waitqueue_head(&tcp_ses->request_q);
> INIT_LIST_HEAD(&tcp_ses->pending_mid_q);
> mutex_init(&tcp_ses->srv_mutex);
> + mutex_init(&tcp_ses->reconnect_mutex);
> memcpy(tcp_ses->workstation_RFC1001_name,
> volume_info->source_rfc1001_name,
> RFC1001_NAME_LEN_WITH_NULL);
> memcpy(tcp_ses->server_RFC1001_name,
> @@ -2182,6 +2199,9 @@ cifs_get_tcp_session(struct smb_vol
> *volume_info)
> INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
> INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
> INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
> +#ifdef CONFIG_CIFS_SMB2
> + INIT_DELAYED_WORK(&tcp_ses->reconnect,
> smb2_reconnect_server);
> +#endif
> memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr,
> sizeof(tcp_ses->srcaddr));
> memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr,
> @@ -2340,7 +2360,7 @@ cifs_put_smb_ses(struct cifs_ses *ses)
> spin_unlock(&cifs_tcp_ses_lock);
>
> sesInfoFree(ses);
> - cifs_put_tcp_session(server);
> + cifs_put_tcp_session(server, 0);
> }
>
> #ifdef CONFIG_KEYS
> @@ -2514,7 +2534,7 @@ cifs_get_smb_ses(struct TCP_Server_Info
> *server, struct smb_vol *volume_info)
> mutex_unlock(&ses->session_mutex);
>
> /* existing SMB ses has a server reference already
> */
> - cifs_put_tcp_session(server);
> + cifs_put_tcp_session(server, 0);
> free_xid(xid);
> return ses;
> }
> @@ -2604,7 +2624,7 @@ cifs_find_tcon(struct cifs_ses *ses, const char
> *unc)
> return NULL;
> }
>
> -static void
> +void
> cifs_put_tcon(struct cifs_tcon *tcon)
> {
> unsigned int xid;
> @@ -3792,7 +3812,7 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct
> smb_vol *volume_info)
> else if (ses)
> cifs_put_smb_ses(ses);
> else
> - cifs_put_tcp_session(server);
> + cifs_put_tcp_session(server, 0);
> bdi_destroy(&cifs_sb->bdi);
> }
>
> @@ -4103,7 +4123,7 @@ cifs_construct_tcon(struct cifs_sb_info
> *cifs_sb, kuid_t fsuid)
> ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);
> if (IS_ERR(ses)) {
> tcon = (struct cifs_tcon *)ses;
> - cifs_put_tcp_session(master_tcon->ses->server);
> + cifs_put_tcp_session(master_tcon->ses->server, 0);
> goto out;
> }
>
> diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
> index 5ca5ea46..950dbf7 100644
> --- a/fs/cifs/smb2pdu.c
> +++ b/fs/cifs/smb2pdu.c
> @@ -1972,6 +1972,54 @@ smb2_echo_callback(struct mid_q_entry *mid)
> add_credits(server, credits_received, CIFS_ECHO_OP);
> }
>
> +void smb2_reconnect_server(struct work_struct *work)
> +{
> + struct TCP_Server_Info *server = container_of(work,
> + struct TCP_Server_Info,
> reconnect.work);
> + struct cifs_ses *ses;
> + struct cifs_tcon *tcon, *tcon2;
> + struct list_head tmp_list;
> + int tcon_exist = false;
> +
> + /* Prevent simultaneous reconnects that can corrupt tcon-
> >rlist list */
> + mutex_lock(&server->reconnect_mutex);
> +
> + INIT_LIST_HEAD(&tmp_list);
> + cifs_dbg(FYI, "Need negotiate, reconnecting tcons\n");
> +
> + spin_lock(&cifs_tcp_ses_lock);
> + list_for_each_entry(ses, &server->smb_ses_list,
> smb_ses_list) {
> + list_for_each_entry(tcon, &ses->tcon_list,
> tcon_list) {
> + if (tcon && tcon->need_reconnect) {
> + tcon->tc_count++;
> + list_add_tail(&tcon->rlist,
> &tmp_list);
> + tcon_exist = true;
> + }
> + }
> + }
> + /*
> + * Get the reference to server struct to be sure that the
> last call of
> + * cifs_put_tcon() in the loop below won't release the
> server pointer.
> + */
> + if (tcon_exist)
> + server->srv_count++;
> +
> + spin_unlock(&cifs_tcp_ses_lock);
> +
> + list_for_each_entry_safe(tcon, tcon2, &tmp_list, rlist) {
> + smb2_reconnect(SMB2_ECHO, tcon);
> + list_del_init(&tcon->rlist);
> + cifs_put_tcon(tcon);
> + }
> +
> + cifs_dbg(FYI, "Reconnecting tcons finished\n");
> + mutex_unlock(&server->reconnect_mutex);
> +
> + /* now we can safely release srv struct */
> + if (tcon_exist)
> + cifs_put_tcp_session(server, 1);
> +}
> +
> int
> SMB2_echo(struct TCP_Server_Info *server)
> {
> @@ -1984,32 +2032,11 @@ SMB2_echo(struct TCP_Server_Info *server)
> cifs_dbg(FYI, "In echo request\n");
>
> if (server->tcpStatus == CifsNeedNegotiate) {
> - struct list_head *tmp, *tmp2;
> - struct cifs_ses *ses;
> - struct cifs_tcon *tcon;
> -
> - cifs_dbg(FYI, "Need negotiate, reconnecting
> tcons\n");
> - spin_lock(&cifs_tcp_ses_lock);
> - list_for_each(tmp, &server->smb_ses_list) {
> - ses = list_entry(tmp, struct cifs_ses,
> smb_ses_list);
> - list_for_each(tmp2, &ses->tcon_list) {
> - tcon = list_entry(tmp2, struct
> cifs_tcon,
> - tcon_list);
> - /* add check for persistent handle
> reconnect */
> - if (tcon && tcon->need_reconnect) {
> - spin_unlock(&cifs_tcp_ses_lo
> ck);
> - rc =
> smb2_reconnect(SMB2_ECHO, tcon);
> - spin_lock(&cifs_tcp_ses_lock
> );
> - }
> - }
> - }
> - spin_unlock(&cifs_tcp_ses_lock);
> + /* No need to send echo on newly established
> connections */
> + mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
> + return rc;
Shouldn't this be queue_work?
> }
>
> - /* if no session, renegotiate failed above */
> - if (server->tcpStatus == CifsNeedNegotiate)
> - return -EIO;
> -
> rc = small_smb2_init(SMB2_ECHO, NULL, (void **)&req);
> if (rc)
> return rc;
> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
> index eb2cde2..f2d511a 100644
> --- a/fs/cifs/smb2proto.h
> +++ b/fs/cifs/smb2proto.h
> @@ -96,6 +96,7 @@ extern int smb2_open_file(const unsigned int xid,
> extern int smb2_unlock_range(struct cifsFileInfo *cfile,
> struct file_lock *flock, const unsigned
> int xid);
> extern int smb2_push_mandatory_locks(struct cifsFileInfo *cfile);
> +extern void smb2_reconnect_server(struct work_struct *work);
>
> /*
> * SMB2 Worker functions - most of protocol specific implementation
> details
Sachin Prabhu
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] CIFS: Fix a possible memory corruption during reconnect
[not found] ` <1480500055.3937.8.camel-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
@ 2016-11-30 19:24 ` Pavel Shilovsky
[not found] ` <CAKywueRP6BV3d=--yETisU=8u+jCmeuZyUcvnsrx-RCOqLGphw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
0 siblings, 1 reply; 8+ messages in thread
From: Pavel Shilovsky @ 2016-11-30 19:24 UTC (permalink / raw)
To: Sachin Prabhu; +Cc: linux-cifs
2016-11-30 2:00 GMT-08:00 Sachin Prabhu <sprabhu-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>:
> On Thu, 2016-11-10 at 15:31 -0800, Pavel Shilovsky wrote:
>> 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.
>>
>> CC: Stable <stable-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
>> Signed-off-by: Pavel Shilovsky <pshilov-0li6OtcxBFHby3iVrkZq2A@public.gmane.org>
>> ---
>> fs/cifs/cifsglob.h | 3 +++
>> fs/cifs/cifsproto.h | 3 +++
>> fs/cifs/connect.c | 34 +++++++++++++++++++-----
>> fs/cifs/smb2pdu.c | 75 ++++++++++++++++++++++++++++++++++++-------
>> ----------
>> 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 1f17f6b..6dcefc8 100644
>> --- a/fs/cifs/cifsglob.h
>> +++ b/fs/cifs/cifsglob.h
>> @@ -632,6 +632,7 @@ struct TCP_Server_Info {
>> bool sec_mskerberos; /* supports
>> legacy MS Kerberos */
>> bool large_buf; /* is current buffer
>> large? */
>> struct delayed_work echo; /* echo ping workqueue job
>> */
>> + struct delayed_work reconnect; /* reconnect workqueue
>> job */
>> char *smallbuf; /* pointer to current "small"
>> buffer */
>> char *bigbuf; /* pointer to current "big"
>> buffer */
>> unsigned int total_read; /* total amount of data read in
>> this pass */
>> @@ -648,6 +649,7 @@ struct TCP_Server_Info {
>> __u8 preauth_hash[512];
>> #endif /* CONFIG_CIFS_SMB2 */
>> unsigned long echo_interval;
>> + struct mutex reconnect_mutex; /* prevent simultaneous
>> reconnects */
>> };
>>
>> static inline unsigned int
>> @@ -850,6 +852,7 @@ struct cifs_tcon {
>> struct list_head tcon_list;
>> int tc_count;
>> struct list_head openFileList;
>> + struct list_head rlist; /* reconnect list */
>> spinlock_t open_file_lock; /* protects list above */
>> struct cifs_ses *ses; /* pointer to session
>> associated with */
>> char treeName[MAX_TREE_SIZE + 1]; /* UNC name of resource in
>> ASCII */
>> diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
>> index ced0e42..cd8025a 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,
>> struct tcon_link *tlink,
>> struct cifs_pending_open
>> *open);
>> extern void cifs_del_pending_open(struct cifs_pending_open *open);
>> +extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
>> + int from_reconnect);
>> +extern void cifs_put_tcon(struct cifs_tcon *tcon);
>>
>> #if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL)
>> extern void cifs_dfs_release_automount_timer(void);
>> diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
>> index 4547aed..75503af 100644
>> --- a/fs/cifs/connect.c
>> +++ b/fs/cifs/connect.c
>> @@ -52,6 +52,9 @@
>> #include "nterr.h"
>> #include "rfc1002pdu.h"
>> #include "fscache.h"
>> +#ifdef CONFIG_CIFS_SMB2
>> +#include "smb2proto.h"
>> +#endif
>>
>> #define CIFS_PORT 445
>> #define RFC1001_PORT 139
>> @@ -2100,8 +2103,8 @@ cifs_find_tcp_session(struct smb_vol *vol)
>> return NULL;
>> }
>>
>> -static void
>> -cifs_put_tcp_session(struct TCP_Server_Info *server)
>> +void
>> +cifs_put_tcp_session(struct TCP_Server_Info *server, int
>> from_reconnect)
>> {
>> struct task_struct *task;
>>
>> @@ -2118,6 +2121,19 @@ cifs_put_tcp_session(struct TCP_Server_Info
>> *server)
>>
>> cancel_delayed_work_sync(&server->echo);
>>
>> +#ifdef CONFIG_CIFS_SMB2
>> + if (from_reconnect)
>> + /*
>> + * Avoid deadlock here: reconnect work calls
>> + * cifs_put_tcp_session() at its end. Need to be
>> sure
>> + * that reconnect work does nothing with server
>> pointer after
>> + * that step.
>> + */
>> + cancel_delayed_work(&server->reconnect);
>> + else
>> + cancel_delayed_work_sync(&server->reconnect);
>> +#endif
>
> Do you need the #ifdef CONFIG_CIFS_SMB2 here considering that there are
> no such macro definitions around the declarations around delayed_work
> reconnect in struct TCP_Server_Info. It would also lead to unused
> variable in case the kernel is compiled without CONFIG_CIFS_SMB2.
Thank you for reviewing this.
I posted the next version of the patch yesterday ([PATCH 4/5] CIFS:
Fix a possible memory corruption during reconnect) that addresses this
problem by moving fields inside TCP_Server_Info to CONFIG_CIFS_SMB2.
>
>> +
>> spin_lock(&GlobalMid_Lock);
>> server->tcpStatus = CifsExiting;
>> spin_unlock(&GlobalMid_Lock);
>> @@ -2171,6 +2187,7 @@ cifs_get_tcp_session(struct smb_vol
>> *volume_info)
>> init_waitqueue_head(&tcp_ses->request_q);
>> INIT_LIST_HEAD(&tcp_ses->pending_mid_q);
>> mutex_init(&tcp_ses->srv_mutex);
>> + mutex_init(&tcp_ses->reconnect_mutex);
>> memcpy(tcp_ses->workstation_RFC1001_name,
>> volume_info->source_rfc1001_name,
>> RFC1001_NAME_LEN_WITH_NULL);
>> memcpy(tcp_ses->server_RFC1001_name,
>> @@ -2182,6 +2199,9 @@ cifs_get_tcp_session(struct smb_vol
>> *volume_info)
>> INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
>> INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
>> INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
>> +#ifdef CONFIG_CIFS_SMB2
>> + INIT_DELAYED_WORK(&tcp_ses->reconnect,
>> smb2_reconnect_server);
>> +#endif
>> memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr,
>> sizeof(tcp_ses->srcaddr));
>> memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr,
>> @@ -2340,7 +2360,7 @@ cifs_put_smb_ses(struct cifs_ses *ses)
>> spin_unlock(&cifs_tcp_ses_lock);
>>
>> sesInfoFree(ses);
>> - cifs_put_tcp_session(server);
>> + cifs_put_tcp_session(server, 0);
>> }
>>
>> #ifdef CONFIG_KEYS
>> @@ -2514,7 +2534,7 @@ cifs_get_smb_ses(struct TCP_Server_Info
>> *server, struct smb_vol *volume_info)
>> mutex_unlock(&ses->session_mutex);
>>
>> /* existing SMB ses has a server reference already
>> */
>> - cifs_put_tcp_session(server);
>> + cifs_put_tcp_session(server, 0);
>> free_xid(xid);
>> return ses;
>> }
>> @@ -2604,7 +2624,7 @@ cifs_find_tcon(struct cifs_ses *ses, const char
>> *unc)
>> return NULL;
>> }
>>
>> -static void
>> +void
>> cifs_put_tcon(struct cifs_tcon *tcon)
>> {
>> unsigned int xid;
>> @@ -3792,7 +3812,7 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct
>> smb_vol *volume_info)
>> else if (ses)
>> cifs_put_smb_ses(ses);
>> else
>> - cifs_put_tcp_session(server);
>> + cifs_put_tcp_session(server, 0);
>> bdi_destroy(&cifs_sb->bdi);
>> }
>>
>> @@ -4103,7 +4123,7 @@ cifs_construct_tcon(struct cifs_sb_info
>> *cifs_sb, kuid_t fsuid)
>> ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);
>> if (IS_ERR(ses)) {
>> tcon = (struct cifs_tcon *)ses;
>> - cifs_put_tcp_session(master_tcon->ses->server);
>> + cifs_put_tcp_session(master_tcon->ses->server, 0);
>> goto out;
>> }
>>
>> diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
>> index 5ca5ea46..950dbf7 100644
>> --- a/fs/cifs/smb2pdu.c
>> +++ b/fs/cifs/smb2pdu.c
>> @@ -1972,6 +1972,54 @@ smb2_echo_callback(struct mid_q_entry *mid)
>> add_credits(server, credits_received, CIFS_ECHO_OP);
>> }
>>
>> +void smb2_reconnect_server(struct work_struct *work)
>> +{
>> + struct TCP_Server_Info *server = container_of(work,
>> + struct TCP_Server_Info,
>> reconnect.work);
>> + struct cifs_ses *ses;
>> + struct cifs_tcon *tcon, *tcon2;
>> + struct list_head tmp_list;
>> + int tcon_exist = false;
>> +
>> + /* Prevent simultaneous reconnects that can corrupt tcon-
>> >rlist list */
>> + mutex_lock(&server->reconnect_mutex);
>> +
>> + INIT_LIST_HEAD(&tmp_list);
>> + cifs_dbg(FYI, "Need negotiate, reconnecting tcons\n");
>> +
>> + spin_lock(&cifs_tcp_ses_lock);
>> + list_for_each_entry(ses, &server->smb_ses_list,
>> smb_ses_list) {
>> + list_for_each_entry(tcon, &ses->tcon_list,
>> tcon_list) {
>> + if (tcon && tcon->need_reconnect) {
>> + tcon->tc_count++;
>> + list_add_tail(&tcon->rlist,
>> &tmp_list);
>> + tcon_exist = true;
>> + }
>> + }
>> + }
>> + /*
>> + * Get the reference to server struct to be sure that the
>> last call of
>> + * cifs_put_tcon() in the loop below won't release the
>> server pointer.
>> + */
>> + if (tcon_exist)
>> + server->srv_count++;
>> +
>> + spin_unlock(&cifs_tcp_ses_lock);
>> +
>> + list_for_each_entry_safe(tcon, tcon2, &tmp_list, rlist) {
>> + smb2_reconnect(SMB2_ECHO, tcon);
>> + list_del_init(&tcon->rlist);
>> + cifs_put_tcon(tcon);
>> + }
>> +
>> + cifs_dbg(FYI, "Reconnecting tcons finished\n");
>> + mutex_unlock(&server->reconnect_mutex);
>> +
>> + /* now we can safely release srv struct */
>> + if (tcon_exist)
>> + cifs_put_tcp_session(server, 1);
>> +}
>> +
>> int
>> SMB2_echo(struct TCP_Server_Info *server)
>> {
>> @@ -1984,32 +2032,11 @@ SMB2_echo(struct TCP_Server_Info *server)
>> cifs_dbg(FYI, "In echo request\n");
>>
>> if (server->tcpStatus == CifsNeedNegotiate) {
>> - struct list_head *tmp, *tmp2;
>> - struct cifs_ses *ses;
>> - struct cifs_tcon *tcon;
>> -
>> - cifs_dbg(FYI, "Need negotiate, reconnecting
>> tcons\n");
>> - spin_lock(&cifs_tcp_ses_lock);
>> - list_for_each(tmp, &server->smb_ses_list) {
>> - ses = list_entry(tmp, struct cifs_ses,
>> smb_ses_list);
>> - list_for_each(tmp2, &ses->tcon_list) {
>> - tcon = list_entry(tmp2, struct
>> cifs_tcon,
>> - tcon_list);
>> - /* add check for persistent handle
>> reconnect */
>> - if (tcon && tcon->need_reconnect) {
>> - spin_unlock(&cifs_tcp_ses_lo
>> ck);
>> - rc =
>> smb2_reconnect(SMB2_ECHO, tcon);
>> - spin_lock(&cifs_tcp_ses_lock
>> );
>> - }
>> - }
>> - }
>> - spin_unlock(&cifs_tcp_ses_lock);
>> + /* No need to send echo on newly established
>> connections */
>> + mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
>> + return rc;
>
> Shouldn't this be queue_work?
It is made it this way because of the following: if someone submitted
the work with a delay previously (now the code doesn't do it but it is
possible in the future), we want to overwrite this delay to 0 since we
need an immediate session/tcon reconnect here.
>> }
>>
>> - /* if no session, renegotiate failed above */
>> - if (server->tcpStatus == CifsNeedNegotiate)
>> - return -EIO;
>> -
>> rc = small_smb2_init(SMB2_ECHO, NULL, (void **)&req);
>> if (rc)
>> return rc;
>> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
>> index eb2cde2..f2d511a 100644
>> --- a/fs/cifs/smb2proto.h
>> +++ b/fs/cifs/smb2proto.h
>> @@ -96,6 +96,7 @@ extern int smb2_open_file(const unsigned int xid,
>> extern int smb2_unlock_range(struct cifsFileInfo *cfile,
>> struct file_lock *flock, const unsigned
>> int xid);
>> extern int smb2_push_mandatory_locks(struct cifsFileInfo *cfile);
>> +extern void smb2_reconnect_server(struct work_struct *work);
>>
>> /*
>> * SMB2 Worker functions - most of protocol specific implementation
>> details
>
> Sachin Prabhu
--
Best regards,
Pavel Shilovsky
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] CIFS: Fix a possible memory corruption during reconnect
[not found] ` <CAKywueRP6BV3d=--yETisU=8u+jCmeuZyUcvnsrx-RCOqLGphw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2016-12-01 9:35 ` Sachin Prabhu
[not found] ` <1480584953.3937.14.camel-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
0 siblings, 1 reply; 8+ messages in thread
From: Sachin Prabhu @ 2016-12-01 9:35 UTC (permalink / raw)
To: Pavel Shilovsky; +Cc: linux-cifs
On Wed, 2016-11-30 at 11:24 -0800, Pavel Shilovsky wrote:
>
> > Do you need the #ifdef CONFIG_CIFS_SMB2 here considering that there
> > are
> > no such macro definitions around the declarations around
> > delayed_work
> > reconnect in struct TCP_Server_Info. It would also lead to unused
> > variable in case the kernel is compiled without CONFIG_CIFS_SMB2.
>
> Thank you for reviewing this.
>
> I posted the next version of the patch yesterday ([PATCH 4/5] CIFS:
> Fix a possible memory corruption during reconnect) that addresses
> this
> problem by moving fields inside TCP_Server_Info to CONFIG_CIFS_SMB2.
>
Thanks. I'll review the patch separately.
> > > + /* No need to send echo on newly established
> > > connections */
> > > + mod_delayed_work(cifsiod_wq, &server->reconnect,
> > > 0);
> > > + return rc;
> >
> > Shouldn't this be queue_work?
>
> It is made it this way because of the following: if someone submitted
> the work with a delay previously (now the code doesn't do it but it
> is
> possible in the future), we want to overwrite this delay to 0 since
> we
> need an immediate session/tcon reconnect here.
>
OK. It should work without any problem but I would have stuck to the
expected functions and use mod_delayed_work() only when new code which
queues the work separately has been added. It is strange to see only
mod_delayed_work() when the work isn't queued anywhere else. Apart from
that small concern, I am fine with it.
Sachin Prabhu
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] CIFS: Fix a possible memory corruption during reconnect
[not found] ` <1480584953.3937.14.camel-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
@ 2016-12-05 21:39 ` Pavel Shilovsky
0 siblings, 0 replies; 8+ messages in thread
From: Pavel Shilovsky @ 2016-12-05 21:39 UTC (permalink / raw)
To: Sachin Prabhu; +Cc: linux-cifs
2016-12-01 1:35 GMT-08:00 Sachin Prabhu <sprabhu-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>:
> On Wed, 2016-11-30 at 11:24 -0800, Pavel Shilovsky wrote:
>>
>> > Do you need the #ifdef CONFIG_CIFS_SMB2 here considering that there
>> > are
>> > no such macro definitions around the declarations around
>> > delayed_work
>> > reconnect in struct TCP_Server_Info. It would also lead to unused
>> > variable in case the kernel is compiled without CONFIG_CIFS_SMB2.
>>
>> Thank you for reviewing this.
>>
>> I posted the next version of the patch yesterday ([PATCH 4/5] CIFS:
>> Fix a possible memory corruption during reconnect) that addresses
>> this
>> problem by moving fields inside TCP_Server_Info to CONFIG_CIFS_SMB2.
>>
>
> Thanks. I'll review the patch separately.
>
>
>> > > + /* No need to send echo on newly established
>> > > connections */
>> > > + mod_delayed_work(cifsiod_wq, &server->reconnect,
>> > > 0);
>> > > + return rc;
>> >
>> > Shouldn't this be queue_work?
>>
>> It is made it this way because of the following: if someone submitted
>> the work with a delay previously (now the code doesn't do it but it
>> is
>> possible in the future), we want to overwrite this delay to 0 since
>> we
>> need an immediate session/tcon reconnect here.
>>
>
> OK. It should work without any problem but I would have stuck to the
> expected functions and use mod_delayed_work() only when new code which
> queues the work separately has been added. It is strange to see only
> mod_delayed_work() when the work isn't queued anywhere else. Apart from
> that small concern, I am fine with it.
>
> Sachin Prabhu
Ok, it makes sense. I have posted the next version (v2) of the
patchset that addresses your suggestions to the list.
--
Best regards,
Pavel Shilovsky
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2016-12-05 21:39 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-11-10 23:31 [PATCH v2] CIFS: Fix a possible memory corruption during reconnect Pavel Shilovsky
[not found] ` <1478820683-2954-1-git-send-email-pshilov-0li6OtcxBFHby3iVrkZq2A@public.gmane.org>
2016-11-23 20:07 ` Pavel Shilovsky
[not found] ` <CAKywueR-0h7i2-8OVaC8+KKh1Yjq_otdpuFu582bW=0xQ=+E9Q-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2016-11-24 16:43 ` Aurélien Aptel
[not found] ` <mps8ts8u8u1.fsf-IBi9RG/b67k@public.gmane.org>
2016-11-28 19:53 ` Pavel Shilovsky
2016-11-30 10:00 ` Sachin Prabhu
[not found] ` <1480500055.3937.8.camel-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2016-11-30 19:24 ` Pavel Shilovsky
[not found] ` <CAKywueRP6BV3d=--yETisU=8u+jCmeuZyUcvnsrx-RCOqLGphw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2016-12-01 9:35 ` Sachin Prabhu
[not found] ` <1480584953.3937.14.camel-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2016-12-05 21:39 ` Pavel Shilovsky
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox