diff options
author | Willy Tarreau <w@1wt.eu> | 2009-04-13 15:40:59 +0200 |
---|---|---|
committer | Willy Tarreau <w@1wt.eu> | 2009-04-13 20:28:08 +0200 |
commit | 2c8e160f605fef18b8a1e21051456650544f0bf6 (patch) | |
tree | 34d225beda6e741b1a6442430fbafc75901b5549 | |
parent | aa395c1c2680a0f525f52dff8257357d78bc6319 (diff) | |
download | linux-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.h | 1 | ||||
-rw-r--r-- | net/unix/af_unix.c | 5 | ||||
-rw-r--r-- | net/unix/garbage.c | 18 |
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); } |