]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
af_unix: limit recursion level
authorEric Dumazet <eric.dumazet@gmail.com>
Thu, 25 Nov 2010 04:11:39 +0000 (04:11 +0000)
committerPaul Gortmaker <paul.gortmaker@windriver.com>
Sun, 17 Apr 2011 20:16:03 +0000 (16:16 -0400)
commit 25888e30319f8896fc656fc68643e6a078263060 upstream

Its easy to eat all kernel memory and trigger NMI watchdog, using an
exploit program that queues unix sockets on top of others.

lkml ref : http://lkml.org/lkml/2010/11/25/8

This mechanism is used in applications, one choice we have is to have a
recursion limit.

Other limits might be needed as well (if we queue other types of files),
since the passfd mechanism is currently limited by socket receive queue
sizes only.

Add a recursion_level to unix socket, allowing up to 4 levels.

Each time we send an unix socket through sendfd mechanism, we copy its
recursion level (plus one) to receiver. This recursion level is cleared
when socket receive queue is emptied.

[PG: slight modifications required due to absense of 7361c36c5 in 34]

Reported-by: Марк Коренберг <socketpair@gmail.com>
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com>
include/net/af_unix.h
net/unix/af_unix.c
net/unix/garbage.c

index 1614d78c60ed2c4b616b530b89f8247addbcc17f..861045fbf936854889f07cad7f8c703a771bb02f 100644 (file)
@@ -10,6 +10,7 @@ extern void unix_inflight(struct file *fp);
 extern void unix_notinflight(struct file *fp);
 extern void unix_gc(void);
 extern void wait_for_unix_gc(void);
+extern struct sock *unix_get_socket(struct file *filp);
 
 #define UNIX_HASH_SIZE 256
 
@@ -56,6 +57,7 @@ struct unix_sock {
         spinlock_t             lock;
        unsigned int            gc_candidate : 1;
        unsigned int            gc_maybe_cycle : 1;
+       unsigned char           recursion_level;
         wait_queue_head_t       peer_wait;
 };
 #define unix_sk(__sk) ((struct unix_sock *)__sk)
index 8c34e3bf877916f5c93733c501faceb89dded657..207a119b5dcd785a4ced77ebfd8b2f26689fe76d 100644 (file)
@@ -1324,9 +1324,25 @@ static void unix_destruct_fds(struct sk_buff *skb)
        sock_wfree(skb);
 }
 
+#define MAX_RECURSION_LEVEL 4
+
 static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)
 {
        int i;
+       unsigned char max_level = 0;
+       int unix_sock_count = 0;
+
+       for (i = scm->fp->count - 1; i >= 0; i--) {
+               struct sock *sk = unix_get_socket(scm->fp->fp[i]);
+
+               if (sk) {
+                       unix_sock_count++;
+                       max_level = max(max_level,
+                                       unix_sk(sk)->recursion_level);
+               }
+       }
+       if (unlikely(max_level > MAX_RECURSION_LEVEL))
+               return -ETOOMANYREFS;
 
        /*
         * Need to duplicate file references for the sake of garbage
@@ -1337,10 +1353,12 @@ static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)
        if (!UNIXCB(skb).fp)
                return -ENOMEM;
 
-       for (i = scm->fp->count-1; i >= 0; i--)
-               unix_inflight(scm->fp->fp[i]);
+       if (unix_sock_count) {
+               for (i = scm->fp->count-1; i >= 0; i--)
+                       unix_inflight(scm->fp->fp[i]);
+       }
        skb->destructor = unix_destruct_fds;
-       return 0;
+       return max_level;
 }
 
 /*
@@ -1362,6 +1380,7 @@ static int unix_dgram_sendmsg(struct kiocb *kiocb, struct socket *sock,
        struct sk_buff *skb;
        long timeo;
        struct scm_cookie tmp_scm;
+       int max_level = 0;
 
        if (NULL == siocb->scm)
                siocb->scm = &tmp_scm;
@@ -1402,8 +1421,9 @@ static int unix_dgram_sendmsg(struct kiocb *kiocb, struct socket *sock,
        memcpy(UNIXCREDS(skb), &siocb->scm->creds, sizeof(struct ucred));
        if (siocb->scm->fp) {
                err = unix_attach_fds(siocb->scm, skb);
-               if (err)
+               if (err < 0)
                        goto out_free;
+               max_level = err + 1;
        }
        unix_get_secdata(siocb->scm, skb);
 
@@ -1484,6 +1504,8 @@ restart:
        }
 
        skb_queue_tail(&other->sk_receive_queue, skb);
+       if (max_level > unix_sk(other)->recursion_level)
+               unix_sk(other)->recursion_level = max_level;
        unix_state_unlock(other);
        other->sk_data_ready(other, len);
        sock_put(other);
@@ -1514,6 +1536,7 @@ static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock,
        int sent = 0;
        struct scm_cookie tmp_scm;
        bool fds_sent = false;
+       int max_level = 0;
 
        if (NULL == siocb->scm)
                siocb->scm = &tmp_scm;
@@ -1578,10 +1601,11 @@ static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock,
                /* Only send the fds in the first buffer */
                if (siocb->scm->fp && !fds_sent) {
                        err = unix_attach_fds(siocb->scm, skb);
-                       if (err) {
+                       if (err < 0) {
                                kfree_skb(skb);
                                goto out_err;
                        }
+                       max_level = err + 1;
                        fds_sent = true;
                }
 
@@ -1598,6 +1622,8 @@ static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock,
                        goto pipe_err_free;
 
                skb_queue_tail(&other->sk_receive_queue, skb);
+               if (max_level > unix_sk(other)->recursion_level)
+                       unix_sk(other)->recursion_level = max_level;
                unix_state_unlock(other);
                other->sk_data_ready(other, size);
                sent += size;
@@ -1814,6 +1840,7 @@ static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,
                unix_state_lock(sk);
                skb = skb_dequeue(&sk->sk_receive_queue);
                if (skb == NULL) {
+                       unix_sk(sk)->recursion_level = 0;
                        if (copied >= target)
                                goto unlock;
 
index ef5aa55c6b915f56da4fe2377b91a8ea574d68ab..493e0e67685df9e05ae7c059678197de940a2706 100644 (file)
@@ -96,7 +96,7 @@ static DECLARE_WAIT_QUEUE_HEAD(unix_gc_wait);
 unsigned int unix_tot_inflight;
 
 
-static struct sock *unix_get_socket(struct file *filp)
+struct sock *unix_get_socket(struct file *filp)
 {
        struct sock *u_sock = NULL;
        struct inode *inode = filp->f_path.dentry->d_inode;