diff options
Diffstat (limited to 'net/unix')
-rw-r--r-- | net/unix/af_unix.c | 95 | ||||
-rw-r--r-- | net/unix/garbage.c | 68 | ||||
-rw-r--r-- | net/unix/sysctl_net_unix.c | 4 |
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} }; |