CIFS: Fix lease context buffer parsing
[platform/adaptation/renesas_rcar/renesas_kernel.git] / fs / cifs / smb2pdu.c
index 92fd6c5..19fafeb 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *   fs/cifs/smb2pdu.c
  *
- *   Copyright (C) International Business Machines  Corp., 2009, 2012
+ *   Copyright (C) International Business Machines  Corp., 2009, 2013
  *                 Etersoft, 2012
  *   Author(s): Steve French (sfrench@us.ibm.com)
  *              Pavel Shilovsky (pshilovsky@samba.org) 2012
@@ -108,17 +108,32 @@ smb2_hdr_assemble(struct smb2_hdr *hdr, __le16 smb2_cmd /* command */ ,
        if (!tcon)
                goto out;
 
+       /* BB FIXME when we do write > 64K add +1 for every 64K in req or rsp */
+       /* GLOBAL_CAP_LARGE_MTU will only be set if dialect > SMB2.02 */
+       /* See sections 2.2.4 and 3.2.4.1.5 of MS-SMB2 */
+       if ((tcon->ses) &&
+           (tcon->ses->server->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU))
+               hdr->CreditCharge = cpu_to_le16(1);
+       /* else CreditCharge MBZ */
+
        hdr->TreeId = tcon->tid;
        /* Uid is not converted */
        if (tcon->ses)
                hdr->SessionId = tcon->ses->Suid;
-       /* BB check following DFS flags BB */
-       /* BB do we have to add check for SHI1005_FLAGS_DFS_ROOT too? */
-       if (tcon->share_flags & SHI1005_FLAGS_DFS)
-               hdr->Flags |= SMB2_FLAGS_DFS_OPERATIONS;
-       /* BB how does SMB2 do case sensitive? */
-       /* if (tcon->nocase)
-               hdr->Flags |= SMBFLG_CASELESS; */
+
+       /*
+        * If we would set SMB2_FLAGS_DFS_OPERATIONS on open we also would have
+        * to pass the path on the Open SMB prefixed by \\server\share.
+        * Not sure when we would need to do the augmented path (if ever) and
+        * setting this flag breaks the SMB2 open operation since it is
+        * illegal to send an empty path name (without \\server\share prefix)
+        * when the DFS flag is set in the SMB open header. We could
+        * consider setting the flag on all operations other than open
+        * but it is safer to net set it for now.
+        */
+/*     if (tcon->share_flags & SHI1005_FLAGS_DFS)
+               hdr->Flags |= SMB2_FLAGS_DFS_OPERATIONS; */
+
        if (tcon->ses && tcon->ses->server && tcon->ses->server->sign)
                hdr->Flags |= SMB2_FLAGS_SIGNED;
 out:
@@ -409,18 +424,22 @@ SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses)
 
        security_blob = smb2_get_data_area_len(&blob_offset, &blob_length,
                                               &rsp->hdr);
-       if (blob_length == 0) {
-               cifs_dbg(VFS, "missing security blob on negprot\n");
-               rc = -EIO;
-               goto neg_exit;
-       }
+       /*
+        * See MS-SMB2 section 2.2.4: if no blob, client picks default which
+        * for us will be
+        *      ses->sectype = RawNTLMSSP;
+        * but for time being this is our only auth choice so doesn't matter.
+        * We just found a server which sets blob length to zero expecting raw.
+        */
+       if (blob_length == 0)
+               cifs_dbg(FYI, "missing security blob on negprot\n");
 
        rc = cifs_enable_signing(server, ses->sign);
 #ifdef CONFIG_SMB2_ASN1  /* BB REMOVEME when updated asn1.c ready */
        if (rc)
                goto neg_exit;
-
-       rc = decode_neg_token_init(security_blob, blob_length,
+       if (blob_length)
+               rc = decode_neg_token_init(security_blob, blob_length,
                                   &server->sec_type);
        if (rc == 1)
                rc = 0;
@@ -834,23 +853,24 @@ parse_lease_state(struct smb2_create_rsp *rsp)
        char *data_offset;
        struct create_lease *lc;
        bool found = false;
+       unsigned int next = 0;
+       char *name;
 
-       data_offset = (char *)rsp;
-       data_offset += 4 + le32_to_cpu(rsp->CreateContextsOffset);
+       data_offset = (char *)rsp + 4 + le32_to_cpu(rsp->CreateContextsOffset);
        lc = (struct create_lease *)data_offset;
        do {
-               char *name = le16_to_cpu(lc->ccontext.NameOffset) + (char *)lc;
+               lc = (struct create_lease *)((char *)lc + next);
+               name = le16_to_cpu(lc->ccontext.NameOffset) + (char *)lc;
                if (le16_to_cpu(lc->ccontext.NameLength) != 4 ||
                    strncmp(name, "RqLs", 4)) {
-                       lc = (struct create_lease *)((char *)lc
-                                       + le32_to_cpu(lc->ccontext.Next));
+                       next = le32_to_cpu(lc->ccontext.Next);
                        continue;
                }
                if (lc->lcontext.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS)
                        return SMB2_OPLOCK_LEVEL_NOCHANGE;
                found = true;
                break;
-       } while (le32_to_cpu(lc->ccontext.Next) != 0);
+       } while (next != 0);
 
        if (!found)
                return 0;
@@ -990,6 +1010,122 @@ creat_exit:
        return rc;
 }
 
+/*
+ *     SMB2 IOCTL is used for both IOCTLs and FSCTLs
+ */
+int
+SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
+          u64 volatile_fid, u32 opcode, bool is_fsctl, char *in_data,
+          u32 indatalen, char **out_data, u32 *plen /* returned data len */)
+{
+       struct smb2_ioctl_req *req;
+       struct smb2_ioctl_rsp *rsp;
+       struct TCP_Server_Info *server;
+       struct cifs_ses *ses = tcon->ses;
+       struct kvec iov[2];
+       int resp_buftype;
+       int num_iovecs;
+       int rc = 0;
+
+       cifs_dbg(FYI, "SMB2 IOCTL\n");
+
+       /* zero out returned data len, in case of error */
+       if (plen)
+               *plen = 0;
+
+       if (ses && (ses->server))
+               server = ses->server;
+       else
+               return -EIO;
+
+       rc = small_smb2_init(SMB2_IOCTL, tcon, (void **) &req);
+       if (rc)
+               return rc;
+
+       req->CtlCode = cpu_to_le32(opcode);
+       req->PersistentFileId = persistent_fid;
+       req->VolatileFileId = volatile_fid;
+
+       if (indatalen) {
+               req->InputCount = cpu_to_le32(indatalen);
+               /* do not set InputOffset if no input data */
+               req->InputOffset =
+                      cpu_to_le32(offsetof(struct smb2_ioctl_req, Buffer) - 4);
+               iov[1].iov_base = in_data;
+               iov[1].iov_len = indatalen;
+               num_iovecs = 2;
+       } else
+               num_iovecs = 1;
+
+       req->OutputOffset = 0;
+       req->OutputCount = 0; /* MBZ */
+
+       /*
+        * Could increase MaxOutputResponse, but that would require more
+        * than one credit. Windows typically sets this smaller, but for some
+        * ioctls it may be useful to allow server to send more. No point
+        * limiting what the server can send as long as fits in one credit
+        */
+       req->MaxOutputResponse = cpu_to_le32(0xFF00); /* < 64K uses 1 credit */
+
+       if (is_fsctl)
+               req->Flags = cpu_to_le32(SMB2_0_IOCTL_IS_FSCTL);
+       else
+               req->Flags = 0;
+
+       iov[0].iov_base = (char *)req;
+       /* 4 for rfc1002 length field */
+       iov[0].iov_len = get_rfc1002_length(req) + 4;
+
+       if (indatalen)
+               inc_rfc1001_len(req, indatalen);
+
+       rc = SendReceive2(xid, ses, iov, num_iovecs, &resp_buftype, 0);
+       rsp = (struct smb2_ioctl_rsp *)iov[0].iov_base;
+
+       if (rc != 0) {
+               if (tcon)
+                       cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE);
+               goto ioctl_exit;
+       }
+
+       /* check if caller wants to look at return data or just return rc */
+       if ((plen == NULL) || (out_data == NULL))
+               goto ioctl_exit;
+
+       *plen = le32_to_cpu(rsp->OutputCount);
+
+       /* We check for obvious errors in the output buffer length and offset */
+       if (*plen == 0)
+               goto ioctl_exit; /* server returned no data */
+       else if (*plen > 0xFF00) {
+               cifs_dbg(VFS, "srv returned invalid ioctl length: %d\n", *plen);
+               *plen = 0;
+               rc = -EIO;
+               goto ioctl_exit;
+       }
+
+       if (get_rfc1002_length(rsp) < le32_to_cpu(rsp->OutputOffset) + *plen) {
+               cifs_dbg(VFS, "Malformed ioctl resp: len %d offset %d\n", *plen,
+                       le32_to_cpu(rsp->OutputOffset));
+               *plen = 0;
+               rc = -EIO;
+               goto ioctl_exit;
+       }
+
+       *out_data = kmalloc(*plen, GFP_KERNEL);
+       if (*out_data == NULL) {
+               rc = -ENOMEM;
+               goto ioctl_exit;
+       }
+
+       memcpy(*out_data, rsp->hdr.ProtocolId + le32_to_cpu(rsp->OutputOffset),
+              *plen);
+ioctl_exit:
+       free_rsp_buf(resp_buftype, rsp);
+       return rc;
+}
+
 int
 SMB2_close(const unsigned int xid, struct cifs_tcon *tcon,
           u64 persistent_fid, u64 volatile_fid)