aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilly Tarreau <w@1wt.eu>2009-04-13 15:40:59 +0200
committerWilly Tarreau <w@1wt.eu>2009-04-13 20:28:08 +0200
commit2c8e160f605fef18b8a1e21051456650544f0bf6 (patch)
tree34d225beda6e741b1a6442430fbafc75901b5549
parentaa395c1c2680a0f525f52dff8257357d78bc6319 (diff)
downloadlinux-2.4-2c8e160f605fef18b8a1e21051456650544f0bf6.tar.gz
net: Fix soft lockups/OOM issues w/ unix garbage collector (CVE-2008-5300)
This is a backport of 2.6 commit 5f23b734963ec7eaa3ebcd9050da0c9b7d143dd3 : This is an implementation of David Miller's suggested fix in: https://bugzilla.redhat.com/show_bug.cgi?id=470201 It has been updated to use wait_event() instead of wait_event_interruptible(). Paraphrasing the description from the above report, it makes sendmsg() block while UNIX garbage collection is in progress. This avoids a situation where child processes continue to queue new FDs over a AF_UNIX socket to a parent which is in the exit path and running garbage collection on these FDs. This contention can result in soft lockups and oom-killing of unrelated processes. Signed-off-by: dann frazier <dannf@hp.com> Signed-off-by: David S. Miller <davem@davemloft.net> Cc: Eugene Teo <eteo@redhat.com> [wt: yield the CPU after breaking a socket pair] Signed-off-by: Willy Tarreau <w@1wt.eu>
-rw-r--r--include/net/af_unix.h1
-rw-r--r--net/unix/af_unix.c5
-rw-r--r--net/unix/garbage.c18
3 files changed, 24 insertions, 0 deletions
diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 86f100123b5a29..56f41c19da2086 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -5,6 +5,7 @@ extern void unix_inflight(struct file *fp);
extern void unix_notinflight(struct file *fp);
typedef struct sock unix_socket;
extern void unix_gc(void);
+extern void wait_for_unix_gc(void);
#define UNIX_HASH_SIZE 256
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index cb967a5977833e..3da6ad977c86ab 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -374,6 +374,7 @@ static int unix_release_sock (unix_socket *sk, int embrion)
read_lock(&skpair->callback_lock);
sk_wake_async(skpair,1,POLL_HUP);
read_unlock(&skpair->callback_lock);
+ yield(); /* let the other side wake up */
}
sock_put(skpair); /* It may now die */
unix_peer(sk) = NULL;
@@ -1161,6 +1162,8 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, int len,
struct sk_buff *skb;
long timeo;
+ wait_for_unix_gc();
+
err = -EOPNOTSUPP;
if (msg->msg_flags&MSG_OOB)
goto out;
@@ -1291,6 +1294,8 @@ static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg, int len,
struct sk_buff *skb;
int sent=0;
+ wait_for_unix_gc();
+
err = -EOPNOTSUPP;
if (msg->msg_flags&MSG_OOB)
goto out_err;
diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index 457515db9b1878..16011be387a86a 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -77,6 +77,7 @@
#include <linux/file.h>
#include <linux/proc_fs.h>
#include <linux/tcp.h>
+#include <linux/wait.h>
#include <net/sock.h>
#include <net/af_unix.h>
@@ -89,6 +90,8 @@
static unix_socket *gc_current=GC_HEAD; /* stack of objects to mark */
+static DECLARE_WAIT_QUEUE_HEAD(unix_gc_wait);
+
atomic_t unix_tot_inflight = ATOMIC_INIT(0);
@@ -163,6 +166,13 @@ extern inline void maybe_unmark_and_push(unix_socket *x)
}
+static int gc_in_progress;
+
+void wait_for_unix_gc(void)
+{
+ wait_event(unix_gc_wait, gc_in_progress == 0);
+}
+
/* The external entry point: unix_gc() */
void unix_gc(void)
@@ -180,6 +190,11 @@ void unix_gc(void)
if (down_trylock(&unix_gc_sem))
return;
+ if (gc_in_progress)
+ goto out;
+
+ gc_in_progress = 1;
+
read_lock(&unix_table_lock);
forall_unix_sockets(i, s)
@@ -306,5 +321,8 @@ void unix_gc(void)
*/
__skb_queue_purge(&hitlist);
+ gc_in_progress = 0;
+ wake_up(&unix_gc_wait);
+ out:
up(&unix_gc_sem);
}