]> dgit.raspbian.org Git - dovecot.git/commitdiff
[PATCH 13/14] lib-mail: Limit total Content-Language tag count per message to 100 000
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Fri, 17 Apr 2026 13:56:37 +0000 (15:56 +0200)
committerNoah Meyerhans <noahm@debian.org>
Wed, 16 Sep 2026 19:06:35 +0000 (15:06 -0400)
A multipart message with many MIME parts each containing many language tags
can exhaust memory: the per-part RFC 2231 parser had no cumulative limit.

Add remaining_language_tags to struct message_part_data_limits, initialised
to MESSAGE_PART_DATA_MAX_TOTAL_LANGUAGE_TAGS (100 000).  Break out of the
tag-parsing loop in parse_content_language() once the budget reaches zero.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gbp-Pq: Name 0013-lib-mail-Limit-total-Content-Language-tag-count-per-.patch

src/lib-mail/message-part-data.c
src/lib-mail/message-part-data.h

index 24e1a319645dc2c5af05ab6de8f606c5320417b4..2eed994e45ca29154ebae15282e93ff2c3e86dc4 100644 (file)
@@ -380,7 +380,8 @@ parse_content_disposition(struct message_part_data *data,
 
 static void
 parse_content_language(struct message_part_data *data,
-       pool_t pool, const unsigned char *value, size_t value_len)
+       pool_t pool, const unsigned char *value, size_t value_len,
+       struct message_part_data_limits *limits)
 {
        struct rfc822_parser_context parser;
        ARRAY_TYPE(const_string) langs;
@@ -398,8 +399,11 @@ parse_content_language(struct message_part_data *data,
 
        rfc822_skip_lwsp(&parser);
        while (rfc822_parse_atom(&parser, str) >= 0) {
-               const char *lang = p_strdup(pool, str_c(str));
+               if (limits->remaining_language_tags == 0)
+                       break;
+               limits->remaining_language_tags--;
 
+               const char *lang = p_strdup(pool, str_c(str));
                array_push_back(&langs, &lang);
                str_truncate(str, 0);
 
@@ -419,7 +423,8 @@ parse_content_language(struct message_part_data *data,
 
 static void
 parse_content_header(struct message_part_data *data,
-       pool_t pool, struct message_header_line *hdr)
+       pool_t pool, struct message_header_line *hdr,
+       struct message_part_data_limits *limits)
 {
        const char *name = hdr->name + strlen("Content-");
 
@@ -459,7 +464,7 @@ parse_content_header(struct message_part_data *data,
                if (strcasecmp(name, "Language") == 0 &&
                    data->content_language == NULL) {
                        parse_content_language(data, pool,
-                               hdr->full_value, hdr->full_value_len);
+                               hdr->full_value, hdr->full_value_len, limits);
                } else if (strcasecmp(name, "Location") == 0 &&
                           data->content_location == NULL) {
                        data->content_location =
@@ -524,7 +529,7 @@ void message_part_data_parse_from_header(pool_t pool,
 
        if (str_begins_icase_with(hdr->name, "Content-")) {
                T_BEGIN {
-                       parse_content_header(part_data, pool, hdr);
+                       parse_content_header(part_data, pool, hdr, limits);
                } T_END;
        }
 
index af09ea715e85cbe13892094d186440934610d274..7f1f9c3e1fa558f10a1dae9bc4fc57103cc0814e 100644 (file)
@@ -9,13 +9,16 @@
 struct message_header_line;
 
 #define MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES 100000
+#define MESSAGE_PART_DATA_MAX_TOTAL_LANGUAGE_TAGS 100000
 
 struct message_part_data_limits {
        unsigned int remaining_addresses;
+       unsigned int remaining_language_tags;
 };
 
 #define MESSAGE_PART_DATA_LIMITS_INIT \
-       { MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES }
+       { MESSAGE_PART_DATA_MAX_TOTAL_ADDRESSES, \
+         MESSAGE_PART_DATA_MAX_TOTAL_LANGUAGE_TAGS }
 
 struct message_part_param {
        const char *name;