]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
Fix reads for sizes larger than INT_MAX in AF_INET lookup
authorSiddhesh Poyarekar <siddhesh@redhat.com>
Wed, 30 Oct 2013 10:43:37 +0000 (16:13 +0530)
committerSiddhesh Poyarekar <siddhesh@redhat.com>
Wed, 30 Oct 2013 10:49:40 +0000 (16:19 +0530)
Currently for AF_INET lookups from the hosts file, buffer sizes larger
than INT_MAX silently overflow and may result in access beyond bounds
of a buffer.  This happens when the number of results in an AF_INET
lookup in /etc/hosts are very large.

There are two aspects to the problem.  One problem is that the size
computed from the buffer size is stored into an int, which results in
overflow for large sizes.  Additionally, even if this size was
expanded, the function used to read content into the buffer (fgets)
accepts only int sizes.  As a result, the fix is to have a function
wrap around fgets that calls it multiple times with int sizes if
necessary.

ChangeLog
NEWS
nss/nss_files/files-XXX.c

index 32f0d5f220fac60c1fb50feb419a7a44878edee6..b31973544cd617eebe04e6cb2968541a9919278b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2013-10-30  Siddhesh Poyarekar  <siddhesh@redhat.com>
+
+       [BZ #16071]
+       * nss/nss_files/files-XXX.c (get_contents_ret): New
+       enumerator.
+       (get_contents): New function.
+       (internal_getent): Use it.  Expand size of LINEBUFLEN.
+
 2013-10-30  Mike Frysinger  <vapier@gentoo.org>
 
        * configure.in: Moved to ...
diff --git a/NEWS b/NEWS
index fa24d9bfa062c24951ff84aa591b532bfc7dd513..87d183afb278ad0c8943123db660b642fd86855c 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -16,7 +16,7 @@ Version 2.19
   15760, 15764, 15797, 15825, 15844, 15847, 15849, 15855, 15856, 15857,
   15859, 15867, 15886, 15887, 15890, 15892, 15893, 15895, 15897, 15905,
   15909, 15919, 15921, 15923, 15939, 15948, 15963, 15966, 15988, 16032,
-  16034, 16036, 16041, 16072, 16074, 16078.
+  16034, 16036, 16041, 16071, 16072, 16074, 16078.
 
 * CVE-2012-4412 The strcoll implementation caches indices and rules for
   large collation sequences to optimize multiple passes.  This cache
index 082d1ea2b727605e1ca277c2dcd6fbd0dfe2cd27..b62208c324c01a71f4b47bc0987f35eacfb22fff 100644 (file)
@@ -179,8 +179,51 @@ CONCAT(_nss_files_end,ENTNAME) (void)
   return NSS_STATUS_SUCCESS;
 }
 \f
-/* Parsing the database file into `struct STRUCTURE' data structures.  */
 
+typedef enum
+{
+  gcr_ok = 0,
+  gcr_error = -1,
+  gcr_overflow = -2
+} get_contents_ret;
+
+/* Hack around the fact that fgets only accepts int sizes.  */
+static get_contents_ret
+get_contents (char *linebuf, size_t len, FILE *stream)
+{
+  size_t remaining_len = len;
+  char *curbuf = linebuf;
+
+  do
+    {
+      int curlen = ((remaining_len > (size_t) INT_MAX) ? INT_MAX
+                   : remaining_len);
+      char *p = fgets_unlocked (curbuf, curlen, stream);
+
+      ((unsigned char *) curbuf)[curlen - 1] = 0xff;
+
+      /* EOF or read error.  */
+      if (p == NULL)
+        return gcr_error;
+
+      /* Done reading in the line.  */
+      if (((unsigned char *) curbuf)[curlen - 1] == 0xff)
+        return gcr_ok;
+
+      /* Drop the terminating '\0'.  */
+      remaining_len -= curlen - 1;
+      curbuf += curlen - 1;
+    }
+  /* fgets copies one less than the input length.  Our last iteration is of
+     REMAINING_LEN and once that is done, REMAINING_LEN is decremented by
+     REMAINING_LEN - 1, leaving the result as 1.  */
+  while (remaining_len > 1);
+
+  /* This means that the current buffer was not large enough.  */
+  return gcr_overflow;
+}
+
+/* Parsing the database file into `struct STRUCTURE' data structures.  */
 static enum nss_status
 internal_getent (struct STRUCTURE *result,
                 char *buffer, size_t buflen, int *errnop H_ERRNO_PROTO
@@ -188,7 +231,7 @@ internal_getent (struct STRUCTURE *result,
 {
   char *p;
   struct parser_data *data = (void *) buffer;
-  int linebuflen = buffer + buflen - data->linebuffer;
+  size_t linebuflen = buffer + buflen - data->linebuffer;
   int parse_result;
 
   if (buflen < sizeof *data + 2)
@@ -200,17 +243,16 @@ internal_getent (struct STRUCTURE *result,
 
   do
     {
-      /* Terminate the line so that we can test for overflow.  */
-      ((unsigned char *) data->linebuffer)[linebuflen - 1] = '\xff';
+      get_contents_ret r = get_contents (data->linebuffer, linebuflen, stream);
 
-      p = fgets_unlocked (data->linebuffer, linebuflen, stream);
-      if (p == NULL)
+      if (r == gcr_error)
        {
          /* End of file or read error.  */
          H_ERRNO_SET (HOST_NOT_FOUND);
          return NSS_STATUS_NOTFOUND;
        }
-      else if (((unsigned char *) data->linebuffer)[linebuflen - 1] != 0xff)
+
+      if (r == gcr_overflow)
        {
          /* The line is too long.  Give the user the opportunity to
             enlarge the buffer.  */
@@ -219,7 +261,8 @@ internal_getent (struct STRUCTURE *result,
          return NSS_STATUS_TRYAGAIN;
        }
 
-      /* Skip leading blanks.  */
+      /* Everything OK.  Now skip leading blanks.  */
+      p = data->linebuffer;
       while (isspace (*p))
        ++p;
     }