]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/commitdiff
SMB3: Add support for multidialect negotiate (SMB2.1 and later)
authorSteve French <smfrench@gmail.com>
Sun, 17 Sep 2017 15:41:35 +0000 (10:41 -0500)
committerSeth Forshee <seth.forshee@canonical.com>
Thu, 12 Oct 2017 21:20:03 +0000 (16:20 -0500)
BugLink: http://bugs.launchpad.net/bugs/1721777
commit 9764c02fcbad40001fd3f63558d918e4d519bb75 upstream.

With the need to discourage use of less secure dialect, SMB1 (CIFS),
we temporarily upgraded the dialect to SMB3 in 4.13, but since there
are various servers which only support SMB2.1 (2.1 is more secure
than CIFS/SMB1) but not optimal for a default dialect - add support
for multidialect negotiation.  cifs.ko will now request SMB2.1
or later (ie SMB2.1 or SMB3.0, SMB3.02) and the server will
pick the latest most secure one it can support.

In addition since we are sending multidialect negotiate, add
support for secure negotiate to validate that a man in the
middle didn't downgrade us.

Signed-off-by: Steve French <smfrench@gmail.com>
Reviewed-by: Pavel Shilovsky <pshilov@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Seth Forshee <seth.forshee@canonical.com>
fs/cifs/cifsglob.h
fs/cifs/connect.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c
fs/cifs/smb2pdu.h

index 221693fe49ec8114d307f3f3974f0420e871b73c..03b6eae0ae28f056e20f5278a1f62174d2f4d88f 100644 (file)
@@ -188,6 +188,8 @@ enum smb_version {
 #ifdef CONFIG_CIFS_SMB311
        Smb_311,
 #endif /* SMB311 */
+       Smb_3any,
+       Smb_default,
        Smb_version_err
 };
 
@@ -1701,6 +1703,10 @@ extern struct smb_version_values smb20_values;
 #define SMB21_VERSION_STRING   "2.1"
 extern struct smb_version_operations smb21_operations;
 extern struct smb_version_values smb21_values;
+#define SMBDEFAULT_VERSION_STRING "default"
+extern struct smb_version_values smbdefault_values;
+#define SMB3ANY_VERSION_STRING "3"
+extern struct smb_version_values smb3any_values;
 #define SMB30_VERSION_STRING   "3.0"
 extern struct smb_version_operations smb30_operations;
 extern struct smb_version_values smb30_values;
index 5a97744161c1e070a2635d81d2c40b90b8733a1c..9e12679ffef5ac05a0a6490c34c1e80419c06be8 100644 (file)
@@ -301,6 +301,8 @@ static const match_table_t cifs_smb_version_tokens = {
        { Smb_311, SMB311_VERSION_STRING },
        { Smb_311, ALT_SMB311_VERSION_STRING },
 #endif /* SMB311 */
+       { Smb_3any, SMB3ANY_VERSION_STRING },
+       { Smb_default, SMBDEFAULT_VERSION_STRING },
        { Smb_version_err, NULL }
 };
 
@@ -1147,6 +1149,14 @@ cifs_parse_smb_version(char *value, struct smb_vol *vol)
                vol->vals = &smb311_values;
                break;
 #endif /* SMB311 */
+       case Smb_3any:
+               vol->ops = &smb30_operations; /* currently identical with 3.0 */
+               vol->vals = &smb3any_values;
+               break;
+       case Smb_default:
+               vol->ops = &smb30_operations; /* currently identical with 3.0 */
+               vol->vals = &smbdefault_values;
+               break;
        default:
                cifs_dbg(VFS, "Unknown vers= option specified: %s\n", value);
                return 1;
@@ -1273,9 +1283,9 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
 
        vol->actimeo = CIFS_DEF_ACTIMEO;
 
-       /* FIXME: add autonegotiation for SMB3 or later rather than just SMB3 */
-       vol->ops = &smb30_operations; /* both secure and accepted widely */
-       vol->vals = &smb30_values;
+       /* offer SMB2.1 and later (SMB3 etc). Secure and widely accepted */
+       vol->ops = &smb30_operations;
+       vol->vals = &smbdefault_values;
 
        vol->echo_interval = SMB_ECHO_INTERVAL_DEFAULT;
 
@@ -1987,11 +1997,10 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
 
        if (got_version == false)
                pr_warn("No dialect specified on mount. Default has changed to "
-                       "a more secure dialect, SMB3 (vers=3.0), from CIFS "
+                       "a more secure dialect, SMB2.1 or later (e.g. SMB3), from CIFS "
                        "(SMB1). To use the less secure SMB1 dialect to access "
-                       "old servers which do not support SMB3 specify vers=1.0"
-                       " on mount. For somewhat newer servers such as Windows "
-                       "7 try vers=2.1.\n");
+                       "old servers which do not support SMB3 (or SMB2.1) specify vers=1.0"
+                       " on mount.\n");
 
        kfree(mountdata_copy);
        return 0;
@@ -2132,6 +2141,7 @@ static int match_server(struct TCP_Server_Info *server, struct smb_vol *vol)
        if (vol->nosharesock)
                return 0;
 
+       /* BB update this for smb3any and default case */
        if ((server->vals != vol->vals) || (server->ops != vol->ops))
                return 0;
 
index cfacf2c97e9418c991b7264f7b75ee271039b260..a6c94812cfa3a9260c620455eb582d72d190beee 100644 (file)
@@ -2906,6 +2906,46 @@ struct smb_version_values smb21_values = {
        .create_lease_size = sizeof(struct create_lease),
 };
 
+struct smb_version_values smb3any_values = {
+       .version_string = SMB3ANY_VERSION_STRING,
+       .protocol_id = SMB302_PROT_ID, /* doesn't matter, send protocol array */
+       .req_capabilities = SMB2_GLOBAL_CAP_DFS | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_LARGE_MTU | SMB2_GLOBAL_CAP_PERSISTENT_HANDLES | SMB2_GLOBAL_CAP_ENCRYPTION,
+       .large_lock_type = 0,
+       .exclusive_lock_type = SMB2_LOCKFLAG_EXCLUSIVE_LOCK,
+       .shared_lock_type = SMB2_LOCKFLAG_SHARED_LOCK,
+       .unlock_lock_type = SMB2_LOCKFLAG_UNLOCK,
+       .header_size = sizeof(struct smb2_hdr),
+       .max_header_size = MAX_SMB2_HDR_SIZE,
+       .read_rsp_size = sizeof(struct smb2_read_rsp) - 1,
+       .lock_cmd = SMB2_LOCK,
+       .cap_unix = 0,
+       .cap_nt_find = SMB2_NT_FIND,
+       .cap_large_files = SMB2_LARGE_FILES,
+       .signing_enabled = SMB2_NEGOTIATE_SIGNING_ENABLED | SMB2_NEGOTIATE_SIGNING_REQUIRED,
+       .signing_required = SMB2_NEGOTIATE_SIGNING_REQUIRED,
+       .create_lease_size = sizeof(struct create_lease_v2),
+};
+
+struct smb_version_values smbdefault_values = {
+       .version_string = SMBDEFAULT_VERSION_STRING,
+       .protocol_id = SMB302_PROT_ID, /* doesn't matter, send protocol array */
+       .req_capabilities = SMB2_GLOBAL_CAP_DFS | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_LARGE_MTU | SMB2_GLOBAL_CAP_PERSISTENT_HANDLES | SMB2_GLOBAL_CAP_ENCRYPTION,
+       .large_lock_type = 0,
+       .exclusive_lock_type = SMB2_LOCKFLAG_EXCLUSIVE_LOCK,
+       .shared_lock_type = SMB2_LOCKFLAG_SHARED_LOCK,
+       .unlock_lock_type = SMB2_LOCKFLAG_UNLOCK,
+       .header_size = sizeof(struct smb2_hdr),
+       .max_header_size = MAX_SMB2_HDR_SIZE,
+       .read_rsp_size = sizeof(struct smb2_read_rsp) - 1,
+       .lock_cmd = SMB2_LOCK,
+       .cap_unix = 0,
+       .cap_nt_find = SMB2_NT_FIND,
+       .cap_large_files = SMB2_LARGE_FILES,
+       .signing_enabled = SMB2_NEGOTIATE_SIGNING_ENABLED | SMB2_NEGOTIATE_SIGNING_REQUIRED,
+       .signing_required = SMB2_NEGOTIATE_SIGNING_REQUIRED,
+       .create_lease_size = sizeof(struct create_lease_v2),
+};
+
 struct smb_version_values smb30_values = {
        .version_string = SMB30_VERSION_STRING,
        .protocol_id = SMB30_PROT_ID,
index 11a15d77b85b805bd23a197db55256aba27f4a72..3d903aa7e64b7b48768cd68f53efa541f7ca1625 100644 (file)
@@ -479,10 +479,25 @@ SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses)
 
        req->hdr.sync_hdr.SessionId = 0;
 
-       req->Dialects[0] = cpu_to_le16(ses->server->vals->protocol_id);
-
-       req->DialectCount = cpu_to_le16(1); /* One vers= at a time for now */
-       inc_rfc1001_len(req, 2);
+       if (strcmp(ses->server->vals->version_string,
+                  SMB3ANY_VERSION_STRING) == 0) {
+               req->Dialects[0] = cpu_to_le16(SMB30_PROT_ID);
+               req->Dialects[1] = cpu_to_le16(SMB302_PROT_ID);
+               req->DialectCount = cpu_to_le16(2);
+               inc_rfc1001_len(req, 4);
+       } else if (strcmp(ses->server->vals->version_string,
+                  SMBDEFAULT_VERSION_STRING) == 0) {
+               req->Dialects[0] = cpu_to_le16(SMB21_PROT_ID);
+               req->Dialects[1] = cpu_to_le16(SMB30_PROT_ID);
+               req->Dialects[2] = cpu_to_le16(SMB302_PROT_ID);
+               req->DialectCount = cpu_to_le16(3);
+               inc_rfc1001_len(req, 6);
+       } else {
+               /* otherwise send specific dialect */
+               req->Dialects[0] = cpu_to_le16(ses->server->vals->protocol_id);
+               req->DialectCount = cpu_to_le16(1);
+               inc_rfc1001_len(req, 2);
+       }
 
        /* only one of SMB2 signing flags may be set in SMB2 request */
        if (ses->sign)
@@ -516,16 +531,42 @@ SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses)
         */
        if (rc == -EOPNOTSUPP) {
                cifs_dbg(VFS, "Dialect not supported by server. Consider "
-                       "specifying vers=1.0 or vers=2.1 on mount for accessing"
+                       "specifying vers=1.0 or vers=2.0 on mount for accessing"
                        " older servers\n");
                goto neg_exit;
        } else if (rc != 0)
                goto neg_exit;
 
+       if (strcmp(ses->server->vals->version_string,
+                  SMB3ANY_VERSION_STRING) == 0) {
+               if (rsp->DialectRevision == cpu_to_le16(SMB20_PROT_ID)) {
+                       cifs_dbg(VFS,
+                               "SMB2 dialect returned but not requested\n");
+                       return -EIO;
+               } else if (rsp->DialectRevision == cpu_to_le16(SMB21_PROT_ID)) {
+                       cifs_dbg(VFS,
+                               "SMB2.1 dialect returned but not requested\n");
+                       return -EIO;
+               }
+       } else if (strcmp(ses->server->vals->version_string,
+                  SMBDEFAULT_VERSION_STRING) == 0) {
+               if (rsp->DialectRevision == cpu_to_le16(SMB20_PROT_ID)) {
+                       cifs_dbg(VFS,
+                               "SMB2 dialect returned but not requested\n");
+                       return -EIO;
+               } else if (rsp->DialectRevision == cpu_to_le16(SMB21_PROT_ID)) {
+                       /* ops set to 3.0 by default for default so update */
+                       ses->server->ops = &smb21_operations;
+               }
+       } else if (rsp->DialectRevision != ses->server->vals->protocol_id) {
+               /* if requested single dialect ensure returned dialect matched */
+               cifs_dbg(VFS, "Illegal 0x%x dialect returned: not requested\n",
+                       cpu_to_le16(rsp->DialectRevision));
+               return -EIO;
+       }
+
        cifs_dbg(FYI, "mode 0x%x\n", rsp->SecurityMode);
 
-       /* BB we may eventually want to match the negotiated vs. requested
-          dialect, even though we are only requesting one at a time */
        if (rsp->DialectRevision == cpu_to_le16(SMB20_PROT_ID))
                cifs_dbg(FYI, "negotiated smb2.0 dialect\n");
        else if (rsp->DialectRevision == cpu_to_le16(SMB21_PROT_ID))
@@ -546,6 +587,8 @@ SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses)
        }
        server->dialect = le16_to_cpu(rsp->DialectRevision);
 
+       /* BB: add check that dialect was valid given dialect(s) we asked for */
+
        /* SMB2 only has an extended negflavor */
        server->negflavor = CIFS_NEGFLAVOR_EXTENDED;
        /* set it to the maximum buffer size value we can send with 1 credit */
@@ -594,6 +637,7 @@ int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon)
        struct validate_negotiate_info_req vneg_inbuf;
        struct validate_negotiate_info_rsp *pneg_rsp;
        u32 rsplen;
+       u32 inbuflen; /* max of 4 dialects */
 
        cifs_dbg(FYI, "validate negotiate\n");
 
@@ -622,9 +666,30 @@ int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon)
        else
                vneg_inbuf.SecurityMode = 0;
 
-       vneg_inbuf.DialectCount = cpu_to_le16(1);
-       vneg_inbuf.Dialects[0] =
-               cpu_to_le16(tcon->ses->server->vals->protocol_id);
+
+       if (strcmp(tcon->ses->server->vals->version_string,
+               SMB3ANY_VERSION_STRING) == 0) {
+               vneg_inbuf.Dialects[0] = cpu_to_le16(SMB30_PROT_ID);
+               vneg_inbuf.Dialects[1] = cpu_to_le16(SMB302_PROT_ID);
+               vneg_inbuf.DialectCount = cpu_to_le16(2);
+               /* structure is big enough for 3 dialects, sending only 2 */
+               inbuflen = sizeof(struct validate_negotiate_info_req) - 2;
+       } else if (strcmp(tcon->ses->server->vals->version_string,
+               SMBDEFAULT_VERSION_STRING) == 0) {
+               vneg_inbuf.Dialects[0] = cpu_to_le16(SMB21_PROT_ID);
+               vneg_inbuf.Dialects[1] = cpu_to_le16(SMB30_PROT_ID);
+               vneg_inbuf.Dialects[2] = cpu_to_le16(SMB302_PROT_ID);
+               vneg_inbuf.DialectCount = cpu_to_le16(3);
+               /* structure is big enough for 3 dialects */
+               inbuflen = sizeof(struct validate_negotiate_info_req);
+       } else {
+               /* otherwise specific dialect was requested */
+               vneg_inbuf.Dialects[0] =
+                       cpu_to_le16(tcon->ses->server->vals->protocol_id);
+               vneg_inbuf.DialectCount = cpu_to_le16(1);
+               /* structure is big enough for 3 dialects, sending only 1 */
+               inbuflen = sizeof(struct validate_negotiate_info_req) - 4;
+       }
 
        rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID,
                FSCTL_VALIDATE_NEGOTIATE_INFO, true /* is_fsctl */,
index 2826882c81d14f138b393824fac9c9091311cd1f..46b6cbce967508dad39ba3b0d85c5d0479aa098b 100644 (file)
@@ -716,7 +716,7 @@ struct validate_negotiate_info_req {
        __u8   Guid[SMB2_CLIENT_GUID_SIZE];
        __le16 SecurityMode;
        __le16 DialectCount;
-       __le16 Dialects[1]; /* dialect (someday maybe list) client asked for */
+       __le16 Dialects[3]; /* BB expand this if autonegotiate > 3 dialects */
 } __packed;
 
 struct validate_negotiate_info_rsp {