Merge git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf
[linux-2.6-microblaze.git] / fs / cifs / smb2misc.c
index e311f58..0516fc4 100644 (file)
@@ -29,6 +29,7 @@
 #include "cifs_unicode.h"
 #include "smb2status.h"
 #include "smb2glob.h"
+#include "nterr.h"
 
 static int
 check_smb2_hdr(struct smb2_sync_hdr *shdr, __u64 mid)
@@ -249,16 +250,10 @@ smb2_check_message(char *buf, unsigned int len, struct TCP_Server_Info *srvr)
                 * of junk. Other servers match RFC1001 len to actual
                 * SMB2/SMB3 frame length (header + smb2 response specific data)
                 * Some windows servers also pad up to 8 bytes when compounding.
-                * If pad is longer than eight bytes, log the server behavior
-                * (once), since may indicate a problem but allow it and continue
-                * since the frame is parseable.
                 */
-               if (clc_len < len) {
-                       pr_warn_once(
-                            "srv rsp padded more than expected. Length %d not %d for cmd:%d mid:%llu\n",
-                            len, clc_len, command, mid);
+               if (clc_len < len)
                        return 0;
-               }
+
                pr_warn_once(
                        "srv rsp too short, len %d not %d. cmd:%d mid:%llu\n",
                        len, clc_len, command, mid);
@@ -534,7 +529,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 
                cifs_dbg(FYI, "found in the open list\n");
                cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
-                        le32_to_cpu(rsp->NewLeaseState));
+                        lease_state);
 
                if (ack_req)
                        cfile->oplock_break_cancelled = false;
@@ -543,17 +538,8 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 
                set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags);
 
-               /*
-                * Set or clear flags depending on the lease state being READ.
-                * HANDLE caching flag should be added when the client starts
-                * to defer closing remote file handles with HANDLE leases.
-                */
-               if (lease_state & SMB2_LEASE_READ_CACHING_HE)
-                       set_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                               &cinode->flags);
-               else
-                       clear_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                                 &cinode->flags);
+               cfile->oplock_epoch = le16_to_cpu(rsp->Epoch);
+               cfile->oplock_level = lease_state;
 
                cifs_queue_oplock_break(cfile);
                kfree(lw);
@@ -576,7 +562,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 
                cifs_dbg(FYI, "found in the pending open list\n");
                cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
-                        le32_to_cpu(rsp->NewLeaseState));
+                        lease_state);
 
                open->oplock = lease_state;
        }
@@ -673,10 +659,10 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
        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(tmp1, &ses->tcon_list) {
                        tcon = list_entry(tmp1, struct cifs_tcon, tcon_list);
 
-                       cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks);
                        spin_lock(&tcon->open_file_lock);
                        list_for_each(tmp2, &tcon->openFileList) {
                                cfile = list_entry(tmp2, struct cifsFileInfo,
@@ -688,6 +674,8 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
                                        continue;
 
                                cifs_dbg(FYI, "file id match, oplock break\n");
+                               cifs_stats_inc(
+                                   &tcon->stats.cifs_stats.num_oplock_brks);
                                cinode = CIFS_I(d_inode(cfile->dentry));
                                spin_lock(&cfile->file_info_lock);
                                if (!CIFS_CACHE_WRITE(cinode) &&
@@ -699,18 +687,9 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
                                set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
                                        &cinode->flags);
 
-                               /*
-                                * Set flag if the server downgrades the oplock
-                                * to L2 else clear.
-                                */
-                               if (rsp->OplockLevel)
-                                       set_bit(
-                                          CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                                          &cinode->flags);
-                               else
-                                       clear_bit(
-                                          CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                                          &cinode->flags);
+                               cfile->oplock_epoch = 0;
+                               cfile->oplock_level = rsp->OplockLevel;
+
                                spin_unlock(&cfile->file_info_lock);
 
                                cifs_queue_oplock_break(cfile);
@@ -720,9 +699,6 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
                                return true;
                        }
                        spin_unlock(&tcon->open_file_lock);
-                       spin_unlock(&cifs_tcp_ses_lock);
-                       cifs_dbg(FYI, "No matching file for oplock break\n");
-                       return true;
                }
        }
        spin_unlock(&cifs_tcp_ses_lock);
@@ -735,45 +711,98 @@ smb2_cancelled_close_fid(struct work_struct *work)
 {
        struct close_cancelled_open *cancelled = container_of(work,
                                        struct close_cancelled_open, work);
+       struct cifs_tcon *tcon = cancelled->tcon;
+       int rc;
 
-       cifs_dbg(VFS, "Close unmatched open\n");
+       if (cancelled->mid)
+               cifs_tcon_dbg(VFS, "Close unmatched open for MID:%llx\n",
+                             cancelled->mid);
+       else
+               cifs_tcon_dbg(VFS, "Close interrupted close\n");
 
-       SMB2_close(0, cancelled->tcon, cancelled->fid.persistent_fid,
-                  cancelled->fid.volatile_fid);
-       cifs_put_tcon(cancelled->tcon);
+       rc = SMB2_close(0, tcon, cancelled->fid.persistent_fid,
+                       cancelled->fid.volatile_fid);
+       if (rc)
+               cifs_tcon_dbg(VFS, "Close cancelled mid failed rc:%d\n", rc);
+
+       cifs_put_tcon(tcon);
        kfree(cancelled);
 }
 
+/*
+ * Caller should already has an extra reference to @tcon
+ * This function is used to queue work to close a handle to prevent leaks
+ * on the server.
+ * We handle two cases. If an open was interrupted after we sent the
+ * SMB2_CREATE to the server but before we processed the reply, and second
+ * if a close was interrupted before we sent the SMB2_CLOSE to the server.
+ */
+static int
+__smb2_handle_cancelled_cmd(struct cifs_tcon *tcon, __u16 cmd, __u64 mid,
+                           __u64 persistent_fid, __u64 volatile_fid)
+{
+       struct close_cancelled_open *cancelled;
+
+       cancelled = kzalloc(sizeof(*cancelled), GFP_KERNEL);
+       if (!cancelled)
+               return -ENOMEM;
+
+       cancelled->fid.persistent_fid = persistent_fid;
+       cancelled->fid.volatile_fid = volatile_fid;
+       cancelled->tcon = tcon;
+       cancelled->cmd = cmd;
+       cancelled->mid = mid;
+       INIT_WORK(&cancelled->work, smb2_cancelled_close_fid);
+       WARN_ON(queue_work(cifsiod_wq, &cancelled->work) == false);
+
+       return 0;
+}
+
+int
+smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid,
+                           __u64 volatile_fid)
+{
+       int rc;
+
+       cifs_dbg(FYI, "%s: tc_count=%d\n", __func__, tcon->tc_count);
+       spin_lock(&cifs_tcp_ses_lock);
+       tcon->tc_count++;
+       spin_unlock(&cifs_tcp_ses_lock);
+
+       rc = __smb2_handle_cancelled_cmd(tcon, SMB2_CLOSE_HE, 0,
+                                        persistent_fid, volatile_fid);
+       if (rc)
+               cifs_put_tcon(tcon);
+
+       return rc;
+}
+
 int
 smb2_handle_cancelled_mid(char *buffer, struct TCP_Server_Info *server)
 {
        struct smb2_sync_hdr *sync_hdr = (struct smb2_sync_hdr *)buffer;
        struct smb2_create_rsp *rsp = (struct smb2_create_rsp *)buffer;
        struct cifs_tcon *tcon;
-       struct close_cancelled_open *cancelled;
+       int rc;
 
        if (sync_hdr->Command != SMB2_CREATE ||
            sync_hdr->Status != STATUS_SUCCESS)
                return 0;
 
-       cancelled = kzalloc(sizeof(*cancelled), GFP_KERNEL);
-       if (!cancelled)
-               return -ENOMEM;
-
        tcon = smb2_find_smb_tcon(server, sync_hdr->SessionId,
                                  sync_hdr->TreeId);
-       if (!tcon) {
-               kfree(cancelled);
+       if (!tcon)
                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);
+       rc = __smb2_handle_cancelled_cmd(tcon,
+                                        le16_to_cpu(sync_hdr->Command),
+                                        le64_to_cpu(sync_hdr->MessageId),
+                                        rsp->PersistentFileId,
+                                        rsp->VolatileFileId);
+       if (rc)
+               cifs_put_tcon(tcon);
 
-       return 0;
+       return rc;
 }
 
 /**
@@ -788,23 +817,37 @@ smb311_update_preauth_hash(struct cifs_ses *ses, struct kvec *iov, int nvec)
        int i, rc;
        struct sdesc *d;
        struct smb2_sync_hdr *hdr;
+       struct TCP_Server_Info *server = cifs_ses_server(ses);
 
-       if (ses->server->tcpStatus == CifsGood) {
-               /* skip non smb311 connections */
-               if (ses->server->dialect != SMB311_PROT_ID)
-                       return 0;
+       hdr = (struct smb2_sync_hdr *)iov[0].iov_base;
+       /* neg prot are always taken */
+       if (hdr->Command == SMB2_NEGOTIATE)
+               goto ok;
 
-               /* skip last sess setup response */
-               hdr = (struct smb2_sync_hdr *)iov[0].iov_base;
-               if (hdr->Flags & SMB2_FLAGS_SIGNED)
-                       return 0;
-       }
+       /*
+        * If we process a command which wasn't a negprot it means the
+        * neg prot was already done, so the server dialect was set
+        * and we can test it. Preauth requires 3.1.1 for now.
+        */
+       if (server->dialect != SMB311_PROT_ID)
+               return 0;
+
+       if (hdr->Command != SMB2_SESSION_SETUP)
+               return 0;
+
+       /* skip last sess setup response */
+       if ((hdr->Flags & SMB2_FLAGS_SERVER_TO_REDIR)
+           && (hdr->Status == NT_STATUS_OK
+               || (hdr->Status !=
+                   cpu_to_le32(NT_STATUS_MORE_PROCESSING_REQUIRED))))
+               return 0;
 
-       rc = smb311_crypto_shash_allocate(ses->server);
+ok:
+       rc = smb311_crypto_shash_allocate(server);
        if (rc)
                return rc;
 
-       d = ses->server->secmech.sdescsha512;
+       d = server->secmech.sdescsha512;
        rc = crypto_shash_init(&d->shash);
        if (rc) {
                cifs_dbg(VFS, "%s: could not init sha512 shash\n", __func__);