[PATCH] DoS in RPC implementation (CVE-2011-4069)
Jeff Law
law@redhat.com
Wed Nov 21 19:44:00 GMT 2012
The original thread is here:
http://sourceware.org/ml/libc-alpha/2012-06/msg00074.html
If I'm understanding everything correctly....
When a large number of connections are opened to an RPC service we can
run out of file descriptors. Once we run out of file descriptors CPU
usage goes through the roof. This patch fixes the excessive CPU
utilization problem.
The problem is we're polling on a large number of FD; once we run out of
FDs accept() fails, we copy the array of FDs and poll again, which
returns immediately and we try to accept again. This repeats
indefinitely. The code to dequeue connections never gets a chance to run.
The patch inserts a nanosleep when the accept call fails due to EM_FILE.
This gives the dequeue code a chance to run and dramatically reduces
the cpu burn.
It seems to me we should do the same thing for ENFILE, though in that
case it's more likely an FD will come available.
Roland's suggestions were to add the missing whitespace prior to the
__nanosleep call and to pull the "accept failed" bits into a common
function, both of which are reflected in this patch.
-------------- next part --------------
diff --git a/ChangeLog b/ChangeLog
index 2391ce4..ca694a0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2012-11-21 Jeff Law <law@redhat.com>
+ Martin Osvald <mosvald@redhat.com>
+
+ * sunrpc/Versions (__svc_accept_failed): Mark as GLIBC_PRIVATE.
+ * sunrpc/rpc/svc.h (__svc_accept_failed): New prototype.
+ * sunrpc/svc.c: Include time.h.
+ (__svc_accept_failed): New function.
+ * sunrpc/svc_tcp.c (rendezvous_request): If the accept fails for
+ any reason other than EINTR, call __svc_accept_failed.
+ * sunrpc/svc_udp.c (svcudp_recv): Similarly.
+ * sunrpc/svc_unix.c (rendezvous_request): Similarly.
+
2012-11-20 Carlos O'Donell <carlos_odonell@mentor.com>
* sysdeps/unix/make-syscalls.sh: Document prefixes.
diff --git a/sunrpc/Versions b/sunrpc/Versions
index a11dd8d..d875f91 100644
--- a/sunrpc/Versions
+++ b/sunrpc/Versions
@@ -118,5 +118,6 @@ libc {
}
GLIBC_PRIVATE {
__libc_clntudp_bufcreate; __libc_rpc_getport;
+ __svc_accept_failed;
}
}
diff --git a/sunrpc/rpc/svc.h b/sunrpc/rpc/svc.h
index 54d1ac1..b324b74 100644
--- a/sunrpc/rpc/svc.h
+++ b/sunrpc/rpc/svc.h
@@ -316,4 +316,5 @@ extern SVCXPRT *svcunix_create (int __sock, u_int __sendsize, u_int __recvsize,
__END_DECLS
+extern void __svc_accept_failed (void) attribute_hidden;
#endif /* rpc/svc.h */
diff --git a/sunrpc/svc.c b/sunrpc/svc.c
index 103770a..e48be67 100644
--- a/sunrpc/svc.c
+++ b/sunrpc/svc.c
@@ -41,6 +41,7 @@
#include <rpc/svc.h>
#include <rpc/pmap_clnt.h>
#include <sys/poll.h>
+#include <time.h>
#ifdef _RPC_THREAD_SAFE_
#define xports RPC_THREAD_VARIABLE(svc_xports_s)
@@ -544,6 +545,21 @@ svc_getreq_common (const int fd)
}
libc_hidden_nolink_sunrpc (svc_getreq_common, GLIBC_2_2)
+/* If there are no file descriptors available, then accept will fail.
+ We want to delay here so the connection request can be dequeued;
+ otherwise we can bounce between polling and accepting, never giving the
+ request a chance to dequeue and eating an enormous amount of cpu time
+ in svc_run if we're polling on many file descriptors. */
+void
+__svc_accept_failed (void)
+{
+ if (errno == EMFILE)
+ {
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 50000000 };
+ __nanosleep (&ts, NULL);
+ }
+}
+
#ifdef _RPC_THREAD_SAFE_
void
diff --git a/sunrpc/svc_tcp.c b/sunrpc/svc_tcp.c
index eb61549..93f2ae2 100644
--- a/sunrpc/svc_tcp.c
+++ b/sunrpc/svc_tcp.c
@@ -247,6 +247,7 @@ again:
{
if (errno == EINTR)
goto again;
+ __svc_accept_failed ();
return FALSE;
}
/*
diff --git a/sunrpc/svc_udp.c b/sunrpc/svc_udp.c
index 6c4d75a..3bd718b 100644
--- a/sunrpc/svc_udp.c
+++ b/sunrpc/svc_udp.c
@@ -277,8 +277,12 @@ again:
(int) su->su_iosz, 0,
(struct sockaddr *) &(xprt->xp_raddr), &len);
xprt->xp_addrlen = len;
- if (rlen == -1 && errno == EINTR)
- goto again;
+ if (rlen == -1)
+ {
+ if (errno == EINTR)
+ goto again;
+ __svc_accept_failed ();
+ }
if (rlen < 16) /* < 4 32-bit ints? */
return FALSE;
xdrs->x_op = XDR_DECODE;
diff --git a/sunrpc/svc_unix.c b/sunrpc/svc_unix.c
index 94507b2..5de09c6 100644
--- a/sunrpc/svc_unix.c
+++ b/sunrpc/svc_unix.c
@@ -244,6 +244,7 @@ again:
{
if (errno == EINTR)
goto again;
+ __svc_accept_failed ();
return FALSE;
}
/*
More information about the Libc-alpha
mailing list