Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753503Ab1BBBCF (ORCPT ); Tue, 1 Feb 2011 20:02:05 -0500 Received: from mga01.intel.com ([192.55.52.88]:7970 "EHLO mga01.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753256Ab1BBAoF (ORCPT ); Tue, 1 Feb 2011 19:44:05 -0500 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="4.60,412,1291622400"; d="scan'208";a="653505063" From: Andi Kleen References: <20110201443.618138584@firstfloor.org> In-Reply-To: <20110201443.618138584@firstfloor.org> To: eric.dumazet@gmail.com, davem@davemloft.net, gregkh@suse.de, ak@linux.intel.com, linux-kernel@vger.kernel.org, stable@kernel.org Subject: [PATCH] [59/139] af_unix: limit recursion level Message-Id: <20110202004415.902363E09BD@tassilo.jf.intel.com> Date: Tue, 1 Feb 2011 16:44:15 -0800 (PST) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 5853 Lines: 187 2.6.35-longterm review patch. If anyone has any objections, please let me know. ------------------ From: Eric Dumazet [ Upstream commit 25888e30319f8896fc656fc68643e6a078263060 ] 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. Reported-by: Марк Коренберг Signed-off-by: Eric Dumazet Signed-off-by: David S. Miller Signed-off-by: Greg Kroah-Hartman Signed-off-by: Andi Kleen --- include/net/af_unix.h | 2 ++ net/unix/af_unix.c | 37 ++++++++++++++++++++++++++++++++----- net/unix/garbage.c | 2 +- 3 files changed, 35 insertions(+), 6 deletions(-) Index: linux-2.6.35.y/include/net/af_unix.h =================================================================== --- linux-2.6.35.y.orig/include/net/af_unix.h +++ linux-2.6.35.y/include/net/af_unix.h @@ -10,6 +10,7 @@ extern void unix_inflight(struct file *f 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; struct socket_wq peer_wq; }; #define unix_sk(__sk) ((struct unix_sock *)__sk) Index: linux-2.6.35.y/net/unix/af_unix.c =================================================================== --- linux-2.6.35.y.orig/net/unix/af_unix.c +++ linux-2.6.35.y/net/unix/af_unix.c @@ -1327,9 +1327,25 @@ static void unix_destruct_scm(struct sk_ 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 @@ -1340,9 +1356,11 @@ static int unix_attach_fds(struct scm_co if (!UNIXCB(skb).fp) return -ENOMEM; - for (i = scm->fp->count-1; i >= 0; i--) - unix_inflight(scm->fp->fp[i]); - return 0; + if (unix_sock_count) { + for (i = scm->fp->count - 1; i >= 0; i--) + unix_inflight(scm->fp->fp[i]); + } + return max_level; } static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool send_fds) @@ -1377,6 +1395,7 @@ static int unix_dgram_sendmsg(struct kio struct sk_buff *skb; long timeo; struct scm_cookie tmp_scm; + int max_level; if (NULL == siocb->scm) siocb->scm = &tmp_scm; @@ -1415,8 +1434,9 @@ static int unix_dgram_sendmsg(struct kio goto out; err = unix_scm_to_skb(siocb->scm, skb, true); - if (err) + if (err < 0) goto out_free; + max_level = err + 1; unix_get_secdata(siocb->scm, skb); skb_reset_transport_header(skb); @@ -1496,6 +1516,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); @@ -1526,6 +1548,7 @@ static int unix_stream_sendmsg(struct ki int sent = 0; struct scm_cookie tmp_scm; bool fds_sent = false; + int max_level; if (NULL == siocb->scm) siocb->scm = &tmp_scm; @@ -1589,10 +1612,11 @@ static int unix_stream_sendmsg(struct ki /* Only send the fds in the first buffer */ err = unix_scm_to_skb(siocb->scm, skb, !fds_sent); - if (err) { + if (err < 0) { kfree_skb(skb); goto out_err; } + max_level = err + 1; fds_sent = true; err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size); @@ -1608,6 +1632,8 @@ static int unix_stream_sendmsg(struct ki 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; @@ -1824,6 +1850,7 @@ static int unix_stream_recvmsg(struct ki 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: linux-2.6.35.y/net/unix/garbage.c =================================================================== --- linux-2.6.35.y.orig/net/unix/garbage.c +++ linux-2.6.35.y/net/unix/garbage.c @@ -96,7 +96,7 @@ static DECLARE_WAIT_QUEUE_HEAD(unix_gc_w 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; -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/