[kernel] r12384 - in dists/etch-security/linux-2.6/debian: . patches/bugfix patches/series

Dann Frazier dannf at alioth.debian.org
Fri Nov 14 23:56:11 UTC 2008


Author: dannf
Date: Fri Nov 14 23:56:09 2008
New Revision: 12384

Log:
* Fix recursive descent in __scm_destroy
   - bugfix/af_unix-fix-garbage-collector-races.patch
   - bugfix/af_unix-convert-socks-to-unix_socks.patch
   - bugfix/net-unix-fix-inflight-counting-bug-in-garbage-collector.patch
   - bugfix/net-fix-recursive-descent-in-__scm_destroy.patch
  See CVE-2008-5029
  ** CURRENTLY AN UNHANDLED ABI BREAKER **

Added:
   dists/etch-security/linux-2.6/debian/patches/bugfix/af_unix-convert-socks-to-unix_socks.patch
   dists/etch-security/linux-2.6/debian/patches/bugfix/af_unix-fix-garbage-collector-races.patch
   dists/etch-security/linux-2.6/debian/patches/bugfix/net-fix-recursive-descent-in-__scm_destroy.patch
   dists/etch-security/linux-2.6/debian/patches/bugfix/net-unix-fix-inflight-counting-bug-in-garbage-collector.patch
Modified:
   dists/etch-security/linux-2.6/debian/changelog
   dists/etch-security/linux-2.6/debian/patches/series/23etch1

Modified: dists/etch-security/linux-2.6/debian/changelog
==============================================================================
--- dists/etch-security/linux-2.6/debian/changelog	(original)
+++ dists/etch-security/linux-2.6/debian/changelog	Fri Nov 14 23:56:09 2008
@@ -30,8 +30,15 @@
   * Fix stack corruption in hfs
      - bugfix/hfs-fix-namelength-memory-corruption.patch
     See CVE-2008-5025
+  * Fix recursive descent in __scm_destroy
+     - bugfix/af_unix-fix-garbage-collector-races.patch
+     - bugfix/af_unix-convert-socks-to-unix_socks.patch
+     - bugfix/net-unix-fix-inflight-counting-bug-in-garbage-collector.patch
+     - bugfix/net-fix-recursive-descent-in-__scm_destroy.patch
+    See CVE-2008-5029
+    ** CURRENTLY AN UNHANDLED ABI BREAKER **
 
- -- dann frazier <dannf at debian.org>  Thu, 13 Nov 2008 11:20:11 -0700
+ -- dann frazier <dannf at debian.org>  Fri, 14 Nov 2008 16:15:22 -0700
 
 linux-2.6 (2.6.18.dfsg.1-23) stable; urgency=high
 

Added: dists/etch-security/linux-2.6/debian/patches/bugfix/af_unix-convert-socks-to-unix_socks.patch
==============================================================================
--- (empty file)
+++ dists/etch-security/linux-2.6/debian/patches/bugfix/af_unix-convert-socks-to-unix_socks.patch	Fri Nov 14 23:56:09 2008
@@ -0,0 +1,81 @@
+From: Pavel Emelyanov <xemul at openvz.org>
+Date: Sun, 11 Nov 2007 06:07:13 +0000 (-0800)
+Subject: [AF_UNIX]: Convert socks to unix_socks in scan_inflight, not in callbacks
+X-Git-Tag: v2.6.24-rc3~155^2~3
+X-Git-Url: http://git.kernel.org/?p=linux%2Fkernel%2Fgit%2Ftorvalds%2Flinux-2.6.git;a=commitdiff_plain;h=5c80f1ae9842a8b7985acd0f02efb9828effb05f
+
+[AF_UNIX]: Convert socks to unix_socks in scan_inflight, not in callbacks
+
+The scan_inflight() routine scans through the unix sockets and calls
+some passed callback. The fact is that all these callbacks work with
+the unix_sock objects, not the sock ones, so make this conversion in
+the scan_inflight() before calling the callbacks.
+
+This removes one unneeded variable from the inc_inflight_move_tail().
+
+Signed-off-by: Pavel Emelyanov <xemul at openvz.org>
+Signed-off-by: David S. Miller <davem at davemloft.net>
+---
+
+Backported to Debian's 2.6.18 by dann frazier <dannf at hp.com>
+
+diff --git a/net/unix/garbage.c b/net/unix/garbage.c
+index 399717e..ebdff3d 100644
+--- a/net/unix/garbage.c
++++ b/net/unix/garbage.c
+@@ -161,7 +161,7 @@ static inline struct sk_buff *sock_queue_head(struct sock *sk)
+ 	for (skb = sock_queue_head(sk)->next, next = skb->next; \
+ 	     skb != sock_queue_head(sk); skb = next, next = skb->next)
+ 
+-static void scan_inflight(struct sock *x, void (*func)(struct sock *),
++static void scan_inflight(struct sock *x, void (*func)(struct unix_sock *),
+ 			  struct sk_buff_head *hitlist)
+ {
+ 	struct sk_buff *skb;
+@@ -185,9 +185,9 @@ static void scan_inflight(struct sock *x, void (*func)(struct sock *),
+ 				 *	if it indeed does so
+ 				 */
+ 				struct sock *sk = unix_get_socket(*fp++);
+-				if(sk) {
++				if (sk) {
+ 					hit = 1;
+-					func(sk);
++					func(unix_sk(sk));
+ 				}
+ 			}
+ 			if (hit && hitlist != NULL) {
+@@ -199,7 +199,7 @@ static void scan_inflight(struct sock *x, void (*func)(struct sock *),
+ 	spin_unlock(&x->sk_receive_queue.lock);
+ }
+ 
+-static void scan_children(struct sock *x, void (*func)(struct sock *),
++static void scan_children(struct sock *x, void (*func)(struct unix_sock *),
+ 			  struct sk_buff_head *hitlist)
+ {
+ 	if (x->sk_state != TCP_LISTEN)
+@@ -235,20 +235,18 @@ static void scan_children(struct sock *x, void (*func)(struct sock *),
+ 	}
+ }
+ 
+-static void dec_inflight(struct sock *sk)
++static void dec_inflight(struct unix_sock *usk)
+ {
+-	atomic_dec(&unix_sk(sk)->inflight);
++	atomic_dec(&usk->inflight);
+ }
+ 
+-static void inc_inflight(struct sock *sk)
++static void inc_inflight(struct unix_sock *usk)
+ {
+-	atomic_inc(&unix_sk(sk)->inflight);
++	atomic_inc(&usk->inflight);
+ }
+ 
+-static void inc_inflight_move_tail(struct sock *sk)
++static void inc_inflight_move_tail(struct unix_sock *u)
+ {
+-	struct unix_sock *u = unix_sk(sk);
+-
+ 	atomic_inc(&u->inflight);
+ 	/*
+ 	 * If this is still a candidate, move it to the end of the

Added: dists/etch-security/linux-2.6/debian/patches/bugfix/af_unix-fix-garbage-collector-races.patch
==============================================================================
--- (empty file)
+++ dists/etch-security/linux-2.6/debian/patches/bugfix/af_unix-fix-garbage-collector-races.patch	Fri Nov 14 23:56:09 2008
@@ -0,0 +1,471 @@
+From: Miklos Szeredi <mszeredi at suse.cz>
+Date: Wed, 11 Jul 2007 21:22:39 +0000 (-0700)
+Subject: [AF_UNIX]: Rewrite garbage collector, fixes race.
+X-Git-Tag: v2.6.23-rc1~1109^2~13
+X-Git-Url: http://git.kernel.org/?p=linux%2Fkernel%2Fgit%2Ftorvalds%2Flinux-2.6.git;a=commitdiff_plain;h=1fd05ba5a2f2aa8e7b9b52ef55df850e2e7d54c9
+
+[AF_UNIX]: Rewrite garbage collector, fixes race.
+
+Throw out the old mark & sweep garbage collector and put in a
+refcounting cycle detecting one.
+
+The old one had a race with recvmsg, that resulted in false positives
+and hence data loss.  The old algorithm operated on all unix sockets
+in the system, so any additional locking would have meant performance
+problems for all users of these.
+
+The new algorithm instead only operates on "in flight" sockets, which
+are very rare, and the additional locking for these doesn't negatively
+impact the vast majority of users.
+
+In fact it's probable, that there weren't *any* heavy senders of
+sockets over sockets, otherwise the above race would have been
+discovered long ago.
+
+The patch works OK with the app that exposed the race with the old
+code.  The garbage collection has also been verified to work in a few
+simple cases.
+
+Signed-off-by: Miklos Szeredi <mszeredi at suse.cz>
+Signed-off-by: David S. Miller <davem at davemloft.net>
+---
+
+Backported to Debian's 2.6.18 by dann frazier <dannf at hp.com>
+
+diff -urpN linux-source-2.6.18.orig/include/net/af_unix.h linux-source-2.6.18/include/net/af_unix.h
+--- linux-source-2.6.18.orig/include/net/af_unix.h	2006-09-19 21:42:06.000000000 -0600
++++ linux-source-2.6.18/include/net/af_unix.h	2008-11-13 17:04:33.000000000 -0700
+@@ -81,9 +81,10 @@ struct unix_sock {
+ 	struct mutex		readlock;
+         struct sock		*peer;
+         struct sock		*other;
+-        struct sock		*gc_tree;
++	struct list_head	link;
+         atomic_t                inflight;
+         spinlock_t		lock;
++	unsigned int		gc_candidate : 1;
+         wait_queue_head_t       peer_wait;
+ };
+ #define unix_sk(__sk) ((struct unix_sock *)__sk)
+diff -urpN linux-source-2.6.18.orig/net/unix/af_unix.c linux-source-2.6.18/net/unix/af_unix.c
+--- linux-source-2.6.18.orig/net/unix/af_unix.c	2006-09-19 21:42:06.000000000 -0600
++++ linux-source-2.6.18/net/unix/af_unix.c	2008-11-13 17:04:33.000000000 -0700
+@@ -592,7 +592,8 @@ static struct sock * unix_create1(struct
+ 	u->dentry = NULL;
+ 	u->mnt	  = NULL;
+ 	spin_lock_init(&u->lock);
+-	atomic_set(&u->inflight, sock ? 0 : -1);
++	atomic_set(&u->inflight, 0);
++	INIT_LIST_HEAD(&u->link);
+ 	mutex_init(&u->readlock); /* single task reading lock */
+ 	init_waitqueue_head(&u->peer_wait);
+ 	unix_insert_socket(unix_sockets_unbound, sk);
+@@ -1101,9 +1102,6 @@ restart:
+ 	/* take ten and and send info to listening sock */
+ 	spin_lock(&other->sk_receive_queue.lock);
+ 	__skb_queue_tail(&other->sk_receive_queue, skb);
+-	/* Undo artificially decreased inflight after embrion
+-	 * is installed to listening socket. */
+-	atomic_inc(&newu->inflight);
+ 	spin_unlock(&other->sk_receive_queue.lock);
+ 	unix_state_runlock(other);
+ 	other->sk_data_ready(other, 0);
+diff -urpN linux-source-2.6.18.orig/net/unix/garbage.c linux-source-2.6.18/net/unix/garbage.c
+--- linux-source-2.6.18.orig/net/unix/garbage.c	2006-09-19 21:42:06.000000000 -0600
++++ linux-source-2.6.18/net/unix/garbage.c	2008-11-13 17:15:31.000000000 -0700
+@@ -62,6 +62,10 @@
+  *	AV		1 Mar 1999
+  *		Damn. Added missing check for ->dead in listen queues scanning.
+  *
++ *	Miklos Szeredi 25 Jun 2007
++ *		Reimplement with a cycle collecting algorithm. This should
++ *		solve several problems with the previous code, like being racy
++ *		wrt receive and holding up unrelated socket operations.
+  */
+  
+ #include <linux/kernel.h>
+@@ -85,10 +89,9 @@
+ 
+ /* Internal data structures and random procedures: */
+ 
+-#define GC_HEAD		((struct sock *)(-1))
+-#define GC_ORPHAN	((struct sock *)(-3))
+-
+-static struct sock *gc_current = GC_HEAD; /* stack of objects to mark */
++static LIST_HEAD(gc_inflight_list);
++static LIST_HEAD(gc_candidates);
++static DEFINE_SPINLOCK(unix_gc_lock);
+ 
+ atomic_t unix_tot_inflight = ATOMIC_INIT(0);
+ 
+@@ -123,8 +126,16 @@ void unix_inflight(struct file *fp)
+ {
+ 	struct sock *s = unix_get_socket(fp);
+ 	if(s) {
+-		atomic_inc(&unix_sk(s)->inflight);
++		struct unix_sock *u = unix_sk(s);
++		spin_lock(&unix_gc_lock);
++		if (atomic_inc_return(&u->inflight) == 1) {
++			BUG_ON(!list_empty(&u->link));
++			list_add_tail(&u->link, &gc_inflight_list);
++		} else {
++			BUG_ON(list_empty(&u->link));
++		}
+ 		atomic_inc(&unix_tot_inflight);
++		spin_unlock(&unix_gc_lock);
+ 	}
+ }
+ 
+@@ -132,182 +143,218 @@ void unix_notinflight(struct file *fp)
+ {
+ 	struct sock *s = unix_get_socket(fp);
+ 	if(s) {
+-		atomic_dec(&unix_sk(s)->inflight);
++		struct unix_sock *u = unix_sk(s);
++		spin_lock(&unix_gc_lock);
++		BUG_ON(list_empty(&u->link));
++		if (atomic_dec_and_test(&u->inflight))
++			list_del_init(&u->link);
+ 		atomic_dec(&unix_tot_inflight);
++		spin_unlock(&unix_gc_lock);
+ 	}
+ }
+ 
++static inline struct sk_buff *sock_queue_head(struct sock *sk)
++{
++	return (struct sk_buff *) &sk->sk_receive_queue;
++}
+ 
+-/*
+- *	Garbage Collector Support Functions
+- */
++#define receive_queue_for_each_skb(sk, next, skb) \
++	for (skb = sock_queue_head(sk)->next, next = skb->next; \
++	     skb != sock_queue_head(sk); skb = next, next = skb->next)
+ 
+-static inline struct sock *pop_stack(void)
++static void scan_inflight(struct sock *x, void (*func)(struct sock *),
++			  struct sk_buff_head *hitlist)
+ {
+-	struct sock *p = gc_current;
+-	gc_current = unix_sk(p)->gc_tree;
+-	return p;
++	struct sk_buff *skb;
++	struct sk_buff *next;
++
++	spin_lock(&x->sk_receive_queue.lock);
++	receive_queue_for_each_skb(x, next, skb) {
++		/*
++		 *	Do we have file descriptors ?
++		 */
++		if (UNIXCB(skb).fp) {
++			int hit = 0;
++			/*
++			 *	Process the descriptors of this socket
++			 */
++			int nfd = UNIXCB(skb).fp->count;
++			struct file **fp = UNIXCB(skb).fp->fp;
++			while (nfd--) {
++				/*
++				 *	Get the socket the fd matches
++				 *	if it indeed does so
++				 */
++				struct sock *sk = unix_get_socket(*fp++);
++				if(sk) {
++					hit = 1;
++					func(sk);
++				}
++			}
++			if (hit && hitlist != NULL) {
++				__skb_unlink(skb, &x->sk_receive_queue);
++				__skb_queue_tail(hitlist, skb);
++			}
++		}
++	}
++	spin_unlock(&x->sk_receive_queue.lock);
+ }
+ 
+-static inline int empty_stack(void)
++static void scan_children(struct sock *x, void (*func)(struct sock *),
++			  struct sk_buff_head *hitlist)
+ {
+-	return gc_current == GC_HEAD;
++	if (x->sk_state != TCP_LISTEN)
++		scan_inflight(x, func, hitlist);
++	else {
++		struct sk_buff *skb;
++		struct sk_buff *next;
++		struct unix_sock *u;
++		LIST_HEAD(embryos);
++
++		/*
++		 * For a listening socket collect the queued embryos
++		 * and perform a scan on them as well.
++		 */
++		spin_lock(&x->sk_receive_queue.lock);
++		receive_queue_for_each_skb(x, next, skb) {
++			u = unix_sk(skb->sk);
++
++			/*
++			 * An embryo cannot be in-flight, so it's safe
++			 * to use the list link.
++			 */
++			BUG_ON(!list_empty(&u->link));
++			list_add_tail(&u->link, &embryos);
++		}
++		spin_unlock(&x->sk_receive_queue.lock);
++
++		while (!list_empty(&embryos)) {
++			u = list_entry(embryos.next, struct unix_sock, link);
++			scan_inflight(&u->sk, func, hitlist);
++			list_del_init(&u->link);
++		}
++	}
+ }
+ 
+-static void maybe_unmark_and_push(struct sock *x)
++static void dec_inflight(struct sock *sk)
+ {
+-	struct unix_sock *u = unix_sk(x);
++	atomic_dec(&unix_sk(sk)->inflight);
++}
+ 
+-	if (u->gc_tree != GC_ORPHAN)
+-		return;
+-	sock_hold(x);
+-	u->gc_tree = gc_current;
+-	gc_current = x;
++static void inc_inflight(struct sock *sk)
++{
++	atomic_inc(&unix_sk(sk)->inflight);
+ }
+ 
++static void inc_inflight_move_tail(struct sock *sk)
++{
++	struct unix_sock *u = unix_sk(sk);
++
++	atomic_inc(&u->inflight);
++	/*
++	 * If this is still a candidate, move it to the end of the
++	 * list, so that it's checked even if it was already passed
++	 * over
++	 */
++	if (u->gc_candidate)
++		list_move_tail(&u->link, &gc_candidates);
++}
+ 
+ /* The external entry point: unix_gc() */
+ 
+ void unix_gc(void)
+ {
+-	static DEFINE_MUTEX(unix_gc_sem);
+-	int i;
+-	struct sock *s;
+-	struct sk_buff_head hitlist;
+-	struct sk_buff *skb;
++	static int gc_in_progress = 0;
+ 
+-	/*
+-	 *	Avoid a recursive GC.
+-	 */
++	struct unix_sock *u;
++	struct unix_sock *next;
++	struct sk_buff_head hitlist;
++	struct list_head cursor;
+ 
+-	if (!mutex_trylock(&unix_gc_sem))
+-		return;
++	spin_lock(&unix_gc_lock);
+ 
+-	spin_lock(&unix_table_lock);
++	/* Avoid a recursive GC. */
++	if (gc_in_progress)
++		goto out;
+ 
+-	forall_unix_sockets(i, s)
+-	{
+-		unix_sk(s)->gc_tree = GC_ORPHAN;
+-	}
++	gc_in_progress = 1;
+ 	/*
+-	 *	Everything is now marked 
+-	 */
+-
+-	/* Invariant to be maintained:
+-		- everything unmarked is either:
+-		-- (a) on the stack, or
+-		-- (b) has all of its children unmarked
+-		- everything on the stack is always unmarked
+-		- nothing is ever pushed onto the stack twice, because:
+-		-- nothing previously unmarked is ever pushed on the stack
++	 * First, select candidates for garbage collection.  Only
++	 * in-flight sockets are considered, and from those only ones
++	 * which don't have any external reference.
++	 *
++	 * Holding unix_gc_lock will protect these candidates from
++	 * being detached, and hence from gaining an external
++	 * reference.  This also means, that since there are no
++	 * possible receivers, the receive queues of these sockets are
++	 * static during the GC, even though the dequeue is done
++	 * before the detach without atomicity guarantees.
+ 	 */
++	list_for_each_entry_safe(u, next, &gc_inflight_list, link) {
++		int total_refs;
++		int inflight_refs;
++
++		total_refs = file_count(u->sk.sk_socket->file);
++		inflight_refs = atomic_read(&u->inflight);
++
++		BUG_ON(inflight_refs < 1);
++		BUG_ON(total_refs < inflight_refs);
++		if (total_refs == inflight_refs) {
++			list_move_tail(&u->link, &gc_candidates);
++			u->gc_candidate = 1;
++		}
++	}
+ 
+ 	/*
+-	 *	Push root set
++	 * Now remove all internal in-flight reference to children of
++	 * the candidates.
+ 	 */
+-
+-	forall_unix_sockets(i, s)
+-	{
+-		int open_count = 0;
+-
+-		/*
+-		 *	If all instances of the descriptor are not
+-		 *	in flight we are in use.
+-		 *
+-		 *	Special case: when socket s is embrion, it may be
+-		 *	hashed but still not in queue of listening socket.
+-		 *	In this case (see unix_create1()) we set artificial
+-		 *	negative inflight counter to close race window.
+-		 *	It is trick of course and dirty one.
+-		 */
+-		if (s->sk_socket && s->sk_socket->file)
+-			open_count = file_count(s->sk_socket->file);
+-		if (open_count > atomic_read(&unix_sk(s)->inflight))
+-			maybe_unmark_and_push(s);
+-	}
++	list_for_each_entry(u, &gc_candidates, link)
++		scan_children(&u->sk, dec_inflight, NULL);
+ 
+ 	/*
+-	 *	Mark phase 
++	 * Restore the references for children of all candidates,
++	 * which have remaining references.  Do this recursively, so
++	 * only those remain, which form cyclic references.
++	 *
++	 * Use a "cursor" link, to make the list traversal safe, even
++	 * though elements might be moved about.
+ 	 */
++	list_add(&cursor, &gc_candidates);
++	while (cursor.next != &gc_candidates) {
++		u = list_entry(cursor.next, struct unix_sock, link);
+ 
+-	while (!empty_stack())
+-	{
+-		struct sock *x = pop_stack();
+-		struct sock *sk;
+-
+-		spin_lock(&x->sk_receive_queue.lock);
+-		skb = skb_peek(&x->sk_receive_queue);
+-		
+-		/*
+-		 *	Loop through all but first born 
+-		 */
++		/* Move cursor to after the current position. */
++		list_move(&cursor, &u->link);
+ 		
+-		while (skb && skb != (struct sk_buff *)&x->sk_receive_queue) {
+-			/*
+-			 *	Do we have file descriptors ?
+-			 */
+-			if(UNIXCB(skb).fp)
+-			{
+-				/*
+-				 *	Process the descriptors of this socket
+-				 */
+-				int nfd=UNIXCB(skb).fp->count;
+-				struct file **fp = UNIXCB(skb).fp->fp;
+-				while(nfd--)
+-				{
+-					/*
+-					 *	Get the socket the fd matches if
+-					 *	it indeed does so
+-					 */
+-					if((sk=unix_get_socket(*fp++))!=NULL)
+-					{
+-						maybe_unmark_and_push(sk);
+-					}
+-				}
+-			}
+-			/* We have to scan not-yet-accepted ones too */
+-			if (x->sk_state == TCP_LISTEN)
+-				maybe_unmark_and_push(skb->sk);
+-			skb=skb->next;
++		if (atomic_read(&u->inflight) > 0) {
++			list_move_tail(&u->link, &gc_inflight_list);
++			u->gc_candidate = 0;
++			scan_children(&u->sk, inc_inflight_move_tail, NULL);
+ 		}
+-		spin_unlock(&x->sk_receive_queue.lock);
+-		sock_put(x);
+ 	}
++	list_del(&cursor);
+ 
++	/*
++	 * Now gc_candidates contains only garbage.  Restore original
++	 * inflight counters for these as well, and remove the skbuffs
++	 * which are creating the cycle(s).
++	 */
+ 	skb_queue_head_init(&hitlist);
++	list_for_each_entry(u, &gc_candidates, link)
++		scan_children(&u->sk, inc_inflight, &hitlist);
+ 
+-	forall_unix_sockets(i, s)
+-	{
+-		struct unix_sock *u = unix_sk(s);
++	spin_unlock(&unix_gc_lock);
+ 
+-		if (u->gc_tree == GC_ORPHAN) {
+-			struct sk_buff *nextsk;
++	/* Here we are. Hitlist is filled. Die. */
++	__skb_queue_purge(&hitlist);
+ 
+-			spin_lock(&s->sk_receive_queue.lock);
+-			skb = skb_peek(&s->sk_receive_queue);
+-			while (skb &&
+-			       skb != (struct sk_buff *)&s->sk_receive_queue) {
+-				nextsk = skb->next;
+-				/*
+-				 *	Do we have file descriptors ?
+-				 */
+-				if (UNIXCB(skb).fp) {
+-					__skb_unlink(skb,
+-						     &s->sk_receive_queue);
+-					__skb_queue_tail(&hitlist, skb);
+-				}
+-				skb = nextsk;
+-			}
+-			spin_unlock(&s->sk_receive_queue.lock);
+-		}
+-		u->gc_tree = GC_ORPHAN;
+-	}
+-	spin_unlock(&unix_table_lock);
++	spin_lock(&unix_gc_lock);
+ 
+-	/*
+-	 *	Here we are. Hitlist is filled. Die.
+-	 */
++	/* All candidates should have been detached by now. */
++	BUG_ON(!list_empty(&gc_candidates));
++	gc_in_progress = 0;
+ 
+-	__skb_queue_purge(&hitlist);
+-	mutex_unlock(&unix_gc_sem);
++ out:
++	spin_unlock(&unix_gc_lock);
+ }

Added: dists/etch-security/linux-2.6/debian/patches/bugfix/net-fix-recursive-descent-in-__scm_destroy.patch
==============================================================================
--- (empty file)
+++ dists/etch-security/linux-2.6/debian/patches/bugfix/net-fix-recursive-descent-in-__scm_destroy.patch	Fri Nov 14 23:56:09 2008
@@ -0,0 +1,100 @@
+From: David S. Miller <davem at davemloft.net>
+Date: Thu, 6 Nov 2008 23:45:32 +0000 (-0800)
+Subject: net: Fix recursive descent in __scm_destroy().
+X-Git-Tag: v2.6.28-rc4~22^2~1
+X-Git-Url: http://git.kernel.org/?p=linux%2Fkernel%2Fgit%2Ftorvalds%2Flinux-2.6.git;a=commitdiff_plain;h=3b53fbf
+
+net: Fix recursive descent in __scm_destroy().
+
+__scm_destroy() walks the list of file descriptors in the scm_fp_list
+pointed to by the scm_cookie argument.
+
+Those, in turn, can close sockets and invoke __scm_destroy() again.
+
+There is nothing which limits how deeply this can occur.
+
+The idea for how to fix this is from Linus.  Basically, we do all of
+the fput()s at the top level by collecting all of the scm_fp_list
+objects hit by an fput().  Inside of the initial __scm_destroy() we
+keep running the list until it is empty.
+
+Signed-off-by: David S. Miller <davem at davemloft.net>
+---
+
+Backported to Debian's 2.6.18 by dann frazier <dannf at hp.com>
+
+diff -urpN linux-source-2.6.18.orig/include/linux/sched.h linux-source-2.6.18/include/linux/sched.h
+--- linux-source-2.6.18.orig/include/linux/sched.h	2006-09-19 21:42:06.000000000 -0600
++++ linux-source-2.6.18/include/linux/sched.h	2008-11-13 18:15:49.000000000 -0700
+@@ -996,6 +996,8 @@ struct task_struct {
+ #ifdef	CONFIG_TASK_DELAY_ACCT
+ 	struct task_delay_info *delays;
+ #endif
++
++	struct list_head	*scm_work_list;
+ };
+ 
+ static inline pid_t process_group(struct task_struct *tsk)
+diff -urpN linux-source-2.6.18.orig/include/net/scm.h linux-source-2.6.18/include/net/scm.h
+--- linux-source-2.6.18.orig/include/net/scm.h	2006-09-19 21:42:06.000000000 -0600
++++ linux-source-2.6.18/include/net/scm.h	2008-11-13 18:12:27.000000000 -0700
+@@ -12,8 +12,9 @@
+ 
+ struct scm_fp_list
+ {
+-	int		count;
+-	struct file	*fp[SCM_MAX_FD];
++	struct list_head	list;
++	int			count;
++	struct file		*fp[SCM_MAX_FD];
+ };
+ 
+ struct scm_cookie
+diff -urpN linux-source-2.6.18.orig/net/core/scm.c linux-source-2.6.18/net/core/scm.c
+--- linux-source-2.6.18.orig/net/core/scm.c	2006-09-19 21:42:06.000000000 -0600
++++ linux-source-2.6.18/net/core/scm.c	2008-11-13 18:12:27.000000000 -0700
+@@ -73,6 +73,7 @@ static int scm_fp_copy(struct cmsghdr *c
+ 		if (!fpl)
+ 			return -ENOMEM;
+ 		*fplp = fpl;
++		INIT_LIST_HEAD(&fpl->list);
+ 		fpl->count = 0;
+ 	}
+ 	fpp = &fpl->fp[fpl->count];
+@@ -104,9 +105,25 @@ void __scm_destroy(struct scm_cookie *sc
+ 
+ 	if (fpl) {
+ 		scm->fp = NULL;
+-		for (i=fpl->count-1; i>=0; i--)
+-			fput(fpl->fp[i]);
+-		kfree(fpl);
++		if (current->scm_work_list) {
++			list_add_tail(&fpl->list, current->scm_work_list);
++		} else {
++			LIST_HEAD(work_list);
++
++			current->scm_work_list = &work_list;
++
++			list_add(&fpl->list, &work_list);
++			while (!list_empty(&work_list)) {
++				fpl = list_entry((&work_list)->next, struct scm_fp_list, list);
++
++				list_del(&fpl->list);
++				for (i=fpl->count-1; i>=0; i--)
++					fput(fpl->fp[i]);
++				kfree(fpl);
++			}
++
++			current->scm_work_list = NULL;
++		}
+ 	}
+ }
+ 
+@@ -277,6 +294,7 @@ struct scm_fp_list *scm_fp_dup(struct sc
+ 
+ 	new_fpl = kmalloc(sizeof(*fpl), GFP_KERNEL);
+ 	if (new_fpl) {
++		INIT_LIST_HEAD(&new_fpl->list);
+ 		for (i=fpl->count-1; i>=0; i--)
+ 			get_file(fpl->fp[i]);
+ 		memcpy(new_fpl, fpl, sizeof(*fpl));

Added: dists/etch-security/linux-2.6/debian/patches/bugfix/net-unix-fix-inflight-counting-bug-in-garbage-collector.patch
==============================================================================
--- (empty file)
+++ dists/etch-security/linux-2.6/debian/patches/bugfix/net-unix-fix-inflight-counting-bug-in-garbage-collector.patch	Fri Nov 14 23:56:09 2008
@@ -0,0 +1,204 @@
+From: Miklos Szeredi <mszeredi at suse.cz>
+Date: Sun, 9 Nov 2008 14:23:57 +0000 (+0100)
+Subject: net: unix: fix inflight counting bug in garbage collector
+X-Git-Tag: v2.6.28-rc4~6
+X-Git-Url: http://git.kernel.org/?p=linux%2Fkernel%2Fgit%2Ftorvalds%2Flinux-2.6.git;a=commitdiff_plain;h=6209344
+
+net: unix: fix inflight counting bug in garbage collector
+
+Previously I assumed that the receive queues of candidates don't
+change during the GC.  This is only half true, nothing can be received
+from the queues (see comment in unix_gc()), but buffers could be added
+through the other half of the socket pair, which may still have file
+descriptors referring to it.
+
+This can result in inc_inflight_move_tail() erronously increasing the
+"inflight" counter for a unix socket for which dec_inflight() wasn't
+previously called.  This in turn can trigger the "BUG_ON(total_refs <
+inflight_refs)" in a later garbage collection run.
+
+Fix this by only manipulating the "inflight" counter for sockets which
+are candidates themselves.  Duplicating the file references in
+unix_attach_fds() is also needed to prevent a socket becoming a
+candidate for GC while the skb that contains it is not yet queued.
+
+Reported-by: Andrea Bittau <a.bittau at cs.ucl.ac.uk>
+Signed-off-by: Miklos Szeredi <mszeredi at suse.cz>
+CC: stable at kernel.org
+Signed-off-by: Linus Torvalds <torvalds at linux-foundation.org>
+---
+
+Adjusted to apply to Debian's 2.6.18 by dann frazier <dannf at hp.com>
+
+diff -urpN linux-source-2.6.18.orig/include/net/af_unix.h linux-source-2.6.18/include/net/af_unix.h
+--- linux-source-2.6.18.orig/include/net/af_unix.h	2008-11-13 17:45:18.000000000 -0700
++++ linux-source-2.6.18/include/net/af_unix.h	2008-11-13 18:01:36.000000000 -0700
+@@ -85,6 +85,7 @@ struct unix_sock {
+         atomic_t                inflight;
+         spinlock_t		lock;
+ 	unsigned int		gc_candidate : 1;
++	unsigned int		gc_maybe_cycle : 1;
+         wait_queue_head_t       peer_wait;
+ };
+ #define unix_sk(__sk) ((struct unix_sock *)__sk)
+diff -urpN linux-source-2.6.18.orig/net/unix/af_unix.c linux-source-2.6.18/net/unix/af_unix.c
+--- linux-source-2.6.18.orig/net/unix/af_unix.c	2008-11-13 17:45:18.000000000 -0700
++++ linux-source-2.6.18/net/unix/af_unix.c	2008-11-13 18:01:36.000000000 -0700
+@@ -1247,14 +1247,23 @@ static void unix_destruct_fds(struct sk_
+ 	sock_wfree(skb);
+ }
+ 
+-static void unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)
++static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)
+ {
+ 	int i;
++
++	/*
++	 * Need to duplicate file references for the sake of garbage
++	 * collection.  Otherwise a socket in the fps might become a
++	 * candidate for GC while the skb is not yet queued.
++	 */
++	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]);
+-	UNIXCB(skb).fp = scm->fp;
+ 	skb->destructor = unix_destruct_fds;
+-	scm->fp = NULL;
++	return 0;
+ }
+ 
+ /*
+@@ -1312,8 +1321,11 @@ static int unix_dgram_sendmsg(struct kio
+ 		goto out;
+ 
+ 	memcpy(UNIXCREDS(skb), &siocb->scm->creds, sizeof(struct ucred));
+-	if (siocb->scm->fp)
+-		unix_attach_fds(siocb->scm, skb);
++	if (siocb->scm->fp) {
++		err = unix_attach_fds(siocb->scm, skb);
++		if (err)
++			goto out_free;
++	}
+ 	unix_get_secdata(siocb->scm, skb);
+ 
+ 	skb->h.raw = skb->data;
+@@ -1484,8 +1496,13 @@ static int unix_stream_sendmsg(struct ki
+ 		size = min_t(int, size, skb_tailroom(skb));
+ 
+ 		memcpy(UNIXCREDS(skb), &siocb->scm->creds, sizeof(struct ucred));
+-		if (siocb->scm->fp)
+-			unix_attach_fds(siocb->scm, skb);
++		if (siocb->scm->fp) {
++			err = unix_attach_fds(siocb->scm, skb);
++			if (err) {
++				kfree_skb(skb);
++				goto out_err;
++			}
++		}
+ 
+ 		if ((err = memcpy_fromiovec(skb_put(skb,size), msg->msg_iov, size)) != 0) {
+ 			kfree_skb(skb);
+diff -urpN linux-source-2.6.18.orig/net/unix/garbage.c linux-source-2.6.18/net/unix/garbage.c
+--- linux-source-2.6.18.orig/net/unix/garbage.c	2008-11-13 18:01:21.000000000 -0700
++++ linux-source-2.6.18/net/unix/garbage.c	2008-11-13 18:04:56.000000000 -0700
+@@ -187,8 +187,17 @@ static void scan_inflight(struct sock *x
+ 				 */
+ 				struct sock *sk = unix_get_socket(*fp++);
+ 				if (sk) {
+-					hit = 1;
+-					func(unix_sk(sk));
++					struct unix_sock *u = unix_sk(sk);
++
++					/*
++					 * Ignore non-candidates, they could
++					 * have been added to the queues after
++					 * starting the garbage collection
++					 */
++					if (u->gc_candidate) {
++						hit = 1;
++						func(u);
++					}
+ 				}
+ 			}
+ 			if (hit && hitlist != NULL) {
+@@ -250,11 +259,11 @@ static void inc_inflight_move_tail(struc
+ {
+ 	atomic_inc(&u->inflight);
+ 	/*
+-	 * If this is still a candidate, move it to the end of the
+-	 * list, so that it's checked even if it was already passed
+-	 * over
++	 * If this still might be part of a cycle, move it to the end
++	 * of the list, so that it's checked even if it was already
++	 * passed over
+ 	 */
+-	if (u->gc_candidate)
++	if (u->gc_maybe_cycle)
+ 		list_move_tail(&u->link, &gc_candidates);
+ }
+ 
+@@ -268,6 +277,7 @@ void unix_gc(void)
+ 	struct unix_sock *next;
+ 	struct sk_buff_head hitlist;
+ 	struct list_head cursor;
++	LIST_HEAD(not_cycle_list);
+ 
+ 	spin_lock(&unix_gc_lock);
+ 
+@@ -283,10 +293,14 @@ void unix_gc(void)
+ 	 *
+ 	 * Holding unix_gc_lock will protect these candidates from
+ 	 * being detached, and hence from gaining an external
+-	 * reference.  This also means, that since there are no
+-	 * possible receivers, the receive queues of these sockets are
+-	 * static during the GC, even though the dequeue is done
+-	 * before the detach without atomicity guarantees.
++	 * reference.  Since there are no possible receivers, all
++	 * buffers currently on the candidates' queues stay there
++	 * during the garbage collection.
++	 *
++	 * We also know that no new candidate can be added onto the
++	 * receive queues.  Other, non candidate sockets _can_ be
++	 * added to queue, so we must make sure only to touch
++	 * candidates.
+ 	 */
+ 	list_for_each_entry_safe(u, next, &gc_inflight_list, link) {
+ 		int total_refs;
+@@ -300,6 +314,7 @@ void unix_gc(void)
+ 		if (total_refs == inflight_refs) {
+ 			list_move_tail(&u->link, &gc_candidates);
+ 			u->gc_candidate = 1;
++			u->gc_maybe_cycle = 1;
+ 		}
+ 	}
+ 
+@@ -326,14 +341,24 @@ void unix_gc(void)
+ 		list_move(&cursor, &u->link);
+ 		
+ 		if (atomic_read(&u->inflight) > 0) {
+-			list_move_tail(&u->link, &gc_inflight_list);
+-			u->gc_candidate = 0;
++			list_move_tail(&u->link, &not_cycle_list);
++			u->gc_maybe_cycle = 0;
+ 			scan_children(&u->sk, inc_inflight_move_tail, NULL);
+ 		}
+ 	}
+ 	list_del(&cursor);
+ 
+ 	/*
++	 * not_cycle_list contains those sockets which do not make up a
++	 * cycle.  Restore these to the inflight list.
++	 */
++	while (!list_empty(&not_cycle_list)) {
++		u = list_entry(not_cycle_list.next, struct unix_sock, link);
++		u->gc_candidate = 0;
++		list_move_tail(&u->link, &gc_inflight_list);
++	}
++
++	/*
+ 	 * Now gc_candidates contains only garbage.  Restore original
+ 	 * inflight counters for these as well, and remove the skbuffs
+ 	 * which are creating the cycle(s).

Modified: dists/etch-security/linux-2.6/debian/patches/series/23etch1
==============================================================================
--- dists/etch-security/linux-2.6/debian/patches/series/23etch1	(original)
+++ dists/etch-security/linux-2.6/debian/patches/series/23etch1	Fri Nov 14 23:56:09 2008
@@ -8,3 +8,7 @@
 + bugfix/hfsplus-fix-Buffer-overflow-with-a-corrupted-image.patch
 + bugfix/hfsplus-check_read_mapping_page-return-value.patch
 + bugfix/hfs-fix-namelength-memory-corruption.patch
++ bugfix/af_unix-fix-garbage-collector-races.patch
++ bugfix/af_unix-convert-socks-to-unix_socks.patch
++ bugfix/net-unix-fix-inflight-counting-bug-in-garbage-collector.patch
++ bugfix/net-fix-recursive-descent-in-__scm_destroy.patch



More information about the Kernel-svn-changes mailing list