CVE-2011-1585: Permissions, Privileges, and Access Controls (CWE-264)
value: The cifs_find_smb_ses function in fs/cifs/connect.c in the Linux kernel before 2.6.36 does not properly determine the associations between users and sessions, which allows local users to bypass CIFS share authentication by leveraging a mount of a share by a different user.
CWE description: Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Published: 2013-06-08T13:05Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: fs/cifs/cifsglob.h, fs/cifs/connect.c
Commit date: 2010-08-02 12:40:35 +0000
Commit message: cifs: clean up cifs_find_smb_ses (try #2)

This patch replaces the earlier patch by the same name. The only
difference is that MAX_PASSWORD_SIZE has been increased to attempt to
match the limits that windows enforces.

Do a better job of matching sessions by authtype. Matching by username
for a Kerberos session is incorrect, and anonymous sessions need special
handling.

Also, in the case where we do match by username, we also need to match
by password. That ensures that someone else doesn't "borrow" an existing
session without needing to know the password.

Finally, passwords can be longer than 16 bytes. Bump MAX_PASSWORD_SIZE
to 512 to match the size that the userspace mount helper allows.

Signed-off-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Steve French <sfrench@us.ibm.com>

Diff: diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 8fb1d10b8742..7b91cb4f0da4 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -35,11 +35,11 @@
 #define MAX_TREE_SIZE (2 + MAX_SERVER_SIZE + 1 + MAX_SHARE_SIZE + 1)
 #define MAX_SERVER_SIZE 15
 #define MAX_SHARE_SIZE  64	/* used to be 20, this should still be enough */
 #define MAX_USERNAME_SIZE 32	/* 32 is to allow for 15 char names + null
 				   termination then *2 for unicode versions */
-#define MAX_PASSWORD_SIZE 16
+#define MAX_PASSWORD_SIZE 512  /* max for windows seems to be 256 wide chars */
 
 #define CIFS_MIN_RCV_POOL 4
 
 /*
  * MAX_REQ is the maximum number of requests that WE will send
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index b24e4cea4e3c..b2063ce113ec 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -1642,21 +1642,31 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
 	}
 	return ERR_PTR(rc);
 }
 
 static struct cifsSesInfo *
-cifs_find_smb_ses(struct TCP_Server_Info *server, char *username)
+cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol)
 {
-	struct list_head *tmp;
 	struct cifsSesInfo *ses;
 
 	write_lock(&cifs_tcp_ses_lock);
-	list_for_each(tmp, &server->smb_ses_list) {
-		ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list);
-		if (strncmp(ses->userName, username, MAX_USERNAME_SIZE))
-			continue;
-
+	list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+		switch (server->secType) {
+		case Kerberos:
+			if (vol->linux_uid != ses->linux_uid)
+				continue;
+			break;
+		default:
+			/* anything else takes username/password */
+			if (strncmp(ses->userName, vol->username,
+				    MAX_USERNAME_SIZE))
+				continue;
+			if (strlen(vol->username) != 0 &&
+			    strncmp(ses->password, vol->password,
+				    MAX_PASSWORD_SIZE))
+				continue;
+		}
 		++ses->ses_count;
 		write_unlock(&cifs_tcp_ses_lock);
 		return ses;
 	}
 	write_unlock(&cifs_tcp_ses_lock);
@@ -1694,11 +1704,11 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
 	int rc = -ENOMEM, xid;
 	struct cifsSesInfo *ses;
 
 	xid = GetXid();
 
-	ses = cifs_find_smb_ses(server, volume_info->username);
+	ses = cifs_find_smb_ses(server, volume_info);
 	if (ses) {
 		cFYI(1, "Existing smb sess found (status=%d)", ses->status);
 
 		/* existing SMB ses has a server reference already */
 		cifs_put_tcp_session(server);

Vulnerable code: #define MAX_PASSWORD_SIZE 16
cifs_find_smb_ses(struct TCP_Server_Info *server, char *username)
	struct list_head *tmp;
	list_for_each(tmp, &server->smb_ses_list) {
		ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list);
		if (strncmp(ses->userName, username, MAX_USERNAME_SIZE))
			continue;

	ses = cifs_find_smb_ses(server, volume_info->username);
Fixed code: #define MAX_PASSWORD_SIZE 512  /* max for windows seems to be 256 wide chars */
cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol)
	list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
		switch (server->secType) {
		case Kerberos:
			if (vol->linux_uid != ses->linux_uid)
				continue;
			break;
		default:
			/* anything else takes username/password */
			if (strncmp(ses->userName, vol->username,
				    MAX_USERNAME_SIZE))
				continue;
			if (strlen(vol->username) != 0 &&
			    strncmp(ses->password, vol->password,
				    MAX_PASSWORD_SIZE))
				continue;
		}
	ses = cifs_find_smb_ses(server, volume_info);