summaryrefslogtreecommitdiffstats
path: root/net/unix
diff options
context:
space:
mode:
Diffstat (limited to 'net/unix')
-rw-r--r--net/unix/af_unix.c95
-rw-r--r--net/unix/garbage.c68
-rw-r--r--net/unix/sysctl_net_unix.c4
3 files changed, 119 insertions, 48 deletions
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index ae33770fe..21614a3c6 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -8,7 +8,7 @@
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
- * Version: $Id: af_unix.c,v 1.73 1999/01/15 06:55:48 davem Exp $
+ * Version: $Id: af_unix.c,v 1.76 1999/05/08 05:54:55 davem Exp $
*
* Fixes:
* Linus Torvalds : Assorted bug cures.
@@ -33,6 +33,16 @@
* Lots of bug fixes.
* Alexey Kuznetosv : Repaired (I hope) bugs introduces
* by above two patches.
+ * Andrea Arcangeli : If possible we block in connect(2)
+ * if the max backlog of the listen socket
+ * is been reached. This won't break
+ * old apps and it will avoid huge amount
+ * of socks hashed (this for unix_gc()
+ * performances reasons).
+ * Security fix that limits the max
+ * number of socks to 2*max_files and
+ * the number of skb queueable in the
+ * dgram receiver.
*
* Known differences from reference BSD that was tested:
*
@@ -100,8 +110,12 @@
int sysctl_unix_delete_delay = HZ;
int sysctl_unix_destroy_delay = 10*HZ;
+int sysctl_unix_max_dgram_qlen = 10;
unix_socket *unix_socket_table[UNIX_HASH_SIZE+1];
+static atomic_t unix_nr_socks = ATOMIC_INIT(0);
+static struct wait_queue * unix_ack_wqueue = NULL;
+static struct wait_queue * unix_dgram_wqueue = NULL;
#define unix_sockets_unbound (unix_socket_table[UNIX_HASH_SIZE])
@@ -263,6 +277,8 @@ static void unix_destroy_timer(unsigned long data)
unix_socket *sk=(unix_socket *)data;
if(!unix_locked(sk) && atomic_read(&sk->wmem_alloc) == 0)
{
+ atomic_dec(&unix_nr_socks);
+
sk_free(sk);
/* socket destroyed, decrement count */
@@ -295,13 +311,18 @@ static int unix_release_sock (unix_socket *sk)
sk->dead=1;
sk->socket = NULL;
+ if (sk->state == TCP_LISTEN)
+ wake_up_interruptible(&unix_ack_wqueue);
+ if (sk->type == SOCK_DGRAM)
+ wake_up_interruptible(&unix_dgram_wqueue);
+
skpair=unix_peer(sk);
if (skpair!=NULL)
{
if (sk->type==SOCK_STREAM && unix_our_peer(sk, skpair))
{
- skpair->state_change(skpair);
+ skpair->data_ready(skpair,0);
skpair->shutdown=SHUTDOWN_MASK; /* No more writes*/
}
unix_unlock(skpair); /* It may now die */
@@ -347,6 +368,8 @@ static void unix_destroy_socket(unix_socket *sk)
if(!unix_locked(sk) && atomic_read(&sk->wmem_alloc) == 0)
{
+ atomic_dec(&unix_nr_socks);
+
sk_free(sk);
/* socket destroyed, decrement count */
@@ -371,6 +394,8 @@ static int unix_listen(struct socket *sock, int backlog)
return -EOPNOTSUPP; /* Only stream sockets accept */
if (!sk->protinfo.af_unix.addr)
return -EINVAL; /* No listens on an unbound socket */
+ if ((unsigned) backlog > SOMAXCONN)
+ backlog = SOMAXCONN;
sk->max_ack_backlog=backlog;
sk->state=TCP_LISTEN;
sock->flags |= SO_ACCEPTCON;
@@ -388,6 +413,9 @@ static struct sock * unix_create1(struct socket *sock, int stream)
{
struct sock *sk;
+ if (atomic_read(&unix_nr_socks) >= 2*max_files)
+ return NULL;
+
MOD_INC_USE_COUNT;
sk = sk_alloc(PF_UNIX, GFP_KERNEL, 1);
if (!sk) {
@@ -395,6 +423,8 @@ static struct sock * unix_create1(struct socket *sock, int stream)
return NULL;
}
+ atomic_inc(&unix_nr_socks);
+
sock_init_data(sock,sk);
if (stream)
@@ -673,9 +703,25 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
we will have to recheck all again in any case.
*/
+restart:
/* Find listening sock */
other=unix_find_other(sunaddr, addr_len, sk->type, hash, &err);
+ if (!other)
+ return -ECONNREFUSED;
+
+ while (other->ack_backlog >= other->max_ack_backlog) {
+ unix_unlock(other);
+ if (other->dead || other->state != TCP_LISTEN)
+ return -ECONNREFUSED;
+ if (flags & O_NONBLOCK)
+ return -EAGAIN;
+ interruptible_sleep_on(&unix_ack_wqueue);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ goto restart;
+ }
+
/* create new sock for complete connection */
newsk = unix_create1(NULL, 1);
@@ -704,7 +750,7 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
/* Check that listener is in valid state. */
err = -ECONNREFUSED;
- if (other == NULL || other->dead || other->state != TCP_LISTEN)
+ if (other->dead || other->state != TCP_LISTEN)
goto out;
err = -ENOMEM;
@@ -815,11 +861,10 @@ static int unix_accept(struct socket *sock, struct socket *newsock, int flags)
continue;
}
tsk = skb->sk;
- sk->ack_backlog--;
+ if (sk->max_ack_backlog == sk->ack_backlog--)
+ wake_up_interruptible(&unix_ack_wqueue);
kfree_skb(skb);
- if (!tsk->dead)
- break;
- unix_release_sock(tsk);
+ break;
}
@@ -947,6 +992,7 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, int len,
* Check with 1003.1g - what should
* datagram error
*/
+ dead:
unix_unlock(other);
unix_peer(sk)=NULL;
other = NULL;
@@ -964,6 +1010,29 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, int len,
goto out_unlock;
}
+ while (skb_queue_len(&other->receive_queue) >=
+ sysctl_unix_max_dgram_qlen)
+ {
+ if (sock->file->f_flags & O_NONBLOCK)
+ {
+ err = -EAGAIN;
+ goto out_unlock;
+ }
+ interruptible_sleep_on(&unix_dgram_wqueue);
+ if (other->dead)
+ goto dead;
+ if (sk->shutdown & SEND_SHUTDOWN)
+ {
+ err = -EPIPE;
+ goto out_unlock;
+ }
+ if (signal_pending(current))
+ {
+ err = -ERESTARTSYS;
+ goto out_unlock;
+ }
+ }
+
skb_queue_tail(&other->receive_queue, skb);
other->data_ready(other,len);
@@ -1126,6 +1195,13 @@ static int unix_dgram_recvmsg(struct socket *sock, struct msghdr *msg, int size,
if (!skb)
goto out;
+ /*
+ * sysctl_unix_max_dgram_qlen may change over the time we blocked
+ * in the waitqueue so we must wakeup every time we shrink the
+ * receiver queue. -arca
+ */
+ wake_up_interruptible(&unix_dgram_wqueue);
+
if (msg->msg_name)
{
msg->msg_namelen = sizeof(short);
@@ -1333,7 +1409,10 @@ static int unix_shutdown(struct socket *sock, int mode)
if (mode&SEND_SHUTDOWN)
peer_mode |= RCV_SHUTDOWN;
other->shutdown |= peer_mode;
- other->state_change(other);
+ if (peer_mode&RCV_SHUTDOWN)
+ other->data_ready(other,0);
+ else
+ other->state_change(other);
}
}
return 0;
diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index 3dcc2cada..4f659bd9f 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -17,11 +17,12 @@
*
* - explicit stack instead of recursion
* - tail recurse on first born instead of immediate push/pop
+ * - we gather the stuff that should not be killed into tree
+ * and stack is just a path from root to the current pointer.
*
* Future optimizations:
*
* - don't just push entire root set; process in place
- * - use linked list for internal stack
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -48,8 +49,18 @@
* such socket and closed it (descriptor). That would happen on
* each unix_gc() until the accept(). Since the struct file in
* question would go to the free list and might be reused...
- * That might be the reason of random oopses on close_fp() in
- * unrelated processes.
+ * That might be the reason of random oopses on filp_close()
+ * in unrelated processes.
+ *
+ * AV 28 Feb 1999
+ * Kill the explicit allocation of stack. Now we keep the tree
+ * with root in dummy + pointer (gc_current) to one of the nodes.
+ * Stack is represented as path from gc_current to dummy. Unmark
+ * now means "add to tree". Push == "make it a son of gc_current".
+ * Pop == "move gc_current to parent". We keep only pointers to
+ * parents (->gc_tree).
+ * AV 1 Mar 1999
+ * Damn. Added missing check for ->dead in listen queues scanning.
*
*/
@@ -65,7 +76,6 @@
#include <linux/netdevice.h>
#include <linux/file.h>
#include <linux/proc_fs.h>
-#include <linux/vmalloc.h>
#include <net/sock.h>
#include <net/tcp.h>
@@ -74,9 +84,8 @@
/* Internal data structures and random procedures: */
-static unix_socket **stack; /* stack of objects to mark */
-static int in_stack = 0; /* first free entry in stack */
-static int max_stack; /* Top of stack */
+#define GC_HEAD ((unix_socket *)(-1))
+static unix_socket *gc_current=GC_HEAD; /* stack of objects to mark */
extern inline unix_socket *unix_get_socket(struct file *filp)
{
@@ -122,32 +131,25 @@ void unix_notinflight(struct file *fp)
/*
* Garbage Collector Support Functions
*/
-
-extern inline void push_stack(unix_socket *x)
-{
- if (in_stack == max_stack)
- panic("can't push onto full stack");
- stack[in_stack++] = x;
-}
extern inline unix_socket *pop_stack(void)
{
- if (in_stack == 0)
- panic("can't pop empty gc stack");
- return stack[--in_stack];
+ unix_socket *p=gc_current;
+ gc_current = p->protinfo.af_unix.gc_tree;
+ return p;
}
extern inline int empty_stack(void)
{
- return in_stack == 0;
+ return gc_current == GC_HEAD;
}
extern inline void maybe_unmark_and_push(unix_socket *x)
{
- if (!(x->protinfo.af_unix.marksweep&MARKED))
+ if (x->protinfo.af_unix.gc_tree)
return;
- x->protinfo.af_unix.marksweep&=~MARKED;
- push_stack(x);
+ x->protinfo.af_unix.gc_tree = gc_current;
+ gc_current = x;
}
@@ -169,23 +171,9 @@ void unix_gc(void)
return;
in_unix_gc=1;
- if(stack==NULL || max_files>max_stack)
- {
- if(stack)
- vfree(stack);
- stack=(unix_socket **)vmalloc(max_files*sizeof(struct unix_socket *));
- if(stack==NULL)
- {
- printk(KERN_NOTICE "unix_gc: deferred due to low memory.\n");
- in_unix_gc=0;
- return;
- }
- max_stack=max_files;
- }
-
forall_unix_sockets(i, s)
{
- s->protinfo.af_unix.marksweep|=MARKED;
+ s->protinfo.af_unix.gc_tree=NULL;
}
/*
* Everything is now marked
@@ -262,7 +250,7 @@ tail:
}
}
/* We have to scan not-yet-accepted ones too */
- if (UNIXCB(skb).attr & MSG_SYN) {
+ if ((UNIXCB(skb).attr & MSG_SYN) && !skb->sk->dead) {
if (f==NULL)
f=skb->sk;
else
@@ -276,9 +264,9 @@ tail:
if (f)
{
- if ((f->protinfo.af_unix.marksweep&MARKED))
+ if (!f->protinfo.af_unix.gc_tree)
{
- f->protinfo.af_unix.marksweep&=~MARKED;
+ f->protinfo.af_unix.gc_tree=GC_HEAD;
x=f;
f=NULL;
goto tail;
@@ -290,7 +278,7 @@ tail:
forall_unix_sockets(i, s)
{
- if (s->protinfo.af_unix.marksweep&MARKED)
+ if (!s->protinfo.af_unix.gc_tree)
{
struct sk_buff *nextsk;
skb=skb_peek(&s->receive_queue);
diff --git a/net/unix/sysctl_net_unix.c b/net/unix/sysctl_net_unix.c
index d492e8e2b..2f06a3643 100644
--- a/net/unix/sysctl_net_unix.c
+++ b/net/unix/sysctl_net_unix.c
@@ -19,6 +19,7 @@
extern int sysctl_unix_destroy_delay;
extern int sysctl_unix_delete_delay;
+extern int sysctl_unix_max_dgram_qlen;
ctl_table unix_table[] = {
{NET_UNIX_DESTROY_DELAY, "destroy_delay",
@@ -27,6 +28,9 @@ ctl_table unix_table[] = {
{NET_UNIX_DELETE_DELAY, "delete_delay",
&sysctl_unix_delete_delay, sizeof(int), 0644, NULL,
&proc_dointvec_jiffies},
+ {NET_UNIX_MAX_DGRAM_QLEN, "max_dgram_qlen",
+ &sysctl_unix_max_dgram_qlen, sizeof(int), 0600, NULL,
+ &proc_dointvec_jiffies},
{0}
};