Commit 712f4aad406bb1ed67f3f98d04c044191f0ff593

Authored by willy tarreau
Committed by David S. Miller
1 parent 3e4006f0b8

unix: properly account for FDs passed over unix sockets

It is possible for a process to allocate and accumulate far more FDs than
the process' limit by sending them over a unix socket then closing them
to keep the process' fd count low.

This change addresses this problem by keeping track of the number of FDs
in flight per user and preventing non-privileged processes from having
more FDs in flight than their configured FD limit.

Reported-by: socketpair@gmail.com
Reported-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Mitigates: CVE-2013-4312 (Linux 2.0+)
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David S. Miller <davem@davemloft.net>

Showing 3 changed files with 29 additions and 9 deletions Side-by-side Diff

include/linux/sched.h
... ... @@ -830,6 +830,7 @@
830 830 unsigned long mq_bytes; /* How many bytes can be allocated to mqueue? */
831 831 #endif
832 832 unsigned long locked_shm; /* How many pages of mlocked shm ? */
  833 + unsigned long unix_inflight; /* How many files in flight in unix sockets */
833 834  
834 835 #ifdef CONFIG_KEYS
835 836 struct key *uid_keyring; /* UID specific keyring */
... ... @@ -1513,6 +1513,21 @@
1513 1513 sock_wfree(skb);
1514 1514 }
1515 1515  
  1516 +/*
  1517 + * The "user->unix_inflight" variable is protected by the garbage
  1518 + * collection lock, and we just read it locklessly here. If you go
  1519 + * over the limit, there might be a tiny race in actually noticing
  1520 + * it across threads. Tough.
  1521 + */
  1522 +static inline bool too_many_unix_fds(struct task_struct *p)
  1523 +{
  1524 + struct user_struct *user = current_user();
  1525 +
  1526 + if (unlikely(user->unix_inflight > task_rlimit(p, RLIMIT_NOFILE)))
  1527 + return !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN);
  1528 + return false;
  1529 +}
  1530 +
1516 1531 #define MAX_RECURSION_LEVEL 4
1517 1532  
1518 1533 static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)
... ... @@ -1521,6 +1536,9 @@
1521 1536 unsigned char max_level = 0;
1522 1537 int unix_sock_count = 0;
1523 1538  
  1539 + if (too_many_unix_fds(current))
  1540 + return -ETOOMANYREFS;
  1541 +
1524 1542 for (i = scm->fp->count - 1; i >= 0; i--) {
1525 1543 struct sock *sk = unix_get_socket(scm->fp->fp[i]);
1526 1544  
... ... @@ -1542,10 +1560,8 @@
1542 1560 if (!UNIXCB(skb).fp)
1543 1561 return -ENOMEM;
1544 1562  
1545   - if (unix_sock_count) {
1546   - for (i = scm->fp->count - 1; i >= 0; i--)
1547   - unix_inflight(scm->fp->fp[i]);
1548   - }
  1563 + for (i = scm->fp->count - 1; i >= 0; i--)
  1564 + unix_inflight(scm->fp->fp[i]);
1549 1565 return max_level;
1550 1566 }
1551 1567  
... ... @@ -120,11 +120,11 @@
120 120 {
121 121 struct sock *s = unix_get_socket(fp);
122 122  
  123 + spin_lock(&unix_gc_lock);
  124 +
123 125 if (s) {
124 126 struct unix_sock *u = unix_sk(s);
125 127  
126   - spin_lock(&unix_gc_lock);
127   -
128 128 if (atomic_long_inc_return(&u->inflight) == 1) {
129 129 BUG_ON(!list_empty(&u->link));
130 130 list_add_tail(&u->link, &gc_inflight_list);
131 131  
132 132  
133 133  
134 134  
135 135  
... ... @@ -132,25 +132,28 @@
132 132 BUG_ON(list_empty(&u->link));
133 133 }
134 134 unix_tot_inflight++;
135   - spin_unlock(&unix_gc_lock);
136 135 }
  136 + fp->f_cred->user->unix_inflight++;
  137 + spin_unlock(&unix_gc_lock);
137 138 }
138 139  
139 140 void unix_notinflight(struct file *fp)
140 141 {
141 142 struct sock *s = unix_get_socket(fp);
142 143  
  144 + spin_lock(&unix_gc_lock);
  145 +
143 146 if (s) {
144 147 struct unix_sock *u = unix_sk(s);
145 148  
146   - spin_lock(&unix_gc_lock);
147 149 BUG_ON(list_empty(&u->link));
148 150  
149 151 if (atomic_long_dec_and_test(&u->inflight))
150 152 list_del_init(&u->link);
151 153 unix_tot_inflight--;
152   - spin_unlock(&unix_gc_lock);
153 154 }
  155 + fp->f_cred->user->unix_inflight--;
  156 + spin_unlock(&unix_gc_lock);
154 157 }
155 158  
156 159 static void scan_inflight(struct sock *x, void (*func)(struct unix_sock *),