CVE-2020-28009: Integer overflow in get_stdinput()
[exim.git] / src / src / string.c
index 1b08e7fb8a58a856a72dd3ec276e2402c9e5a64d..64d14439e6bab3c2378e8bf594c40a2423d6348c 100644 (file)
@@ -281,17 +281,17 @@ return ch;
 /* This function is called for critical strings. It checks for any
 non-printing characters, and if any are found, it makes a new copy
 of the string with suitable escape sequences. It is most often called by the
-macro string_printing(), which sets allow_tab TRUE.
+macro string_printing(), which sets flags to 0.
 
 Arguments:
   s             the input string
-  allow_tab     TRUE to allow tab as a printing character
+  flags                Bit 0: convert tabs.  Bit 1: convert spaces.
 
 Returns:        string with non-printers encoded as printing sequences
 */
 
 const uschar *
-string_printing2(const uschar *s, BOOL allow_tab)
+string_printing2(const uschar *s, int flags)
 {
 int nonprintcount = 0;
 int length = 0;
@@ -301,7 +301,10 @@ uschar *ss, *tt;
 while (*t != 0)
   {
   int c = *t++;
-  if (!mac_isprint(c) || (!allow_tab && c == '\t')) nonprintcount++;
+  if (  !mac_isprint(c)
+     || flags & SP_TAB && c == '\t'
+     || flags & SP_SPACE && c == ' '
+     ) nonprintcount++;
   length++;
   }
 
@@ -310,17 +313,19 @@ if (nonprintcount == 0) return s;
 /* Get a new block of store guaranteed big enough to hold the
 expanded string. */
 
-ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s));
+tt = ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s));
 
 /* Copy everything, escaping non printers. */
 
-t = s;
-tt = ss;
-
-while (*t != 0)
+for (t = s; *t; )
   {
   int c = *t;
-  if (mac_isprint(c) && (allow_tab || c != '\t')) *tt++ = *t++; else
+  if (  mac_isprint(c)
+     && (!(flags & SP_TAB) || c != '\t')
+     && (!(flags & SP_SPACE) || c != ' ')
+     )
+    *tt++ = *t++;
+  else
     {
     *tt++ = '\\';
     switch (*t)
@@ -570,7 +575,7 @@ uschar *
 string_copy_dnsdomain(uschar *s)
 {
 uschar *yield;
-uschar *ss = yield = store_get(Ustrlen(s) + 1, is_tainted(s));
+uschar *ss = yield = store_get(Ustrlen(s) + 1, TRUE);  /* always treat as tainted */
 
 while (*s != 0)
   {
@@ -853,6 +858,9 @@ Arguments:
   separator  a pointer to the separator character in an int (see above)
   buffer     where to put a copy of the next string in the list; or
                NULL if the next string is returned in new memory
+            Note that if the list is tainted then a provided buffer must be
+            also (else we trap, with a message referencing the callsite).
+            If we do the allocation, taint is handled there.
   buflen     when buffer is not NULL, the size of buffer; otherwise ignored
 
 Returns:     pointer to buffer, containing the next substring,
@@ -944,9 +952,15 @@ else
     s = ss;
     if (!*s || *++s != sep || sep_is_special) break;
     }
-  while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--;
+
+  /* Trim trailing spaces from the returned string */
+
+  /* while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--; */
+  while (  g->ptr > 0 && isspace(g->s[g->ptr-1])
+       && (g->ptr == 1 || g->s[g->ptr-2] != '\\') )
+    g->ptr--;
   buffer = string_from_gstring(g);
-  gstring_release_unused(g);
+  gstring_release_unused_trc(g, CCS func, line);
   }
 
 /* Update the current pointer and return the new string */
@@ -1080,7 +1094,16 @@ existing length of the string. */
 
 unsigned inc = oldsize < 4096 ? 127 : 1023;
 
+if (g->ptr < 0 || g->ptr > g->size || g->size >= INT_MAX/2)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "internal error in gstring_grow (ptr %d size %d)", g->ptr, g->size);
+
 if (count <= 0) return;
+
+if (count >= INT_MAX/2 - g->ptr)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "internal error in gstring_grow (ptr %d count %d)", g->ptr, count);
+
 g->size = (p + count + inc + 1) & ~inc;                /* one for a NUL */
 
 /* Try to extend an existing allocation. If the result of calling
@@ -1129,17 +1152,25 @@ string_catn(gstring * g, const uschar *s, int count)
 int p;
 BOOL srctaint = is_tainted(s);
 
+if (count < 0)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "internal error in string_catn (count %d)", count);
+
 if (!g)
   {
   unsigned inc = count < 4096 ? 127 : 1023;
-  unsigned size = ((count + inc) &  ~inc) + 1;
+  unsigned size = ((count + inc) &  ~inc) + 1; /* round up requested count */
   g = string_get_tainted(size, srctaint);
   }
 else if (srctaint && !is_tainted(g->s))
   gstring_rebuffer(g);
 
+if (g->ptr < 0 || g->ptr > g->size)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "internal error in string_catn (ptr %d size %d)", g->ptr, g->size);
+
 p = g->ptr;
-if (p + count >= g->size)
+if (count >= g->size - p)
   gstring_grow(g, count);
 
 /* Because we always specify the exact number of characters to copy, we can
@@ -1681,6 +1712,7 @@ int main(void)
 uschar buffer[256];
 
 printf("Testing is_ip_address\n");
+store_init();
 
 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
   {