[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, ¬_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(¬_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