All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/1] Fix DoS vulnerability  in libtirpc
@ 2021-08-07 17:00 Dai Ngo
  2021-08-09 14:12 ` [Libtirpc-devel] " Chuck Lever III
  0 siblings, 1 reply; 2+ messages in thread
From: Dai Ngo @ 2021-08-07 17:00 UTC (permalink / raw)
  To: libtirpc-devel; +Cc: linux-nfs

Currently svc_run does not handle poll time and rendezvous_request
does not handle EMFILE error returned from accept(2 as it used to.
These two missing functionality were removed by commit b2c9430f46c4.

The effect of not handling poll timeout allows idle TCP conections
to remain ESTABLISHED indefinitely. When the number of connections
reaches the limit of the open file descriptors (ulimit -n) then
accept(2) fails with EMFILE. Since there is no handling of EMFILE
error this causes svc_run() to get in a tight loop calling accept(2).
This resulting in the RPC service of svc_run is being down, it's
no longer able to service any requests.

The below script, td.sh, with nc (nmap-ncat-7.80-3) can be used
to take down the RPC service:

if [ $# -ne 3 ]; then
        echo "$0: server dst_port conn_cnt"
        exit
fi
server=$1
dport=$2
conn_cnt=$3
echo "dport[$dport] server[$server] conn_cnt[$conn_cnt]"

pcnt=0
while [ $pcnt -lt $conn_cnt ]
do
        nc -v --recv-only $server $dport &
        pcnt=`expr $pcnt + 1`
done

Fix by restoring code in svc_run to cleanup idle conncetions when
poll(2) returns 0 (timeout) and in rendezvous_request to handle
EMFILE error returned from accept(2).

Fixes: b2c9430f46c4 Use poll() instead of select() in svc_run()
Signed-off-by: dai.ngo@oracle.com
---
 src/libtirpc.map |  2 +-
 src/rpc_com.h    |  2 ++
 src/svc.c        |  2 +-
 src/svc_run.c    |  2 ++
 src/svc_vc.c     | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/src/libtirpc.map b/src/libtirpc.map
index 21d60651ff57..b754110faadb 100644
--- a/src/libtirpc.map
+++ b/src/libtirpc.map
@@ -331,5 +331,5 @@ TIRPC_PRIVATE {
   global:
     __libc_clntudp_bufcreate;
   # private, but used by rpcbind:
-    __svc_clean_idle; svc_auth_none; libtirpc_set_debug;
+    __svc_destroy_idle; __svc_clean_idle; svc_auth_none; libtirpc_set_debug;
 };
diff --git a/src/rpc_com.h b/src/rpc_com.h
index 76badefcfe90..ede6ec8b1d4e 100644
--- a/src/rpc_com.h
+++ b/src/rpc_com.h
@@ -55,6 +55,7 @@ struct netbuf *__rpcb_findaddr_timed(rpcprog_t, rpcvers_t,
 bool_t __rpc_control(int,void *);
 
 bool_t __svc_clean_idle(fd_set *, int, bool_t);
+void __svc_destroy_idle(int, bool_t);
 bool_t __xdrrec_setnonblock(XDR *, int);
 bool_t __xdrrec_getrec(XDR *, enum xprt_stat *, bool_t);
 void __xprt_unregister_unlocked(SVCXPRT *);
@@ -62,6 +63,7 @@ void __xprt_set_raddr(SVCXPRT *, const struct sockaddr_storage *);
 
 
 extern int __svc_maxrec;
+extern SVCXPRT **__svc_xports;
 
 #ifdef __cplusplus
 }
diff --git a/src/svc.c b/src/svc.c
index 6db164bbd76b..aa0c92591914 100644
--- a/src/svc.c
+++ b/src/svc.c
@@ -57,7 +57,7 @@
 
 #define max(a, b) (a > b ? a : b)
 
-static SVCXPRT **__svc_xports;
+SVCXPRT **__svc_xports;
 int __svc_maxrec;
 
 /*
diff --git a/src/svc_run.c b/src/svc_run.c
index f40314b9948e..4eba36174524 100644
--- a/src/svc_run.c
+++ b/src/svc_run.c
@@ -44,6 +44,7 @@
 #include "rpc_com.h"
 #include <sys/select.h>
 
+
 void
 svc_run()
 {
@@ -86,6 +87,7 @@ svc_run()
           warn ("svc_run: - poll failed");
           break;
         case 0:
+          __svc_destroy_idle(30, FALSE);
           continue;
         default:
           svc_getreq_poll (my_pollfd, i);
diff --git a/src/svc_vc.c b/src/svc_vc.c
index f1d9f001fcdc..4880ab5dbc26 100644
--- a/src/svc_vc.c
+++ b/src/svc_vc.c
@@ -58,6 +58,7 @@
 
 #include <rpc/rpc.h>
 
+#include "debug.h"
 #include "rpc_com.h"
 
 #include <getpeereid.h>
@@ -337,6 +338,10 @@ again:
 	if (sock < 0) {
 		if (errno == EINTR)
 			goto again;
+		if (errno == EMFILE || errno == ENFILE) {
+			/* remove least active fd */
+			__svc_destroy_idle(0, FALSE);
+		}
 		return (FALSE);
 	}
 	/*
@@ -820,3 +825,46 @@ __svc_clean_idle(fd_set *fds, int timeout, bool_t cleanblock)
 {
 	return FALSE;
 }
+
+void
+__svc_destroy_idle(int timeout, bool_t cleanblock)
+{
+	int i;
+	SVCXPRT *xprt, *least_active;
+	struct timeval tv, tdiff, tmax;
+	struct cf_conn *cd;
+
+	gettimeofday(&tv, NULL);
+	tmax.tv_sec = tmax.tv_usec = 0;
+	least_active = NULL;
+	rwlock_wrlock(&svc_fd_lock);
+
+	for (i = 0; i <= svc_max_pollfd; i++) {
+		if (svc_pollfd[i].fd == -1)
+			continue;
+		xprt = __svc_xports[i];
+		if (xprt == NULL || xprt->xp_ops == NULL ||
+			xprt->xp_ops->xp_recv != svc_vc_recv)
+			continue;
+		cd = (struct cf_conn *)xprt->xp_p1;
+		if (!cleanblock && !cd->nonblock)
+			continue;
+		if (timeout == 0) {
+			timersub(&tv, &cd->last_recv_time, &tdiff);
+			if (timercmp(&tdiff, &tmax, >)) {
+				tmax = tdiff;
+				least_active = xprt;
+			}
+			continue;
+		}
+		if (tv.tv_sec - cd->last_recv_time.tv_sec > timeout) {
+			__xprt_unregister_unlocked(xprt);
+			__svc_vc_dodestroy(xprt);
+		}
+	}
+	if (timeout == 0 && least_active != NULL) {
+		__xprt_unregister_unlocked(least_active);
+		__svc_vc_dodestroy(least_active);
+	}
+	rwlock_unlock(&svc_fd_lock);
+}
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [Libtirpc-devel] [PATCH 1/1] Fix DoS vulnerability  in libtirpc
  2021-08-07 17:00 [PATCH 1/1] Fix DoS vulnerability in libtirpc Dai Ngo
@ 2021-08-09 14:12 ` Chuck Lever III
  0 siblings, 0 replies; 2+ messages in thread
From: Chuck Lever III @ 2021-08-09 14:12 UTC (permalink / raw)
  To: Dai Ngo; +Cc: libtirpc List, Linux NFS Mailing List



> On Aug 7, 2021, at 1:00 PM, Dai Ngo <dai.ngo@oracle.com> wrote:
> 
> Currently svc_run does not handle poll time and rendezvous_request
> does not handle EMFILE error returned from accept(2 as it used to.
> These two missing functionality were removed by commit b2c9430f46c4.
> 
> The effect of not handling poll timeout allows idle TCP conections
> to remain ESTABLISHED indefinitely. When the number of connections
> reaches the limit of the open file descriptors (ulimit -n) then
> accept(2) fails with EMFILE. Since there is no handling of EMFILE
> error this causes svc_run() to get in a tight loop calling accept(2).
> This resulting in the RPC service of svc_run is being down, it's
> no longer able to service any requests.
> 
> The below script, td.sh, with nc (nmap-ncat-7.80-3) can be used
> to take down the RPC service:
> 
> if [ $# -ne 3 ]; then
>        echo "$0: server dst_port conn_cnt"
>        exit
> fi
> server=$1
> dport=$2
> conn_cnt=$3
> echo "dport[$dport] server[$server] conn_cnt[$conn_cnt]"
> 
> pcnt=0
> while [ $pcnt -lt $conn_cnt ]
> do
>        nc -v --recv-only $server $dport &
>        pcnt=`expr $pcnt + 1`
> done
> 
> Fix by restoring code in svc_run to cleanup idle conncetions when
> poll(2) returns 0 (timeout) and in rendezvous_request to handle
> EMFILE error returned from accept(2).
> 
> Fixes: b2c9430f46c4 Use poll() instead of select() in svc_run()
> Signed-off-by: dai.ngo@oracle.com
> ---
> src/libtirpc.map |  2 +-
> src/rpc_com.h    |  2 ++
> src/svc.c        |  2 +-
> src/svc_run.c    |  2 ++
> src/svc_vc.c     | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
> 5 files changed, 54 insertions(+), 2 deletions(-)
> 
> diff --git a/src/libtirpc.map b/src/libtirpc.map
> index 21d60651ff57..b754110faadb 100644
> --- a/src/libtirpc.map
> +++ b/src/libtirpc.map
> @@ -331,5 +331,5 @@ TIRPC_PRIVATE {
>   global:
>     __libc_clntudp_bufcreate;
>   # private, but used by rpcbind:
> -    __svc_clean_idle; svc_auth_none; libtirpc_set_debug;
> +    __svc_destroy_idle; __svc_clean_idle; svc_auth_none; libtirpc_set_debug;

As I stated yesterday, as a reviewer I agree with everything
in this patch except for the addition of __svc_destroy_idle
to the library's private API list.

IMO the private __svc_clean_idle() API was a mistake we
shouldn't make again. (That might be why Thorsten removed
it years ago).

Thanks, Dai, for your work nailing this down!


> };
> diff --git a/src/rpc_com.h b/src/rpc_com.h
> index 76badefcfe90..ede6ec8b1d4e 100644
> --- a/src/rpc_com.h
> +++ b/src/rpc_com.h
> @@ -55,6 +55,7 @@ struct netbuf *__rpcb_findaddr_timed(rpcprog_t, rpcvers_t,
> bool_t __rpc_control(int,void *);
> 
> bool_t __svc_clean_idle(fd_set *, int, bool_t);
> +void __svc_destroy_idle(int, bool_t);
> bool_t __xdrrec_setnonblock(XDR *, int);
> bool_t __xdrrec_getrec(XDR *, enum xprt_stat *, bool_t);
> void __xprt_unregister_unlocked(SVCXPRT *);
> @@ -62,6 +63,7 @@ void __xprt_set_raddr(SVCXPRT *, const struct sockaddr_storage *);
> 
> 
> extern int __svc_maxrec;
> +extern SVCXPRT **__svc_xports;
> 
> #ifdef __cplusplus
> }
> diff --git a/src/svc.c b/src/svc.c
> index 6db164bbd76b..aa0c92591914 100644
> --- a/src/svc.c
> +++ b/src/svc.c
> @@ -57,7 +57,7 @@
> 
> #define max(a, b) (a > b ? a : b)
> 
> -static SVCXPRT **__svc_xports;
> +SVCXPRT **__svc_xports;
> int __svc_maxrec;
> 
> /*
> diff --git a/src/svc_run.c b/src/svc_run.c
> index f40314b9948e..4eba36174524 100644
> --- a/src/svc_run.c
> +++ b/src/svc_run.c
> @@ -44,6 +44,7 @@
> #include "rpc_com.h"
> #include <sys/select.h>
> 
> +
> void
> svc_run()
> {
> @@ -86,6 +87,7 @@ svc_run()
>           warn ("svc_run: - poll failed");
>           break;
>         case 0:
> +          __svc_destroy_idle(30, FALSE);
>           continue;
>         default:
>           svc_getreq_poll (my_pollfd, i);
> diff --git a/src/svc_vc.c b/src/svc_vc.c
> index f1d9f001fcdc..4880ab5dbc26 100644
> --- a/src/svc_vc.c
> +++ b/src/svc_vc.c
> @@ -58,6 +58,7 @@
> 
> #include <rpc/rpc.h>
> 
> +#include "debug.h"
> #include "rpc_com.h"
> 
> #include <getpeereid.h>
> @@ -337,6 +338,10 @@ again:
> 	if (sock < 0) {
> 		if (errno == EINTR)
> 			goto again;
> +		if (errno == EMFILE || errno == ENFILE) {
> +			/* remove least active fd */
> +			__svc_destroy_idle(0, FALSE);
> +		}
> 		return (FALSE);
> 	}
> 	/*
> @@ -820,3 +825,46 @@ __svc_clean_idle(fd_set *fds, int timeout, bool_t cleanblock)
> {
> 	return FALSE;
> }
> +
> +void
> +__svc_destroy_idle(int timeout, bool_t cleanblock)
> +{
> +	int i;
> +	SVCXPRT *xprt, *least_active;
> +	struct timeval tv, tdiff, tmax;
> +	struct cf_conn *cd;
> +
> +	gettimeofday(&tv, NULL);
> +	tmax.tv_sec = tmax.tv_usec = 0;
> +	least_active = NULL;
> +	rwlock_wrlock(&svc_fd_lock);
> +
> +	for (i = 0; i <= svc_max_pollfd; i++) {
> +		if (svc_pollfd[i].fd == -1)
> +			continue;
> +		xprt = __svc_xports[i];
> +		if (xprt == NULL || xprt->xp_ops == NULL ||
> +			xprt->xp_ops->xp_recv != svc_vc_recv)
> +			continue;
> +		cd = (struct cf_conn *)xprt->xp_p1;
> +		if (!cleanblock && !cd->nonblock)
> +			continue;
> +		if (timeout == 0) {
> +			timersub(&tv, &cd->last_recv_time, &tdiff);
> +			if (timercmp(&tdiff, &tmax, >)) {
> +				tmax = tdiff;
> +				least_active = xprt;
> +			}
> +			continue;
> +		}
> +		if (tv.tv_sec - cd->last_recv_time.tv_sec > timeout) {
> +			__xprt_unregister_unlocked(xprt);
> +			__svc_vc_dodestroy(xprt);
> +		}
> +	}
> +	if (timeout == 0 && least_active != NULL) {
> +		__xprt_unregister_unlocked(least_active);
> +		__svc_vc_dodestroy(least_active);
> +	}
> +	rwlock_unlock(&svc_fd_lock);
> +}
> -- 
> 2.26.2

--
Chuck Lever




^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2021-08-09 14:12 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-07 17:00 [PATCH 1/1] Fix DoS vulnerability in libtirpc Dai Ngo
2021-08-09 14:12 ` [Libtirpc-devel] " Chuck Lever III

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.