[gmime] Modified GMimeParser to prevent stack overflows when parsing deeply nested messages



commit 591439e68b62c916e8cac2e45cf52fc7111c7444
Author: Jeffrey Stedfast <jestedfa microsoft com>
Date:   Wed Nov 13 16:46:26 2019 -0500

    Modified GMimeParser to prevent stack overflows when parsing deeply nested messages

 gmime/gmime-parser-options.h | 32 +++++++++++++------------
 gmime/gmime-parser.c         | 57 ++++++++++++++++++++++++++++----------------
 2 files changed, 54 insertions(+), 35 deletions(-)
---
diff --git a/gmime/gmime-parser-options.h b/gmime/gmime-parser-options.h
index 148100a2..73089759 100644
--- a/gmime/gmime-parser-options.h
+++ b/gmime/gmime-parser-options.h
@@ -43,20 +43,21 @@ typedef enum {
 
 /**
  * GMimeParserWarning:
- * @GMIME_WARN_DUPLICATED_HEADER: repeated exactly the same header which should exist only once
- * @GMIME_WARN_DUPLICATED_PARAMETER: repeated exactly the same header parameter
- * @GMIME_WARN_UNENCODED_8BIT_HEADER: a header contains unencoded 8-bit characters
- * @GMIME_WARN_INVALID_CONTENT_TYPE: invalid content type, assume `application/octet-stream`
- * @GMIME_WARN_INVALID_RFC2047_HEADER_VALUE: invalid RFC 2047 encoded header value
- * @GMIME_WARN_INVALID_PARAMETER: invalid header parameter
- * @GMIME_WARN_MALFORMED_MULTIPART: no items in a `multipart/...`
- * @GMIME_WARN_TRUNCATED_MESSAGE: the message is truncated
- * @GMIME_WARN_MALFORMED_MESSAGE: the message is malformed
- * @GMIME_WARN_INVALID_ADDRESS_LIST: invalid address list
- * @GMIME_CRIT_INVALID_HEADER_NAME: invalid header name, the parser may skip the message or parts of it
- * @GMIME_CRIT_CONFLICTING_HEADER: conflicting header
- * @GMIME_CRIT_CONFLICTING_PARAMETER: conflicting header parameter
- * @GMIME_CRIT_MULTIPART_WITHOUT_BOUNDARY: a `multipart/...` part lacks the required boundary parameter
+ * @GMIME_WARN_DUPLICATED_HEADER: Repeated exactly the same header which should exist only once.
+ * @GMIME_WARN_DUPLICATED_PARAMETER: Repeated exactly the same header parameter.
+ * @GMIME_WARN_UNENCODED_8BIT_HEADER: A header contains unencoded 8-bit characters.
+ * @GMIME_WARN_INVALID_CONTENT_TYPE: Invalid content type, assuming `application/octet-stream`.
+ * @GMIME_WARN_INVALID_RFC2047_HEADER_VALUE: Invalid RFC 2047 encoded header value.
+ * @GMIME_WARN_INVALID_PARAMETER: Invalid header parameter.
+ * @GMIME_WARN_MALFORMED_MULTIPART: No child parts detected within a multipart.
+ * @GMIME_WARN_TRUNCATED_MESSAGE: The message is truncated.
+ * @GMIME_WARN_MALFORMED_MESSAGE: The message is malformed.
+ * @GMIME_WARN_INVALID_ADDRESS_LIST: Invalid address list.
+ * @GMIME_CRIT_INVALID_HEADER_NAME: Invalid header name, the parser may skip the message or parts of it.
+ * @GMIME_CRIT_CONFLICTING_HEADER: Conflicting header.
+ * @GMIME_CRIT_CONFLICTING_PARAMETER: Conflicting header parameter.
+ * @GMIME_CRIT_MULTIPART_WITHOUT_BOUNDARY: A multipart lacks the required boundary parameter.
+ * @GMIME_CRIT_NESTING_OVERFLOW: The maximum MIME nesting level has been exceeded.
  *
  * Issues the @GMimeParser detects. Note that the `GMIME_CRIT_*` issues indicate that some parts of the 
@GMimeParser input may
  * be ignored or will be interpreted differently by other software products.
@@ -75,7 +76,8 @@ typedef enum {
        GMIME_CRIT_CONFLICTING_PARAMETER,
        GMIME_CRIT_MULTIPART_WITHOUT_BOUNDARY,
        GMIME_WARN_INVALID_PARAMETER,
-       GMIME_WARN_INVALID_ADDRESS_LIST
+       GMIME_WARN_INVALID_ADDRESS_LIST,
+       GMIME_CRIT_NESTING_OVERFLOW
 } GMimeParserWarning;
 
 /**
diff --git a/gmime/gmime-parser.c b/gmime/gmime-parser.c
index d10f5eec..fe2f5386 100644
--- a/gmime/gmime-parser.c
+++ b/gmime/gmime-parser.c
@@ -98,9 +98,9 @@ static void parser_init (GMimeParser *parser, GMimeStream *stream);
 static void parser_close (GMimeParser *parser);
 
 static GMimeObject *parser_construct_leaf_part (GMimeParser *parser, GMimeParserOptions *options, 
ContentType *content_type,
-                                               gboolean toplevel, BoundaryType *found);
+                                               gboolean toplevel, BoundaryType *found, int depth);
 static GMimeObject *parser_construct_multipart (GMimeParser *parser, GMimeParserOptions *options, 
ContentType *content_type,
-                                               gboolean toplevel, BoundaryType *found);
+                                               gboolean toplevel, BoundaryType *found, int depth);
 
 static GObjectClass *parent_class = NULL;
 
@@ -165,6 +165,7 @@ struct _GMimeParserPrivate {
        size_t headerleft;
        
        BoundaryStack *bounds;
+       guint max_level;
        
        GMimeOpenPGPState openpgp;
        short int state;
@@ -387,6 +388,7 @@ parser_init (GMimeParser *parser, GMimeStream *stream)
        priv->seekable = offset != -1;
        
        priv->bounds = NULL;
+       priv->max_level = 1024;
 }
 
 static void
@@ -1666,7 +1668,7 @@ check_repeated_header (GMimeParserOptions *options, GMimeObject *object, const H
 }
 
 static void
-parser_scan_message_part (GMimeParser *parser, GMimeParserOptions *options, GMimeMessagePart *mpart, 
BoundaryType *found)
+parser_scan_message_part (GMimeParser *parser, GMimeParserOptions *options, GMimeMessagePart *mpart, 
BoundaryType *found, int depth)
 {
        struct _GMimeParserPrivate *priv = parser->priv;
        ContentType *content_type;
@@ -1746,9 +1748,9 @@ parser_scan_message_part (GMimeParser *parser, GMimeParserOptions *options, GMim
        
        content_type = parser_content_type (parser, NULL);
        if (content_type_is_type (content_type, "multipart", "*"))
-               object = parser_construct_multipart (parser, options, content_type, TRUE, found);
+               object = parser_construct_multipart (parser, options, content_type, TRUE, found, depth + 1);
        else
-               object = parser_construct_leaf_part (parser, options, content_type, TRUE, found);
+               object = parser_construct_leaf_part (parser, options, content_type, TRUE, found, depth + 1);
        
        content_type_destroy (content_type);
        message->mime_part = object;
@@ -1772,7 +1774,7 @@ is_rfc822 (const char *subtype)
 }
 
 static GMimeObject *
-parser_construct_leaf_part (GMimeParser *parser, GMimeParserOptions *options, ContentType *content_type, 
gboolean toplevel, BoundaryType *found)
+parser_construct_leaf_part (GMimeParser *parser, GMimeParserOptions *options, ContentType *content_type, 
gboolean toplevel, BoundaryType *found, int depth)
 {
        struct _GMimeParserPrivate *priv = parser->priv;
        const char *subtype = content_type->subtype;
@@ -1786,7 +1788,16 @@ parser_construct_leaf_part (GMimeParser *parser, GMimeParserOptions *options, Co
        if (!g_ascii_strcasecmp (type, "message") && is_rfc822 (subtype)) {
                gboolean is_encoded = FALSE;
                
-               for (i = 0; i < priv->headers->len; i++) {
+               if (depth >= priv->max_level) {
+                       /* The maximum MIME nesting level has been exceeded. Treat this message/rfc822
+                        * part as if it was a leaf-node MIME part (i.e. don't recursively parse the
+                        * message content). */
+                       _g_mime_parser_options_warn (options, priv->headers_begin, 
GMIME_CRIT_NESTING_OVERFLOW, NULL);
+                       w(g_warning ("maximum nesting level exceeded"));
+                       is_encoded = TRUE;
+               }
+               
+               for (i = 0; i < priv->headers->len && !is_encoded; i++) {
                        header = priv->headers->pdata[i];
                        
                        if (g_ascii_strcasecmp (header->name, "Content-Transfer-Encoding") != 0)
@@ -1842,7 +1853,7 @@ parser_construct_leaf_part (GMimeParser *parser, GMimeParserOptions *options, Co
        }
        
        if (GMIME_IS_MESSAGE_PART (object))
-               parser_scan_message_part (parser, options, (GMimeMessagePart *) object, found);
+               parser_scan_message_part (parser, options, (GMimeMessagePart *) object, found, depth + 1);
        else
                parser_scan_mime_part_content (parser, (GMimePart *) object, found);
 
@@ -1910,7 +1921,7 @@ parser_scan_multipart_face (GMimeParser *parser, GMimeMultipart *multipart, gboo
 #define parser_scan_multipart_epilogue(parser, multipart) parser_scan_multipart_face (parser, multipart, 
FALSE)
 
 static BoundaryType
-parser_scan_multipart_subparts (GMimeParser *parser, GMimeParserOptions *options, GMimeMultipart *multipart)
+parser_scan_multipart_subparts (GMimeParser *parser, GMimeParserOptions *options, GMimeMultipart *multipart, 
int depth)
 {
        struct _GMimeParserPrivate *priv = parser->priv;
        ContentType *content_type;
@@ -1938,9 +1949,9 @@ parser_scan_multipart_subparts (GMimeParser *parser, GMimeParserOptions *options
                
                content_type = parser_content_type (parser, ((GMimeObject *) multipart)->content_type);
                if (content_type_is_type (content_type, "multipart", "*"))
-                       subpart = parser_construct_multipart (parser, options, content_type, FALSE, &found);
+                       subpart = parser_construct_multipart (parser, options, content_type, FALSE, &found, 
depth + 1);
                else
-                       subpart = parser_construct_leaf_part (parser, options, content_type, FALSE, &found);
+                       subpart = parser_construct_leaf_part (parser, options, content_type, FALSE, &found, 
depth + 1);
                
                g_mime_multipart_add (multipart, subpart);
                content_type_destroy (content_type);
@@ -1951,7 +1962,7 @@ parser_scan_multipart_subparts (GMimeParser *parser, GMimeParserOptions *options
 }
 
 static GMimeObject *
-parser_construct_multipart (GMimeParser *parser, GMimeParserOptions *options, ContentType *content_type, 
gboolean toplevel, BoundaryType *found)
+parser_construct_multipart (GMimeParser *parser, GMimeParserOptions *options, ContentType *content_type, 
gboolean toplevel, BoundaryType *found, int depth)
 {
        struct _GMimeParserPrivate *priv = parser->priv;
        GMimeMultipart *multipart;
@@ -1991,13 +2002,13 @@ parser_construct_multipart (GMimeParser *parser, GMimeParserOptions *options, Co
                }
        }
        
-       if ((boundary = g_mime_object_get_content_type_parameter (object, "boundary"))) {
+       if ((boundary = g_mime_object_get_content_type_parameter (object, "boundary")) && depth < 
priv->max_level) {
                parser_push_boundary (parser, boundary);
                
                *found = parser_scan_multipart_prologue (parser, multipart);
                
                if (*found == BOUNDARY_IMMEDIATE)
-                       *found = parser_scan_multipart_subparts (parser, options, multipart);
+                       *found = parser_scan_multipart_subparts (parser, options, multipart, depth);
                
                if (*found == BOUNDARY_IMMEDIATE_END) {
                        /* eat end boundary */
@@ -2022,8 +2033,14 @@ parser_construct_multipart (GMimeParser *parser, GMimeParserOptions *options, Co
                else if (*found == BOUNDARY_PARENT && found_immediate_boundary (priv, FALSE))
                        *found = BOUNDARY_IMMEDIATE;
        } else {
-               _g_mime_parser_options_warn (options, ctype_offset, GMIME_CRIT_MULTIPART_WITHOUT_BOUNDARY, 
content_type->subtype);
-               w(g_warning ("multipart without boundary encountered"));
+               if (depth >= priv->max_level) {
+                       _g_mime_parser_options_warn (options, priv->headers_begin, 
GMIME_CRIT_NESTING_OVERFLOW, NULL);
+                       w(g_warning ("maximum nesting level exceeded @ boundary = %s", boundary));
+               } else {
+                       _g_mime_parser_options_warn (options, ctype_offset, 
GMIME_CRIT_MULTIPART_WITHOUT_BOUNDARY, content_type->subtype);
+                       w(g_warning ("multipart without boundary encountered"));
+               }
+               
                /* this will scan everything into the prologue */
                *found = parser_scan_multipart_prologue (parser, multipart);
        }
@@ -2048,9 +2065,9 @@ parser_construct_part (GMimeParser *parser, GMimeParserOptions *options)
        
        content_type = parser_content_type (parser, NULL);
        if (content_type_is_type (content_type, "multipart", "*"))
-               object = parser_construct_multipart (parser, options, content_type, FALSE, &found);
+               object = parser_construct_multipart (parser, options, content_type, FALSE, &found, 0);
        else
-               object = parser_construct_leaf_part (parser, options, content_type, FALSE, &found);
+               object = parser_construct_leaf_part (parser, options, content_type, FALSE, &found, 0);
        
        content_type_destroy (content_type);
        
@@ -2140,9 +2157,9 @@ parser_construct_message (GMimeParser *parser, GMimeParserOptions *options)
        
        content_type = parser_content_type (parser, NULL);
        if (content_type_is_type (content_type, "multipart", "*"))
-               object = parser_construct_multipart (parser, options, content_type, TRUE, &found);
+               object = parser_construct_multipart (parser, options, content_type, TRUE, &found, 0);
        else
-               object = parser_construct_leaf_part (parser, options, content_type, TRUE, &found);
+               object = parser_construct_leaf_part (parser, options, content_type, TRUE, &found, 0);
        
        content_type_destroy (content_type);
        message->mime_part = object;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]