Re: [PATCH] Handle mismatched open calls

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



2017-02-27 13:42 GMT-08:00 Sachin Prabhu <sprabhu@xxxxxxxxxx>:
> A signal can interrupt a SendReceive call which result in incoming
> responses to the call being ignored. This is a problem for calls such as
> open which results in the successful response being ignored. This
> results in an open file resource on the server.
>
> The patch looks into responses which were cancelled after being sent and
> in case of successful open closes the open fids.
>
> For this patch, the check is only done in SendReceive2()
>
> RH-bz: 1403319
>
> v2: Search for sess and tcon while under spinlock
>
> Signed-off-by: Sachin Prabhu <sprabhu@xxxxxxxxxx>
> ---
>  fs/cifs/cifsglob.h      | 11 +++++++++
>  fs/cifs/connect.c       |  9 +++++--
>  fs/cifs/smb2misc.c      | 47 +++++++++++++++++++++++++++++++++++++
>  fs/cifs/smb2ops.c       |  4 ++++
>  fs/cifs/smb2proto.h     |  8 +++++++
>  fs/cifs/smb2transport.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++---
>  fs/cifs/transport.c     |  3 +++
>  7 files changed, 139 insertions(+), 5 deletions(-)
>
> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> index 7ea8a33..cc97d73 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -241,6 +241,7 @@ struct smb_version_operations {
>         /* verify the message */
>         int (*check_message)(char *, unsigned int, struct TCP_Server_Info *);
>         bool (*is_oplock_break)(char *, struct TCP_Server_Info *);
> +       int (*handle_cancelled_mid)(char *, struct TCP_Server_Info *);
>         void (*downgrade_oplock)(struct TCP_Server_Info *,
>                                         struct cifsInodeInfo *, bool);
>         /* process transaction2 response */
> @@ -1319,12 +1320,19 @@ struct mid_q_entry {
>         void *callback_data;      /* general purpose pointer for callback */
>         void *resp_buf;         /* pointer to received SMB header */
>         int mid_state;  /* wish this were enum but can not pass to wait_event */
> +       unsigned int mid_flags;
>         __le16 command;         /* smb command code */
>         bool large_buf:1;       /* if valid response, is pointer to large buf */
>         bool multiRsp:1;        /* multiple trans2 responses for one request  */
>         bool multiEnd:1;        /* both received */
>  };
>
> +struct close_cancelled_open {
> +       struct cifs_fid         fid;
> +       struct cifs_tcon        *tcon;
> +       struct work_struct      work;
> +};
> +
>  /*     Make code in transport.c a little cleaner by moving
>         update of optional stats into function below */
>  #ifdef CONFIG_CIFS_STATS2
> @@ -1456,6 +1464,9 @@ static inline void free_dfs_info_array(struct dfs_info3_param *param,
>  #define   MID_RESPONSE_MALFORMED 0x10
>  #define   MID_SHUTDOWN          0x20
>
> +/* Flags */
> +#define   MID_WAIT_CANCELLED    1 /* Cancelled while waiting for response */
> +
>  /* Types of response buffer returned from SendReceive2 */
>  #define   CIFS_NO_BUFFER        0    /* Response buffer not returned */
>  #define   CIFS_SMALL_BUFFER     1
> diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
> index 35ae49e..95bd14d 100644
> --- a/fs/cifs/connect.c
> +++ b/fs/cifs/connect.c
> @@ -887,10 +887,15 @@ cifs_demultiplex_thread(void *p)
>
>                 server->lstrp = jiffies;
>                 if (mid_entry != NULL) {
> +                       if ((mid_entry->mid_flags & MID_WAIT_CANCELLED) &&
> +                                       (server->ops->handle_cancelled_mid))
> +                               server->ops->handle_cancelled_mid(buf, server);
>                         if (!mid_entry->multiRsp || mid_entry->multiEnd)
>                                 mid_entry->callback(mid_entry);
> -               } else if (!server->ops->is_oplock_break ||
> -                          !server->ops->is_oplock_break(buf, server)) {
> +               } else if (server->ops->is_oplock_break &&
> +                          server->ops->is_oplock_break(buf, server)) {
> +                       cifs_dbg(FYI, "Received oplock break\n");
> +               } else {
>                         cifs_dbg(VFS, "No task to wake, unknown frame received! NumMids %d\n",
>                                  atomic_read(&midCount));
>                         cifs_dump_mem("Received Data is: ", buf,
> diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c
> index 3d38348..ce94607 100644
> --- a/fs/cifs/smb2misc.c
> +++ b/fs/cifs/smb2misc.c
> @@ -654,3 +654,50 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
>         cifs_dbg(FYI, "Can not process oplock break for non-existent connection\n");
>         return false;
>  }
> +
> +void
> +smb2_cancelled_close_fid(struct work_struct *work)
> +{
> +       struct close_cancelled_open *cancelled = container_of(work,
> +                                       struct close_cancelled_open, work);
> +
> +       cifs_dbg(VFS, "Close unmatched open\n");
> +
> +       SMB2_close(0, cancelled->tcon, cancelled->fid.persistent_fid,
> +                                          cancelled->fid.volatile_fid);
> +       cifs_put_tcon(cancelled->tcon);
> +       kfree(cancelled);
> +}
> +
> +int
> +smb2_handle_cancelled_mid(char *buffer, struct TCP_Server_Info *server)
> +{
> +       struct smb2_create_rsp *rsp = (struct smb2_create_rsp *)buffer;
> +       struct smb2_hdr *smb2hdr = &rsp->hdr;
> +       struct cifs_tcon *tcon;
> +       struct close_cancelled_open *cancelled;
> +
> +       if ((smb2hdr->Command != SMB2_CREATE) ||
> +               (smb2hdr->Status != STATUS_SUCCESS))
> +               return 0;
> +
> +       tcon = smb2_find_smb_hdr_tcon(smb2hdr, server);
> +       if (!tcon)
> +               return -ENOENT;
> +
> +       cancelled = (struct close_cancelled_open *)
> +                       kzalloc(sizeof(struct close_cancelled_open),
> +                               GFP_KERNEL);
> +       if (!cancelled) {
> +               cifs_put_tcon(tcon);
> +               return -ENOMEM;
> +       }

I suggest to re-order obtaining tcon ref and memory allocation to
prevent calling cifs_put_tcon(tcon) from demultiplex thread (this can
put the last ref of tcp session in the middle of demultiplex loop).

> +
> +       cancelled->fid.persistent_fid = rsp->PersistentFileId;
> +       cancelled->fid.volatile_fid = rsp->VolatileFileId;
> +       cancelled->tcon = tcon;
> +       INIT_WORK(&cancelled->work, smb2_cancelled_close_fid);
> +       queue_work(cifsiod_wq, &cancelled->work);
> +
> +       return 0;
> +}
> diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
> index 5d456eb..007abf7 100644
> --- a/fs/cifs/smb2ops.c
> +++ b/fs/cifs/smb2ops.c
> @@ -1565,6 +1565,7 @@ struct smb_version_operations smb20_operations = {
>         .clear_stats = smb2_clear_stats,
>         .print_stats = smb2_print_stats,
>         .is_oplock_break = smb2_is_valid_oplock_break,
> +       .handle_cancelled_mid = smb2_handle_cancelled_mid,
>         .downgrade_oplock = smb2_downgrade_oplock,
>         .need_neg = smb2_need_neg,
>         .negotiate = smb2_negotiate,
> @@ -1645,6 +1646,7 @@ struct smb_version_operations smb21_operations = {
>         .clear_stats = smb2_clear_stats,
>         .print_stats = smb2_print_stats,
>         .is_oplock_break = smb2_is_valid_oplock_break,
> +       .handle_cancelled_mid = smb2_handle_cancelled_mid,
>         .downgrade_oplock = smb2_downgrade_oplock,
>         .need_neg = smb2_need_neg,
>         .negotiate = smb2_negotiate,
> @@ -1727,6 +1729,7 @@ struct smb_version_operations smb30_operations = {
>         .print_stats = smb2_print_stats,
>         .dump_share_caps = smb2_dump_share_caps,
>         .is_oplock_break = smb2_is_valid_oplock_break,
> +       .handle_cancelled_mid = smb2_handle_cancelled_mid,
>         .downgrade_oplock = smb2_downgrade_oplock,
>         .need_neg = smb2_need_neg,
>         .negotiate = smb2_negotiate,
> @@ -1815,6 +1818,7 @@ struct smb_version_operations smb311_operations = {
>         .print_stats = smb2_print_stats,
>         .dump_share_caps = smb2_dump_share_caps,
>         .is_oplock_break = smb2_is_valid_oplock_break,
> +       .handle_cancelled_mid = smb2_handle_cancelled_mid,
>         .downgrade_oplock = smb2_downgrade_oplock,
>         .need_neg = smb2_need_neg,
>         .negotiate = smb2_negotiate,
> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
> index f2d511a..8786e05 100644
> --- a/fs/cifs/smb2proto.h
> +++ b/fs/cifs/smb2proto.h
> @@ -48,6 +48,11 @@ extern struct mid_q_entry *smb2_setup_request(struct cifs_ses *ses,
>                               struct smb_rqst *rqst);
>  extern struct mid_q_entry *smb2_setup_async_request(
>                         struct TCP_Server_Info *server, struct smb_rqst *rqst);
> +extern struct cifs_ses *smb2_find_smb_ses(struct smb2_hdr *smb2hdr,
> +                                       struct TCP_Server_Info *server);
> +extern struct cifs_tcon *smb2_find_smb_sess_tcon(struct cifs_ses *ses, __u32  tid);
> +extern struct cifs_tcon *smb2_find_smb_hdr_tcon(struct smb2_hdr *smb2hdr,
> +                                               struct TCP_Server_Info *server);
>  extern int smb2_calc_signature(struct smb_rqst *rqst,
>                                 struct TCP_Server_Info *server);
>  extern int smb3_calc_signature(struct smb_rqst *rqst,
> @@ -158,6 +163,9 @@ extern int SMB2_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
>  extern int SMB2_oplock_break(const unsigned int xid, struct cifs_tcon *tcon,
>                              const u64 persistent_fid, const u64 volatile_fid,
>                              const __u8 oplock_level);
> +extern int smb2_handle_cancelled_mid(char *buffer,
> +                                       struct TCP_Server_Info *server);
> +void smb2_cancelled_close_fid(struct work_struct *work);
>  extern int SMB2_QFS_info(const unsigned int xid, struct cifs_tcon *tcon,
>                          u64 persistent_file_id, u64 volatile_file_id,
>                          struct kstatfs *FSData);
> diff --git a/fs/cifs/smb2transport.c b/fs/cifs/smb2transport.c
> index bc9a7b6..c178649 100644
> --- a/fs/cifs/smb2transport.c
> +++ b/fs/cifs/smb2transport.c
> @@ -115,22 +115,77 @@ smb3_crypto_shash_allocate(struct TCP_Server_Info *server)
>  }
>
>  static struct cifs_ses *
> -smb2_find_smb_ses(struct smb2_hdr *smb2hdr, struct TCP_Server_Info *server)
> +_smb2_find_smb_ses(struct smb2_hdr *smb2hdr, struct TCP_Server_Info *server)
>  {
>         struct cifs_ses *ses;
>
> -       spin_lock(&cifs_tcp_ses_lock);
>         list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
>                 if (ses->Suid != smb2hdr->SessionId)
>                         continue;
> -               spin_unlock(&cifs_tcp_ses_lock);
>                 return ses;
>         }
> +
> +       return NULL;
> +}
> +
> +struct cifs_ses *
> +smb2_find_smb_ses(struct smb2_hdr *smb2hdr, struct TCP_Server_Info *server)
> +{
> +       struct cifs_ses *ses;
> +
> +       spin_lock(&cifs_tcp_ses_lock);
> +       ses = _smb2_find_smb_ses(smb2hdr, server);
>         spin_unlock(&cifs_tcp_ses_lock);
>
> +       return ses;
> +}
> +
> +static struct cifs_tcon *
> +_smb2_find_smb_sess_tcon(struct cifs_ses *ses, __u32  tid)
> +{
> +       struct list_head *tmp;
> +       struct cifs_tcon *tcon;
> +
> +       list_for_each(tmp, &ses->tcon_list) {
> +               tcon = list_entry(tmp, struct cifs_tcon, tcon_list);
> +               if (tcon->tid != tid)
> +                       continue;
> +               ++tcon->tc_count;
> +               return tcon;
> +       }
> +
>         return NULL;
>  }
>
> +/*
> + * Obtain tcon corresponding to the tid in the given
> + * cifs_ses
> + */
> +struct cifs_tcon *
> +smb2_find_smb_sess_tcon(struct cifs_ses *ses, __u32  tid)

I haven't found any callers of this function - so, it can be excluded
from this patch.

> +{
> +       struct cifs_tcon *tcon;
> +
> +       spin_lock(&cifs_tcp_ses_lock);
> +       tcon = _smb2_find_smb_sess_tcon(ses, tid);
> +       spin_unlock(&cifs_tcp_ses_lock);
> +
> +       return tcon;
> +}
> +
> +struct cifs_tcon *
> +smb2_find_smb_hdr_tcon(struct smb2_hdr *smb2hdr, struct TCP_Server_Info *server)
> +{
> +       struct cifs_ses *ses;
> +       struct cifs_tcon *tcon;
> +
> +       spin_lock(&cifs_tcp_ses_lock);
> +       ses = _smb2_find_smb_ses(smb2hdr, server);
> +       tcon = _smb2_find_smb_sess_tcon(ses, smb2hdr->TreeId);
> +       spin_unlock(&cifs_tcp_ses_lock);
> +
> +       return tcon;
> +}
>
>  int
>  smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
> @@ -509,6 +564,7 @@ smb2_mid_entry_alloc(const struct smb2_hdr *smb_buffer,
>
>         atomic_inc(&midCount);
>         temp->mid_state = MID_REQUEST_ALLOCATED;
> +       temp->mid_flags = 0;
>         return temp;
>  }
>
> diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c
> index fbb84c0..e97e80e 100644
> --- a/fs/cifs/transport.c
> +++ b/fs/cifs/transport.c
> @@ -728,9 +728,12 @@ SendReceive2(const unsigned int xid, struct cifs_ses *ses,
>
>         rc = wait_for_response(ses->server, midQ);
>         if (rc != 0) {
> +               cifs_dbg(FYI, "Cancelling wait for mid %lu\n",
> +                               (unsigned long)midQ->mid);
>                 send_cancel(ses->server, buf, midQ);
>                 spin_lock(&GlobalMid_Lock);
>                 if (midQ->mid_state == MID_REQUEST_SUBMITTED) {
> +                       midQ->mid_flags |= MID_WAIT_CANCELLED;
>                         midQ->callback = DeleteMidQEntry;
>                         spin_unlock(&GlobalMid_Lock);
>                         cifs_small_buf_release(buf);
> --
> 2.9.3
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

Other than the notes above, the patch looks good.

-- 
Best regards,
Pavel Shilovsky
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux