Subject: lib/15025: libwrap hangs in an infinite loop when a host access line is > 2048 characters long
To: None <gnats-bugs@gnats.netbsd.org>
From: None <trevin@eyring.com>
List: netbsd-bugs
Date: 12/21/2001 12:13:39
>Number:         15025
>Category:       lib
>Synopsis:       libwrap hangs in an infinite loop when a host access line is > 2048 characters long
>Confidential:   no
>Severity:       critical
>Priority:       medium
>Responsible:    lib-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Fri Dec 21 12:14:01 PST 2001
>Closed-Date:
>Last-Modified:
>Originator:     Trevin Beattie
>Release:        1.4.2
>Organization:
Eyring Corporation
>Environment:
NetBSD tuktu.eyring.com 1.4.2 NetBSD 1.4.2 (TUKTU) #0: Thu Mar 23 09:25:10 MST 2000     root@tuktu.eyring.com:/usr/src/sys/arch/i386/compile/TUKTU i386

>Description:
I realize our version of NetBSD is out of date, but it's stable, and
we don't want to muck with our primary server if we don't have to.
More importantly, I've tracked the bug down to the xgets() function
in src/lib/libwrap/misc.c, and this function has not been fixed as of
either NetBSD-release or NetBSD-current (12/21/01).

The problem is that when you accidentally create a line in /etc/hosts.deny
that is longer than 2048 characters, the wrapper library hangs.  top(1)
reveals that all programs using the wrapper are running in infinite loops
(eating up CPU time) in particular multiple copies of inetd.  Because
the processes never die and each connection to the server forks a new
instance of inetd, the situation eventually deteriorates to a point where
the system cannot start any new processes.

Dec 14 18:32:38 tuktu /netbsd: proc: table is full
Dec 14 18:33:20 tuktu /netbsd: proc: table is full
Dec 14 18:34:28 tuktu inetd[215]: fork: Resource temporarily unavailable
Dec 14 18:35:07 tuktu last message repeated 2 times
Dec 14 18:35:49 tuktu inetd[215]: fork: Resource temporarily unavailable
Dec 14 18:37:10 tuktu inetd[215]: fork: Resource temporarily unavailable
Dec 14 18:44:21 tuktu last message repeated 9 times
Dec 14 18:47:37 tuktu last message repeated 8 times
Dec 14 18:54:45 tuktu last message repeated 9 times
Dec 14 18:56:47 tuktu last message repeated 7 times
Dec 14 19:04:19 tuktu last message repeated 10 times
Dec 14 19:07:53 tuktu last message repeated 8 times
Dec 14 19:14:18 tuktu last message repeated 8 times
Dec 14 19:17:45 tuktu last message repeated 7 times
Dec 14 19:24:20 tuktu last message repeated 8 times
Dec 14 19:26:40 tuktu last message repeated 6 times
Dec 14 19:35:00 tuktu last message repeated 6 times
...

The heart of the problem lies in the implementation of xgets().
xgets() will loop as long as the return value of fgets() is not NULL,
and in each pass will subtract the length of the string read from the
length of the buffer remaining.  But remember that fgets() always stores
a '\0' at the end of the buffer, even if the line is too long.
Eventually the loop will call fgets() with a length of 1.  fgets() must
read 0 characters (n-1), but it is not considered an error nor an end
of file, so it returns a valid pointer to a zero-length string.

The phenomenon can be demonstrated by the following short program:

#include <stdio.h>

int
main ()
{
  FILE *fp;
  char buffer[16], *status;
  int len;

  fp = fopen ("/etc/motd", "r");
  printf ("After fgets (%p, %d, fp), ", buffer, sizeof (buffer));
  status = fgets (buffer, sizeof (buffer), fp);
  len = strlen (buffer);
  printf ("fgets returns %p, strlen returns %d\n", status, len);
  printf ("After fgets (%p, %d, fp), ", &buffer[len], sizeof (buffer) - len);
  status = fgets (&buffer[len], sizeof (buffer) - len, fp);
  len = strlen (&buffer[len]);
  printf ("fgets returns %p, strlen returns %d\n", status, len);
  fclose (fp);
  return 0;
}

The output is:

After fgets (0xbfbfd644, 16, fp), fgets returns 0xbfbfd644, strlen returns 15
After fgets (0xbfbfd653, 1, fp), fgets returns 0xbfbfd653, strlen returns 0

>How-To-Repeat:
Create a line in /etc/hosts.deny longer than 2048 characters.
(It may not even matter what the content is.)  Run top(1) and then
initiate a few connections to the machine on inetd-handled ports (or
run tcpdchk or any other program that uses libwrap), and watch as the
processes gradually fall into endless loops.

>Fix:
One solution is to check for a length of 1 before calling fgets():

--- libwrap/misc.c.orig   Wed Sep  1 05:17:44 1999
+++ libwrap/misc.c      Fri Dec 21 12:11:45 2001
@@ -38,7 +38,7 @@
     int     got;
     char   *start = ptr;
 
-    while (fgets(ptr, len, fp)) {
+    while ((len > 1) && fgets(ptr, len, fp)) {
        got = strlen(ptr);
        if (got >= 1 && ptr[got - 1] == '\n') {
            tcpd_context.line++;

>Release-Note:
>Audit-Trail:
>Unformatted: