File nofreeze-c72f740a.patch of Package mutt
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 | 9 ++++++++
mime.h | 5 ++++
parse.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++----------
3 files changed, 72 insertions(+), 11 deletions(-)
--- handler.c
+++ handler.c 2020-12-02 13:13:03.692676976 +0000
@@ -1720,8 +1720,16 @@ int mutt_body_handler (BODY *b, STATE *s
int plaintext = 0;
handler_t handler = NULL;
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 */
@@ -1836,6 +1844,7 @@ int mutt_body_handler (BODY *b, STATE *s
fputs (" --]\n", s->fpout);
}
+ recurse_level--;
s->flags = oflags | (s->flags & MUTT_FIRSTDONE);
if (rc)
{
--- mime.h
+++ mime.h 2020-12-02 13:11:27.322523653 +0000
@@ -52,6 +52,11 @@ enum
DISPNONE /* no preferred disposition */
};
+/* 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-12-08 09:03:21.076476696 +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.
@@ -481,9 +487,17 @@ 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)
{
@@ -496,9 +510,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:
@@ -506,16 +521,16 @@ 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
- return;
+ goto bail;
}
break;
default:
- return;
+ goto bail;
}
/* try to recover from parsing error */
@@ -524,6 +539,8 @@ void mutt_parse_part (FILE *fp, BODY *b)
b->type = TYPETEXT;
mutt_str_replace (&b->subtype, "plain");
}
+bail:
+ recurse_level--;
}
/* parse a MESSAGE/RFC822 body
@@ -537,7 +554,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;
@@ -555,7 +572,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);
}
@@ -572,7 +589,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;
@@ -649,6 +667,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;
}
}
}
@@ -659,11 +685,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;