2017-03-03 15:41 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 > v3: Allocate struct cancelled before we pick the tcon > Remove smb2_find_smb_sess_tcon() of the global functions > v4: rebase against latest upstream. > > 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 | 7 ++++++ > fs/cifs/smb2transport.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++--- > fs/cifs/transport.c | 3 +++ > 7 files changed, 142 insertions(+), 6 deletions(-) > > diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h > index d42dd32..c34bdb1 100644 > --- a/fs/cifs/cifsglob.h > +++ b/fs/cifs/cifsglob.h > @@ -243,6 +243,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 */ > @@ -1343,6 +1344,7 @@ 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 */ > @@ -1350,6 +1352,12 @@ struct mid_q_entry { > bool decrypted:1; /* decrypted entry */ > }; > > +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 > @@ -1481,6 +1489,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 de4c56e..6180166 100644 > --- a/fs/cifs/connect.c > +++ b/fs/cifs/connect.c > @@ -903,10 +903,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 fd516ea..33e66cf 100644 > --- a/fs/cifs/smb2misc.c > +++ b/fs/cifs/smb2misc.c > @@ -659,3 +659,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_sync_hdr *sync_hdr = get_sync_hdr(buffer); > + struct smb2_create_rsp *rsp = (struct smb2_create_rsp *)buffer; > + struct cifs_tcon *tcon; > + struct close_cancelled_open *cancelled; > + > + if ((sync_hdr->Command != SMB2_CREATE) || > + (sync_hdr->Status != STATUS_SUCCESS)) > + return 0; > + > + cancelled = (struct close_cancelled_open *) > + kzalloc(sizeof(struct close_cancelled_open), > + GFP_KERNEL); > + if (!cancelled) > + return -ENOMEM; > + > + tcon = smb2_find_smb_tcon(server, sync_hdr->SessionId, sync_hdr->TreeId);; > + if (!tcon) { > + kfree(cancelled); > + return -ENOENT; > + } > + > + 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 0231108..c6d14cc 100644 > --- a/fs/cifs/smb2ops.c > +++ b/fs/cifs/smb2ops.c > @@ -2322,6 +2322,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, > @@ -2404,6 +2405,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, > @@ -2488,6 +2490,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, > @@ -2582,6 +2585,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 69e3587..6853454 100644 > --- a/fs/cifs/smb2proto.h > +++ b/fs/cifs/smb2proto.h > @@ -48,6 +48,10 @@ 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 TCP_Server_Info *server, > + __u64 ses_id); > +extern struct cifs_tcon *smb2_find_smb_tcon(struct TCP_Server_Info *server, > + __u64 ses_id, __u32 tid); > extern int smb2_calc_signature(struct smb_rqst *rqst, > struct TCP_Server_Info *server); > extern int smb3_calc_signature(struct smb_rqst *rqst, > @@ -164,6 +168,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 7c3bb1b..78b4135 100644 > --- a/fs/cifs/smb2transport.c > +++ b/fs/cifs/smb2transport.c > @@ -115,23 +115,81 @@ smb3_crypto_shash_allocate(struct TCP_Server_Info *server) > return 0; > } > > -struct cifs_ses * > -smb2_find_smb_ses(struct TCP_Server_Info *server, __u64 ses_id) > +static struct cifs_ses * > +_smb2_find_smb_ses(struct TCP_Server_Info *server, __u64 ses_id) > { > 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 != ses_id) > continue; > - spin_unlock(&cifs_tcp_ses_lock); > return ses; > } > + > + return NULL; > +} > + > +struct cifs_ses * > +smb2_find_smb_ses(struct TCP_Server_Info *server, __u64 ses_id) > +{ > + struct cifs_ses *ses; > + > + spin_lock(&cifs_tcp_ses_lock); > + ses = _smb2_find_smb_ses(server, ses_id); > 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) > +{ > + 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_tcon(struct TCP_Server_Info *server, __u64 ses_id, __u32 tid) > +{ > + struct cifs_ses *ses; > + struct cifs_tcon *tcon; > + > + spin_lock(&cifs_tcp_ses_lock); > + ses = _smb2_find_smb_ses(server, ses_id ); > + if (!ses) > + return NULL; > + tcon = _smb2_find_smb_sess_tcon(ses, tid); > + spin_unlock(&cifs_tcp_ses_lock); > + > + return tcon; > +} > + > int > smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) > { > @@ -511,6 +569,7 @@ smb2_mid_entry_alloc(const struct smb2_sync_hdr *shdr, > > 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 526f053..d82a4ae 100644 > --- a/fs/cifs/transport.c > +++ b/fs/cifs/transport.c > @@ -752,9 +752,12 @@ cifs_send_recv(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, rqst, 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); > add_credits(ses->server, 1, optype); > -- > 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 Reviewed-by: Pavel Shilovsky <pshilov@xxxxxxxxxxxxx> -- 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