CVE-2016-2550: Resource Management Errors (CWE-399)
value: The Linux kernel before 4.5 allows local users to bypass file-descriptor limits and cause a denial of service (memory consumption) by leveraging incorrect tracking of descriptor ownership and sending each descriptor over a UNIX socket before closing it. NOTE: this vulnerability exists because of an incorrect fix for CVE-2013-4312.
CWE description: Weaknesses in this category are related to improper management of system resources.
CVSS v3 base score: 5.5
Published: 2016-04-27T17:59Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: include/net/af_unix.h, include/net/scm.h, net/core/scm.c, net/unix/af_unix.c, net/unix/garbage.c
Commit date: 2016-02-08 10:30:42 -0500
Commit message: unix: correctly track in-flight fds in sending process user_struct

The commit referenced in the Fixes tag incorrectly accounted the number
of in-flight fds over a unix domain socket to the original opener
of the file-descriptor. This allows another process to arbitrary
deplete the original file-openers resource limit for the maximum of
open files. Instead the sending processes and its struct cred should
be credited.

To do so, we add a reference counted struct user_struct pointer to the
scm_fp_list and use it to account for the number of inflight unix fds.

Fixes: 712f4aad406bb1 ("unix: properly account for FDs passed over unix sockets")
Reported-by: David Herrmann <dh.herrmann@gmail.com>
Cc: David Herrmann <dh.herrmann@gmail.com>
Cc: Willy Tarreau <w@1wt.eu>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>

Diff: diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 2a91a0561a478..9b4c418bebd84 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -4,12 +4,12 @@
 #include <linux/socket.h>
 #include <linux/un.h>
 #include <linux/mutex.h>
 #include <net/sock.h>
 
-void unix_inflight(struct file *fp);
-void unix_notinflight(struct file *fp);
+void unix_inflight(struct user_struct *user, struct file *fp);
+void unix_notinflight(struct user_struct *user, struct file *fp);
 void unix_gc(void);
 void wait_for_unix_gc(void);
 struct sock *unix_get_socket(struct file *filp);
 struct sock *unix_peer_get(struct sock *);
 
diff --git a/include/net/scm.h b/include/net/scm.h
index 262532d111f51..59fa93c01d2a1 100644
--- a/include/net/scm.h
+++ b/include/net/scm.h
@@ -19,10 +19,11 @@ struct scm_creds {
 };
 
 struct scm_fp_list {
 	short			count;
 	short			max;
+	struct user_struct	*user;
 	struct file		*fp[SCM_MAX_FD];
 };
 
 struct scm_cookie {
 	struct pid		*pid;		/* Skb credentials */
diff --git a/net/core/scm.c b/net/core/scm.c
index 14596fb371727..2696aefdc1488 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -85,10 +85,11 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
 		if (!fpl)
 			return -ENOMEM;
 		*fplp = fpl;
 		fpl->count = 0;
 		fpl->max = SCM_MAX_FD;
+		fpl->user = NULL;
 	}
 	fpp = &fpl->fp[fpl->count];
 
 	if (fpl->count + num > fpl->max)
 		return -EINVAL;
@@ -105,10 +106,14 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
 		if (fd < 0 || !(file = fget_raw(fd)))
 			return -EBADF;
 		*fpp++ = file;
 		fpl->count++;
 	}
+
+	if (!fpl->user)
+		fpl->user = get_uid(current_user());
+
 	return num;
 }
 
 void __scm_destroy(struct scm_cookie *scm)
 {
@@ -117,10 +122,11 @@ void __scm_destroy(struct scm_cookie *scm)
 
 	if (fpl) {
 		scm->fp = NULL;
 		for (i=fpl->count-1; i>=0; i--)
 			fput(fpl->fp[i]);
+		free_uid(fpl->user);
 		kfree(fpl);
 	}
 }
 EXPORT_SYMBOL(__scm_destroy);
 
@@ -334,9 +340,10 @@ struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl)
 			  GFP_KERNEL);
 	if (new_fpl) {
 		for (i = 0; i < fpl->count; i++)
 			get_file(fpl->fp[i]);
 		new_fpl->max = new_fpl->count;
+		new_fpl->user = get_uid(fpl->user);
 	}
 	return new_fpl;
 }
 EXPORT_SYMBOL(scm_fp_dup);
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 49d5093eb0553..29be035f9c650 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1494,11 +1494,11 @@ static void unix_detach_fds(struct scm_cookie *scm, struct sk_buff *skb)
 
 	scm->fp = UNIXCB(skb).fp;
 	UNIXCB(skb).fp = NULL;
 
 	for (i = scm->fp->count-1; i >= 0; i--)
-		unix_notinflight(scm->fp->fp[i]);
+		unix_notinflight(scm->fp->user, scm->fp->fp[i]);
 }
 
 static void unix_destruct_scm(struct sk_buff *skb)
 {
 	struct scm_cookie scm;
@@ -1559,11 +1559,11 @@ static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)
 	UNIXCB(skb).fp = scm_fp_dup(scm->fp);
 	if (!UNIXCB(skb).fp)
 		return -ENOMEM;
 
 	for (i = scm->fp->count - 1; i >= 0; i--)
-		unix_inflight(scm->fp->fp[i]);
+		unix_inflight(scm->fp->user, scm->fp->fp[i]);
 	return max_level;
 }
 
 static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool send_fds)
 {
diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index 8fcdc2283af50..6a0d48525fcf9 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -114,11 +114,11 @@ struct sock *unix_get_socket(struct file *filp)
 
 /* Keep the number of times in flight count for the file
  * descriptor if it is for an AF_UNIX socket.
  */
 
-void unix_inflight(struct file *fp)
+void unix_inflight(struct user_struct *user, struct file *fp)
 {
 	struct sock *s = unix_get_socket(fp);
 
 	spin_lock(&unix_gc_lock);
 
@@ -131,15 +131,15 @@ void unix_inflight(struct file *fp)
 		} else {
 			BUG_ON(list_empty(&u->link));
 		}
 		unix_tot_inflight++;
 	}
-	fp->f_cred->user->unix_inflight++;
+	user->unix_inflight++;
 	spin_unlock(&unix_gc_lock);
 }
 
-void unix_notinflight(struct file *fp)
+void unix_notinflight(struct user_struct *user, struct file *fp)
 {
 	struct sock *s = unix_get_socket(fp);
 
 	spin_lock(&unix_gc_lock);
 
@@ -150,11 +150,11 @@ void unix_notinflight(struct file *fp)
 
 		if (atomic_long_dec_and_test(&u->inflight))
 			list_del_init(&u->link);
 		unix_tot_inflight--;
 	}
-	fp->f_cred->user->unix_inflight--;
+	user->unix_inflight--;
 	spin_unlock(&unix_gc_lock);
 }
 
 static void scan_inflight(struct sock *x, void (*func)(struct unix_sock *),
 			  struct sk_buff_head *hitlist)

Vulnerable code: void unix_inflight(struct file *fp);
void unix_notinflight(struct file *fp);
		unix_notinflight(scm->fp->fp[i]);
		unix_inflight(scm->fp->fp[i]);
void unix_inflight(struct file *fp)
	fp->f_cred->user->unix_inflight++;
void unix_notinflight(struct file *fp)
	fp->f_cred->user->unix_inflight--;
Fixed code: void unix_inflight(struct user_struct *user, struct file *fp);
void unix_notinflight(struct user_struct *user, struct file *fp);
	struct user_struct	*user;
		fpl->user = NULL;

	if (!fpl->user)
		fpl->user = get_uid(current_user());

		free_uid(fpl->user);
		new_fpl->user = get_uid(fpl->user);
		unix_notinflight(scm->fp->user, scm->fp->fp[i]);
		unix_inflight(scm->fp->user, scm->fp->fp[i]);
void unix_inflight(struct user_struct *user, struct file *fp)
	user->unix_inflight++;
void unix_notinflight(struct user_struct *user, struct file *fp)
	user->unix_inflight--;