From c72f740aa7c80c0a79775628e62daa2a43357cd5 Mon Sep 17 00:00:00 2001
From: Kevin McCarthy <kevin@8t8.us>
Date: Tue, 19 May 2020 12:26:55 -0700
Subject: [PATCH] Add mitigation against DoS from thousands of parts.

A demonstration attack using a million tiny parts will freeze Mutt for
several minutes.  This is actually better than some other mail
software, but can still be a problem at large levels.

For now, set it to a very conservative 5000, but this can be adjusted
up (or down) if necessary.

Declare the previous stack-limit max depth as a constant too, and
decrease it down to 50.  Change the handler to return non-fatal "1" on
reaching the limit.
---
 handler.c |    8 +++++++
 mime.h    |    5 ++++
 parse.c   |   64 +++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 3 files changed, 68 insertions(+), 9 deletions(-)

--- handler.c
+++ handler.c	2020-11-25 07:48:40.036066951 +0000
@@ -1491,8 +1491,16 @@ int mutt_body_handler (BODY *b, STATE *s
   size_t tmplength = 0;
   char type[STRING];
   int rc = 0;
+  static unsigned short recurse_level = 0;
 
   int oflags = s->flags;
+
+  if (recurse_level >= MUTT_MIME_MAX_DEPTH)
+  {
+    dprint (1, (debugfile, "mutt_body_handler: recurse level too deep. giving up!\n"));
+    return 1;
+  }
+  recurse_level++;
   
   /* first determine which handler to use to process this part */
 
--- mime.h
+++ mime.h	2020-11-24 08:35:52.314768183 +0000
@@ -51,6 +51,11 @@ enum
   DISPFORMDATA
 };
 
+/* Some limits to mitigate stack overflow and denial of service attacks */
+#define MUTT_MIME_MAX_DEPTH  50
+#define MUTT_MIME_MAX_PARTS  5000
+
+
 /* MIME encoding/decoding global vars */
 
 #ifndef _SENDLIB_C
--- parse.c
+++ parse.c	2020-11-25 07:56:29.823084601 +0000
@@ -34,6 +34,12 @@
 #include <sys/stat.h>
 #include <stdlib.h>
 
+static void _parse_part (FILE *fp, BODY *b, int *counter);
+static BODY *_parse_messageRFC822 (FILE *fp, BODY *parent, int *counter);
+static BODY *_parse_multipart (FILE *fp, const char *boundary, LOFF_T end_off,
+                               int digest, int *counter);
+
+
 /* Reads an arbitrarily long header field, and looks ahead for continuation
  * lines.  ``line'' must point to a dynamically allocated string; it is
  * increased if more space is required to fit the whole line.
@@ -515,9 +521,18 @@ BODY *mutt_read_mime_header (FILE *fp, i
   return (p);
 }
 
-void mutt_parse_part (FILE *fp, BODY *b)
+static void _parse_part (FILE *fp, BODY *b, int *counter)
 {
   char *bound = 0;
+  static unsigned short recurse_level = 0;
+
+  if (recurse_level >= MUTT_MIME_MAX_DEPTH)
+  {
+    dprint (1, (debugfile, "mutt_parse_part(): recurse level too deep. giving up!\n"));
+    return;
+  }
+  recurse_level++;
+
 
   switch (b->type)
   {
@@ -530,9 +545,10 @@ void mutt_parse_part (FILE *fp, BODY *b)
           bound = mutt_get_parameter ("boundary", b->parameter);
 
       fseeko (fp, b->offset, SEEK_SET);
-      b->parts =  mutt_parse_multipart (fp, bound, 
-					b->offset + b->length,
-					ascii_strcasecmp ("digest", b->subtype) == 0);
+      b->parts =  _parse_multipart (fp, bound, 
+				    b->offset + b->length,
+				    ascii_strcasecmp ("digest", b->subtype) == 0,
+				    counter);
       break;
 
     case TYPEMESSAGE:
@@ -540,7 +556,7 @@ void mutt_parse_part (FILE *fp, BODY *b)
       {
 	fseeko (fp, b->offset, SEEK_SET);
 	if (mutt_is_message_type(b->type, b->subtype))
-	  b->parts = mutt_parse_messageRFC822 (fp, b);
+	  b->parts = _parse_messageRFC822 (fp, b, counter);
 	else if (ascii_strcasecmp (b->subtype, "external-body") == 0)
 	  b->parts = mutt_read_mime_header (fp, 0);
 	else
@@ -571,7 +587,7 @@ void mutt_parse_part (FILE *fp, BODY *b)
  * NOTE: this assumes that `parent->length' has been set!
  */
 
-BODY *mutt_parse_messageRFC822 (FILE *fp, BODY *parent)
+static BODY *_parse_messageRFC822 (FILE *fp, BODY *parent, int *counter)
 {
   BODY *msg;
 
@@ -589,7 +605,7 @@ BODY *mutt_parse_messageRFC822 (FILE *fp
   if (msg->length < 0)
     msg->length = 0;
 
-  mutt_parse_part(fp, msg);
+  _parse_part(fp, msg, counter);
   return (msg);
 }
 
@@ -606,7 +622,8 @@ BODY *mutt_parse_messageRFC822 (FILE *fp
  *	digest		1 if reading a multipart/digest, 0 otherwise
  */
 
-BODY *mutt_parse_multipart (FILE *fp, const char *boundary, LOFF_T end_off, int digest)
+static BODY *_parse_multipart (FILE *fp, const char *boundary, LOFF_T end_off,
+                               int digest, int *counter)
 {
 #ifdef SUN_ATTACHMENT
   int lines;
@@ -683,6 +700,14 @@ BODY *mutt_parse_multipart (FILE *fp, co
 	}
 	else
 	  last = head = new;
+
+        /* It seems more intuitive to add the counter increment to
+         * _parse_part(), but we want to stop the case where a multipart
+         * contains thousands of tiny parts before the memory and data
+         * structures are allocated.
+         */
+        if (++(*counter) >= MUTT_MIME_MAX_PARTS)
+          break;
       }
     }
   }
@@ -693,11 +718,32 @@ BODY *mutt_parse_multipart (FILE *fp, co
 
   /* parse recursive MIME parts */
   for(last = head; last; last = last->next)
-    mutt_parse_part(fp, last);
+    _parse_part(fp, last, counter);
   
   return (head);
 }
 
+void mutt_parse_part (FILE *fp, BODY *b)
+{
+  int counter = 0;
+
+  _parse_part (fp, b, &counter);
+}
+
+BODY *mutt_parse_messageRFC822 (FILE *fp, BODY *parent)
+{
+  int counter = 0;
+
+  return _parse_messageRFC822 (fp, parent, &counter);
+}
+
+BODY *mutt_parse_multipart (FILE *fp, const char *boundary, LOFF_T end_off, int digest)
+{
+  int counter = 0;
+
+  return _parse_multipart (fp, boundary, end_off, digest, &counter);
+}
+
 static const char *uncomment_timezone (char *buf, size_t buflen, const char *tz)
 {
   char *p;
