https://bugs.exim.org/show_bug.cgi?id=3155

            Bug ID: 3155
           Summary: memory corruption in internal_transport_write_message
                    & transport_headers_send
           Product: Exim
           Version: 4.98.2
          Hardware: x86-64
                OS: Linux
            Status: NEW
          Severity: bug
          Priority: medium
         Component: Transports
          Assignee: [email protected]
          Reporter: [email protected]
                CC: [email protected]

Created attachment 1512
  --> https://bugs.exim.org/attachment.cgi?id=1512&action=edit
script to reproduce the bug(s)

Hi Exim developers,

I discovered a possible memory corruption in the functions
internal_transport_write_message() and transport_headers_send() in
transports.c.

In both cases, the function write_chunk() is called in between store_mark() and
corresponding store_reset() calls. In transport_headers_send() this call is a
bit hidden as the function pointer sendfn() is called and write_chunk() is
assigned to this pointer.

write_chunk() does call transport_write_block() in case the header buffer needs
flushing. transport_write_block() will allocate tctx->u.msg if not yet
allocated.

When store_reset() is called afterwards, the memory u.msg is pointing to can
get overwritten (or even freed?) anytime later although u.msg is still being
used.


I have prepared a small script that demonstrates the behaviour:
exim-mem-corruption.py (see attachment)

To reproduce the bug you must run the script on a machine with exim listening
on localhost:25. Use a suitable sender address (first param) that is accepted
by your mail setup.

For the bug in transport_headers_send() you must use a recipient address
(second param) that is routed through a transport with headers_rewrite to
rewrite the From: header. The rewrite does not need to actually change the
header, a rewrite to the same value is enough to trigger the bug.

To trigger the bug in internal_transport_write_message(), you must have a
transport, that has envelope_to_add enabled. You then have to call the script
with so many recipients, that the char length exceeds DELIVER_OUT_BUFFER_SIZE
(default: 8192).
I did not get a SIGSEGV in my tests, but the resulting mail contained corrupted
data including non-ASCII control sequences and overwritten header names (e.g.
Subject).


I also developed a small patch to workaround the headers_rewrite bug:
--- exim-4.98.2.orig/src/transport.c
+++ exim-4.98.2/src/transport.c
@@ -770,10 +770,27 @@ for (header_line * h = header_list; h; h
       if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
                  tblock->rewrite_existflags, FALSE)))
        {
+        uschar * saved_hh_text;
+        BOOL result;
        len = hh->slen;
        if (tctx->options & topt_truncate_headers && len > 998) len = 998;
-       if (!sendfn(tctx, hh->text, len)) return FALSE;
+        /* save hh->text in newly allocated memory to allow invalidating
+           current storage location with store_reset() BEFORE calling
+           sendfn() which might allocate memory from store that MUST NOT
+           be reused after store_reset() */
+        saved_hh_text = malloc(len);
+        if (!saved_hh_text) {
+          log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of "
+                    "memory in line %d in %s", len, __LINE__, __FUNCTION__);
+          return FALSE;
+        }
+        memcpy(saved_hh_text, hh->text, len);
        store_reset(reset_point);
+        /* save the result of sendfn() to free saved_hh_text
+           before returning on failure */
+       result = sendfn(tctx, saved_hh_text, len);
+        free(saved_hh_text);
+        if (!result) return FALSE;
        continue;     /* With the next header line */
        }
       }

This patch is obviously just a workaround as it introduces a malloc() call
outside of store.c! I'm not familiar enough with exim's store management to
provide a better solution.

For the envelope_to_add bug I did not search a workaround yet as we do not use
this option (GDPR), Also a similar approach like above is not possible here
without changes in write_env_to() as well.


After using the workaround, I discovered ANOTHER BUG! It is triggered, when
such a mail is sent through an smtp transport with chunking enabled, which is
the default. I assume the headers must be larger than DELIVER_OUT_BUFFER_SIZE
but I did not have time yet to debug this in more detail. As a workaround I
temporarily disabled chunking to send out the problematic mail in my production
system.

If you need a real-world example for a mail with large headers, the German mail
provider GMX is including a header "UI-OutboundReport". This header seems to
include a very long base64-like encoded data, the length seems to be depending
on the size of the whole mail. In my case, this header alone had >4kB due to a
2MB PDF attachment. The From: header was close to the end because mailman
removed the original header and added the new one at the end. All together,
this triggered the bug in transport_headers_send() as I have set
headers_rewrite to fix the From: header for SRS compliant forwards of bounces.
If needed, I can provide an anonymized version of that mail, but only
non-public, please.

-- 
You are receiving this mail because:
You are on the CC list for the bug.

-- 
## subscription configuration (requires account):
##   https://lists.exim.org/mailman3/postorius/lists/exim-dev.lists.exim.org/
## unsubscribe (doesn't require an account):
##   [email protected]
## Exim details at http://www.exim.org/
## Please use the Wiki with this list - http://wiki.exim.org/

Reply via email to