[exim-cvs] Fix taint trap in parse_fix_phrase(). Bug 2617

Αρχική Σελίδα
Delete this message
Reply to this message
Συντάκτης: Exim Git Commits Mailing List
Ημερομηνία:  
Προς: exim-cvs
Αντικείμενο: [exim-cvs] Fix taint trap in parse_fix_phrase(). Bug 2617
Gitweb: https://git.exim.org/exim.git/commitdiff/3c90bbcdc7cf73298156f7bcd5f5e750e7814e72
Commit:     3c90bbcdc7cf73298156f7bcd5f5e750e7814e72
Parent:     d78e97406b4d15e53a9eeb345ac07dc2fa69689e
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Thu Jul 9 15:30:55 2020 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Thu Jul 9 18:09:04 2020 +0100


    Fix taint trap in parse_fix_phrase().  Bug 2617
---
 doc/doc-txt/ChangeLog |  6 ++++
 src/src/acl.c         |  3 +-
 src/src/exim.c        |  3 +-
 src/src/expand.c      |  5 +--
 src/src/functions.h   |  4 +--
 src/src/parse.c       | 89 ++++++++++++++++++---------------------------------
 src/src/rewrite.c     |  9 ++----
 src/src/sieve.c       | 17 ++--------
 test/stdout/0002      |  4 +--
 9 files changed, 49 insertions(+), 91 deletions(-)


diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 8d368c8..6d688d1 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -86,6 +86,12 @@ JH/17 Bug 2295: Fix DKIM signing to always semicolon-terminate.  Although the
       intended but triggered by a line-wrap alignement.  Discovery and fix by
       Guillaume Outters, hacked on by JH.


+JH/18 Bug 2617: Fix a taint trap in parse_fix_phrase().  Previously when the
+      name being quoted was tainted a trap would be taken.  Fix by using
+      dynamicaly created buffers.  The routine could have been called by a
+      rewrite with the "h" flag, by using the "-F" command-line option, or
+      by using a "name=" option on a control=submission ACL modifier.
+


 Exim version 4.94
 -----------------
diff --git a/src/src/acl.c b/src/src/acl.c
index 8e17a0e..185a39d 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3268,8 +3268,7 @@ for (; cb; cb = cb->next)
           {
           const uschar *pp = p + 6;
           while (*pp) pp++;
-          submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
-        big_buffer, big_buffer_size));
+          submission_name = parse_fix_phrase(p+6, pp-p-6);
           p = pp;
           }
         else break;
diff --git a/src/src/exim.c b/src/src/exim.c
index 6454bc6..c0ef915 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -4775,8 +4775,7 @@ if (!originator_login || f.running_in_test_harness)
 /* Ensure that the user name is in a suitable form for use as a "phrase" in an
 RFC822 address.*/


-originator_name = string_copy(parse_fix_phrase(originator_name,
- Ustrlen(originator_name), big_buffer, big_buffer_size));
+originator_name = parse_fix_phrase(originator_name, Ustrlen(originator_name));

 /* If a message is created by this call of Exim, the uid/gid of its originator
 are those of the caller. These values are overridden if an existing message is
diff --git a/src/src/expand.c b/src/src/expand.c
index 41860d9..7b8462e 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -7589,13 +7589,10 @@ while (*s)
       prescribed by the RFC, if there are characters that need to be encoded */


       case EOP_RFC2047:
-        {
-        uschar buffer[2048];
         yield = string_cat(yield,
                 parse_quote_2047(sub, Ustrlen(sub), headers_charset,
-                  buffer, sizeof(buffer), FALSE));
+                  FALSE));
         continue;
-        }


       /* RFC 2047 decode */


diff --git a/src/src/functions.h b/src/src/functions.h
index 69bdaa5..f56ab3e 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -368,9 +368,9 @@ extern int     parse_forward_list(uschar *, int, address_item **, uschar **,
                  const uschar *, uschar *, error_block **);
 extern uschar *parse_find_address_end(uschar *, BOOL);
 extern uschar *parse_find_at(uschar *);
-extern const uschar *parse_fix_phrase(const uschar *, int, uschar *, int);
+extern const uschar *parse_fix_phrase(const uschar *, int);
 extern uschar *parse_message_id(uschar *, uschar **, uschar **);
-extern const uschar *parse_quote_2047(const uschar *, int, uschar *, uschar *, int, BOOL);
+extern const uschar *parse_quote_2047(const uschar *, int, uschar *, BOOL);
 extern uschar *parse_date_time(uschar *str, time_t *t);
 extern int     vaguely_random_number(int);
 #ifndef DISABLE_TLS
diff --git a/src/src/parse.c b/src/src/parse.c
index 0ce36a3..acece9b 100644
--- a/src/src/parse.c
+++ b/src/src/parse.c
@@ -843,8 +843,7 @@ return NULL;


/* This function is used for quoting text in headers according to RFC 2047.
If the only characters that strictly need quoting are spaces, we return the
-original string, unmodified. If a quoted string is too long for the buffer, it
-is truncated. (This shouldn't happen: this is normally handling short strings.)
+original string, unmodified.

 Hmmph. As always, things get perverted for other uses. This function was
 originally for the "phrase" part of addresses. Now it is being used for much
@@ -856,77 +855,57 @@ Arguments:
                  chars
   len          the length of the string
   charset      the name of the character set; NULL => iso-8859-1
-  buffer       the buffer to put the answer in
-  buffer_size  the size of the buffer
   fold         if TRUE, a newline is inserted before the separating space when
                  more than one encoded-word is generated


 Returns:       pointer to the original string, if no quoting needed, or
-               pointer to buffer containing the quoted string, or
-               a pointer to "String too long" if the buffer can't even hold
-               the introduction
+               pointer to allocated memory containing the quoted string
 */


const uschar *
-parse_quote_2047(const uschar *string, int len, uschar *charset, uschar *buffer,
- int buffer_size, BOOL fold)
+parse_quote_2047(const uschar *string, int len, uschar *charset, BOOL fold)
{
-const uschar *s = string;
-uschar *p, *t;
-int hlen;
+const uschar * s = string;
+int hlen, l;
BOOL coded = FALSE;
BOOL first_byte = FALSE;
+gstring * g =
+ string_fmt_append(NULL, "=?%s?Q?", charset ? charset : US"iso-8859-1");

-if (!charset) charset = US"iso-8859-1";
+hlen = l = g->ptr;

-/* We don't expect this to fail! */
-
-if (!string_format(buffer, buffer_size, "=?%s?Q?", charset))
- return US"String too long";
-
-hlen = Ustrlen(buffer);
-t = buffer + hlen;
-p = buffer;
-
-for (; len > 0; len--)
+for (s = string; len > 0; s++, len--)
{
- int ch = *s++;
- if (t > buffer + buffer_size - hlen - 8) break;
+ int ch = *s;

-  if ((t - p > 67) && !first_byte)
+  if (g->ptr - l > 67 && !first_byte)
     {
-    *t++ = '?';
-    *t++ = '=';
-    if (fold) *t++ = '\n';
-    *t++ = ' ';
-    p = t;
-    Ustrncpy(p, buffer, hlen);
-    t += hlen;
+    g = fold ? string_catn(g, US"?=\n ", 4) : string_catn(g, US"?= ", 3);
+    l = g->ptr;
+    g = string_catn(g, g->s, hlen);
     }


-  if (ch < 33 || ch > 126 ||
-      Ustrchr("?=()<>@,;:\\\".[]_", ch) != NULL)
+  if (  ch < 33 || ch > 126
+     || Ustrchr("?=()<>@,;:\\\".[]_", ch) != NULL)
     {
     if (ch == ' ')
       {
-      *t++ = '_';
+      g = string_catn(g, US"_", 1);
       first_byte = FALSE;
       }
     else
       {
-      t += sprintf(CS t, "=%02X", ch);
+      g = string_fmt_append(g, "=%02X", ch);
       coded = TRUE;
       first_byte = !first_byte;
       }
     }
-  else { *t++ = ch; first_byte = FALSE; }
+  else
+    { g = string_catn(g, s, 1); first_byte = FALSE; }
   }


-*t++ = '?';
-*t++ = '=';
-*t = 0;
-
-return coded ? buffer : string;
+g = string_catn(g, US"?=", 2);
+return coded ? string_from_gstring(g) : string;
}


@@ -969,32 +948,25 @@ August 2000: Additional code added:
We *could* use this for all cases, getting rid of the messy original code,
but leave it for now. It would complicate simple cases like "John Q. Smith".

-The result is passed back in the buffer; it is usually going to be added to
-some other string. In order to be sure there is going to be no overflow,
-restrict the length of the input to 1/4 of the buffer size - this allows for
-every single character to be quoted or encoded without overflowing, and that
-wouldn't happen because of amalgamation. If the phrase is too long, return a
-fixed string.
+The result is passed back in allocated memory.

 Arguments:
   phrase       an RFC822 phrase
   len          the length of the phrase
-  buffer       a buffer to put the result in
-  buffer_size  the size of the buffer


 Returns:       the fixed RFC822 phrase
 */


const uschar *
-parse_fix_phrase(const uschar *phrase, int len, uschar *buffer, int buffer_size)
+parse_fix_phrase(const uschar *phrase, int len)
{
int ch, i;
BOOL quoted = FALSE;
const uschar *s, *end;
+uschar * buffer;
uschar *t, *yield;

while (len > 0 && isspace(*phrase)) { phrase++; len--; }
-if (len > buffer_size/4) return US"Name too long";

/* See if there are any non-printing characters, and if so, use the RFC 2047
encoding for the whole thing. */
@@ -1002,11 +974,13 @@ encoding for the whole thing. */
for (i = 0, s = phrase; i < len; i++, s++)
if ((*s < 32 && *s != '\t') || *s > 126) break;

-if (i < len) return parse_quote_2047(phrase, len, headers_charset, buffer,
- buffer_size, FALSE);
+if (i < len)
+ return parse_quote_2047(phrase, len, headers_charset, FALSE);

/* No non-printers; use the RFC 822 quoting rules */

+buffer = store_get(len*4, is_tainted(phrase));
+
s = phrase;
end = s + len;
yield = t = buffer + 1;
@@ -1173,6 +1147,7 @@ while (s < end)
}

*t = 0;
+store_release_above(t+1);
return yield;
}

@@ -2102,7 +2077,6 @@ int main(void)
{
int start, end, domain;
uschar buffer[1024];
-uschar outbuff[1024];

big_buffer = store_malloc(big_buffer_size);

@@ -2115,8 +2089,7 @@ while (Ufgets(buffer, sizeof(buffer), stdin) != NULL)
   {
   buffer[Ustrlen(buffer)-1] = 0;
   if (buffer[0] == 0) break;
-  printf("%s\n", CS parse_fix_phrase(buffer, Ustrlen(buffer), outbuff,
-    sizeof(outbuff)));
+  printf("%s\n", CS parse_fix_phrase(buffer, Ustrlen(buffer)));
   }


 printf("Testing parse_extract_address without group syntax and without UTF-8\n");
diff --git a/src/src/rewrite.c b/src/src/rewrite.c
index f942bec..7bff8a2 100644
--- a/src/src/rewrite.c
+++ b/src/src/rewrite.c
@@ -292,16 +292,11 @@ for (rewrite_rule * rule = rewrite_rules;
         uschar *p1 = new + start - 1;
         uschar *p2 = new + end + 1;
         const uschar *pf1, *pf2;
-        uschar buff1[256], buff2[256];


         while (p1 > new && p1[-1] == ' ') p1--;
-        pf1 = parse_fix_phrase(new, p1 - new, buff1, sizeof(buff1));
+        pf1 = parse_fix_phrase(new, p1 - new);
         while (*p2 == ' ') p2++;
-        pf2 = parse_fix_phrase(p2, Ustrlen(p2), buff2, sizeof(buff2));
-
-        /* Note that pf1 and pf2 are NOT necessarily buff1 and buff2. For
-        a non-RFC 2047 phrase that does not need to be RFC 2822 quoted, they
-        will be buff1+1 and buff2+1. */
+        pf2 = parse_fix_phrase(p2, Ustrlen(p2));


         start = Ustrlen(pf1) + start + new - p1;
         end = start + Ustrlen(newparsed);
diff --git a/src/src/sieve.c b/src/src/sieve.c
index 42f2668..9571351 100644
--- a/src/src/sieve.c
+++ b/src/src/sieve.c
@@ -3087,11 +3087,8 @@ while (*filter->pc)
             if ((pid = child_open_exim2(&fd, envelope_from, envelope_from,
             US"sieve-notify")) >= 1)
               {
-              FILE *f;
-              uschar *buffer;
-              int buffer_capacity;
+              FILE * f = fdopen(fd, "wb");


-              f = fdopen(fd, "wb");
               fprintf(f,"From: %s\n", from.length == -1
         ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain")
         : from.character);
@@ -3104,12 +3101,9 @@ while (*filter->pc)
                 message.character=US"Notification";
                 message.length=Ustrlen(message.character);
                 }
-              /* Allocation is larger than necessary, but enough even for split MIME words */
-              buffer_capacity = 32 + 4*message.length;
-              buffer=store_get(buffer_capacity, TRUE);
               if (message.length != -1)
         fprintf(f, "Subject: %s\n", parse_quote_2047(message.character,
-          message.length, US"utf-8", buffer, buffer_capacity, TRUE));
+          message.length, US"utf-8", TRUE));
               fprintf(f,"\n");
               if (body.length>0) fprintf(f,"%s\n",body.character);
               fflush(f);
@@ -3263,8 +3257,6 @@ while (*filter->pc)
     if (exec)
       {
       address_item *addr;
-      uschar *buffer;
-      int buffer_capacity;
       md5 base;
       uschar digest[16];
       uschar hexdigest[33];
@@ -3342,11 +3334,8 @@ while (*filter->pc)
             addr->reply->from = expand_string(US"$local_part@$domain");
           else
             addr->reply->from = from.character;
-          /* Allocation is larger than necessary, but enough even for split MIME words */
-          buffer_capacity=32+4*subject.length;
-          buffer = store_get(buffer_capacity, is_tainted(subject.character));
       /* deconst cast safe as we pass in a non-const item */
-          addr->reply->subject = US parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity, TRUE);
+          addr->reply->subject = US parse_quote_2047(subject.character, subject.length, US"utf-8", TRUE);
           addr->reply->oncelog = string_from_gstring(once);
           addr->reply->once_repeat=days*86400;


diff --git a/test/stdout/0002 b/test/stdout/0002
index d0e8b5d..df659d2 100644
--- a/test/stdout/0002
+++ b/test/stdout/0002
@@ -706,8 +706,8 @@ newline    tab\134backslash ~tilde\177DEL\200\201.

 > abcd      abcd
 > <:abcd:>  =?iso-8859-8?Q?=3C=3Aabcd=3A=3E?=
 > <:ab cd:> =?iso-8859-8?Q?=3C=3Aab_cd=3A=3E?=

-> long:     =?iso-8859-8?Q?_here_we_go=3A_a_string_that_is_going_to_be_encoded=3A_?= =?iso-8859-8?Q?it_will_go_over_the_75-char_limit?=
-> long:     =?iso-8859-8?Q?_here_we_go=3A_a_string_that_is_going_to_be_encoded=3A_?= =?iso-8859-8?Q?it_will_go_over_the_75-char_limit_by_a_long_way=3B_in?= =?iso-8859-8?Q?_fact_this_one_will_go_over_the_150_character_limit?=
+> long:     =?iso-8859-8?Q?_here_we_go=3A_a_string_that_is_going_to_be_encoded=3A_it_will_go_ov?= =?iso-8859-8?Q?er_the_75-char_limit?=
+> long:     =?iso-8859-8?Q?_here_we_go=3A_a_string_that_is_going_to_be_encoded=3A_it_will_go_ov?= =?iso-8859-8?Q?er_the_75-char_limit_by_a_long_way=3B_in_fact_this_on?= =?iso-8859-8?Q?e_will_go_over_the_150_character_limit?=

>
> # RFC 2047 decode
>