From a10cb3eafeddede34d173590ca1abbde58d785f2 Mon Sep 17 00:00:00 2001
From: Apollon Oikonomopoulos <apoikos@debian.org>
Date: Thu, 1 Mar 2018 12:20:17 +0200
Subject: [PATCH 1/3] Fix CVE-2017-14461: rfc822_parse_domain information leak
 vulnerability

commit 76dccf7477cf85a3d41033ef8a4bd3904d27e8ef
Author: Timo Sirainen <timo.sirainen@dovecot.fi>
Date:   Fri Dec 22 18:58:11 2017 +0200

    global: Call rfc822_parser_deinit() wherever possible

    (cherry picked from commit 0ed696987e5e5d44e971da2a10f6275b276ece34)

commit 4fa4af7db6f3a0967359663e235f4c42e9590cac
Author: Timo Sirainen <timo.sirainen@dovecot.fi>
Date:   Tue Jan 9 11:33:59 2018 -0500

    lib-mail: Refactor code to make the next commit smaller

    (cherry picked from commit 18a7a161c8dae6f630770a3cbab7374a0c3dd732)

commit d456bddaa1f5beaab12bac0d0908c6ed57e59e2d
Author: Timo Sirainen <timo.sirainen@dovecot.fi>
Date:   Fri Dec 22 18:56:53 2017 +0200

    lib-mail: Add rfc822_parser_deinit()

    It's not a strict requirement to call this, but it assert-crashes if the
    state isn't valid.

    (cherry picked from commit f5cd17a27f0b666567747f8c921ebe1026970f11)

commit b0bb68ab388b3ba68a6853d154de5174d520e473
Author: Timo Sirainen <timo.sirainen@dovecot.fi>
Date:   Fri Dec 22 18:42:53 2017 +0200

    lib-mail: Make sure parsers don't accidentally go much beyond end pointer

    (cherry picked from commit e9b86842441a668b30796bff7d60828614570a1b)

commit ba9f19d434306da006d4cfad5a514fb2eef92c63
Author: Timo Sirainen <timo.sirainen@dovecot.fi>
Date:   Fri Dec 22 18:36:55 2017 +0200

    lib-mail: Fix out-of-bounds read when parsing an invalid email address

    The included unit test doesn't fail, but running it with valgrind shows
    "Invalid read of size 1" error.

    Broken in d6737a17a27402e7a262f7ba8a2ed588d576f23c

    Discovered by Aleksandar Nikolic of Cisco Talos

    (cherry picked from commit b72d864b8c34cb21076214c0b28101baec530141)

commit f757fae9b793663775967f21c55edddb27ec470b
Author: Timo Sirainen <timo.sirainen@dovecot.fi>
Date:   Fri Dec 22 18:31:52 2017 +0200

    lib-mail: rfc822-parser - Add asserts to make sure parser state is correct

    (cherry picked from commit 30dc856f7b97b75b0e0d69f5003d5d99a13249b4)
---
 src/lib-imap/imap-bodystructure.c           | 11 ++++++--
 src/lib-mail/istream-attachment-extractor.c |  1 +
 src/lib-mail/message-address.c              | 14 +++++-----
 src/lib-mail/message-date.c                 |  5 ++--
 src/lib-mail/message-decoder.c              |  6 ++++-
 src/lib-mail/message-id.c                   | 31 ++++++++++------------
 src/lib-mail/message-parser.c               | 10 ++++---
 src/lib-mail/message-search.c               |  1 +
 src/lib-mail/rfc2231-parser.c               |  2 +-
 src/lib-mail/rfc822-parser.c                | 41 ++++++++++++++++-------------
 src/lib-mail/rfc822-parser.h                |  8 ++++++
 src/lib-mail/test-rfc2231-parser.c          |  1 +
 src/lib-mail/test-rfc822-parser.c           |  2 ++
 src/plugins/fts/fts-build-mail.c            |  1 +
 src/plugins/fts/fts-parser-script.c         |  5 +++-
 15 files changed, 86 insertions(+), 53 deletions(-)

diff --git a/src/lib-imap/imap-bodystructure.c b/src/lib-imap/imap-bodystructure.c
index a379a7548..bd96641eb 100644
--- a/src/lib-imap/imap-bodystructure.c
+++ b/src/lib-imap/imap-bodystructure.c
@@ -63,6 +63,7 @@ static void parse_content_type(struct message_part_body_data *data,
 		   We don't completely ignore a broken Content-Type, because
 		   then it would be written as text/plain. This would cause a
 		   mismatch with the message_part's MESSAGE_PART_FLAG_TEXT. */
+		rfc822_parser_deinit(&parser);
 		return;
 	}
 
@@ -89,6 +90,7 @@ static void parse_content_type(struct message_part_body_data *data,
 		data->content_type_params =
 			p_strdup(data->pool, str_c(str) + 1);
 	}
+	rfc822_parser_deinit(&parser);
 }
 
 static void parse_content_transfer_encoding(struct message_part_body_data *data,
@@ -106,6 +108,7 @@ static void parse_content_transfer_encoding(struct message_part_body_data *data,
 		data->content_transfer_encoding =
 			imap_get_string(data->pool, str_c(str));
 	}
+	rfc822_parser_deinit(&parser);
 }
 
 static void parse_content_disposition(struct message_part_body_data *data,
@@ -119,8 +122,10 @@ static void parse_content_disposition(struct message_part_body_data *data,
 	rfc822_skip_lwsp(&parser);
 
 	str = t_str_new(256);
-	if (rfc822_parse_mime_token(&parser, str) < 0)
+	if (rfc822_parse_mime_token(&parser, str) < 0) {
+		rfc822_parser_deinit(&parser);
 		return;
+	}
 	data->content_disposition = imap_get_string(data->pool, str_c(str));
 
 	/* parse parameters and save them */
@@ -136,6 +141,7 @@ static void parse_content_disposition(struct message_part_body_data *data,
 		data->content_disposition_params =
 			p_strdup(data->pool, str_c(str) + 1);
 	}
+	rfc822_parser_deinit(&parser);
 }
 
 static void parse_content_language(const unsigned char *value, size_t value_len,
@@ -158,12 +164,13 @@ static void parse_content_language(const unsigned char *value, size_t value_len,
 	while (rfc822_parse_atom(&parser, str) >= 0) {
 		str_append(str, "\" \"");
 
-		if (parser.data == parser.end || *parser.data != ',')
+		if (parser.data >= parser.end || *parser.data != ',')
 			break;
 		parser.data++;
 		rfc822_skip_lwsp(&parser);
 	}
 
+	rfc822_parser_deinit(&parser);
 	if (str_len(str) > 1) {
 		str_truncate(str, str_len(str) - 2);
 		data->content_language = p_strdup(data->pool, str_c(str));
diff --git a/src/lib-mail/istream-attachment-extractor.c b/src/lib-mail/istream-attachment-extractor.c
index 27b051ea2..4f1e0f25c 100644
--- a/src/lib-mail/istream-attachment-extractor.c
+++ b/src/lib-mail/istream-attachment-extractor.c
@@ -88,6 +88,7 @@ static void parse_content_type(struct attachment_istream *astream,
 		(void)rfc822_parse_content_type(&parser, content_type);
 		astream->part.content_type = i_strdup(str_c(content_type));
 	} T_END;
+	rfc822_parser_deinit(&parser);
 }
 
 static void
diff --git a/src/lib-mail/message-address.c b/src/lib-mail/message-address.c
index 4f1a31f1e..c0273e4b3 100644
--- a/src/lib-mail/message-address.c
+++ b/src/lib-mail/message-address.c
@@ -40,7 +40,7 @@ static int parse_local_part(struct message_address_parser_context *ctx)
 	   local-part      = dot-atom / quoted-string / obs-local-part
 	   obs-local-part  = word *("." word)
 	*/
-	i_assert(ctx->parser.data != ctx->parser.end);
+	i_assert(ctx->parser.data < ctx->parser.end);
 
 	str_truncate(ctx->str, 0);
 	if (*ctx->parser.data == '"')
@@ -73,7 +73,7 @@ static int parse_domain_list(struct message_address_parser_context *ctx)
 	/* obs-domain-list = "@" domain *(*(CFWS / "," ) [CFWS] "@" domain) */
 	str_truncate(ctx->str, 0);
 	for (;;) {
-		if (ctx->parser.data == ctx->parser.end)
+		if (ctx->parser.data >= ctx->parser.end)
 			return 0;
 
 		if (*ctx->parser.data != '@')
@@ -150,7 +150,7 @@ static int parse_name_addr(struct message_address_parser_context *ctx)
 		ctx->addr.domain = "SYNTAX_ERROR";
 		ctx->addr.invalid_syntax = TRUE;
 	}
-	return ctx->parser.data != ctx->parser.end;
+	return ctx->parser.data < ctx->parser.end ? 1 : 0;
 }
 
 static int parse_addr_spec(struct message_address_parser_context *ctx)
@@ -161,7 +161,8 @@ static int parse_addr_spec(struct message_address_parser_context *ctx)
 	str_truncate(ctx->parser.last_comment, 0);
 
 	ret = parse_local_part(ctx);
-	if (ret != 0 && *ctx->parser.data == '@') {
+	if (ret != 0 && ctx->parser.data < ctx->parser.end &&
+	    *ctx->parser.data == '@') {
 		ret2 = parse_domain(ctx);
 		if (ret2 <= 0)
 			ret = ret2;
@@ -316,9 +317,10 @@ message_address_parse_real(pool_t pool, const unsigned char *data, size_t size,
 
 	if (rfc822_skip_lwsp(&ctx.parser) <= 0) {
 		/* no addresses */
-		return NULL;
+	} else {
+		(void)parse_address_list(&ctx, max_addresses);
 	}
-	(void)parse_address_list(&ctx, max_addresses);
+	rfc822_parser_deinit(&ctx.parser);
 	return ctx.first_addr;
 }
 
diff --git a/src/lib-mail/message-date.c b/src/lib-mail/message-date.c
index ab52e7832..18815ac7f 100644
--- a/src/lib-mail/message-date.c
+++ b/src/lib-mail/message-date.c
@@ -103,7 +103,7 @@ static int next_token(struct message_date_parser_context *ctx,
 	int ret;
 
 	str_truncate(ctx->str, 0);
-	ret = ctx->parser.data == ctx->parser.end ? 0 :
+	ret = ctx->parser.data >= ctx->parser.end ? 0 :
 		rfc822_parse_atom(&ctx->parser, ctx->str);
 
 	*value = str_data(ctx->str);
@@ -205,7 +205,7 @@ message_date_parser_tokens(struct message_date_parser_context *ctx,
 	tm.tm_min = (value[0]-'0') * 10 + (value[1]-'0');
 
 	/* [:ss] */
-	if (ctx->parser.data != ctx->parser.end &&
+	if (ctx->parser.data < ctx->parser.end &&
 	    IS_TIME_SEP(*ctx->parser.data)) {
 		ctx->parser.data++;
 		rfc822_skip_lwsp(&ctx->parser);
@@ -252,6 +252,7 @@ bool message_date_parse(const unsigned char *data, size_t size,
 		ctx.str = t_str_new(128);
 		success = message_date_parser_tokens(&ctx, timestamp_r,
 						     timezone_offset_r);
+		rfc822_parser_deinit(&ctx.parser);
 	} T_END;
 
 	return success;
diff --git a/src/lib-mail/message-decoder.c b/src/lib-mail/message-decoder.c
index 80c532ee2..5da484c19 100644
--- a/src/lib-mail/message-decoder.c
+++ b/src/lib-mail/message-decoder.c
@@ -117,6 +117,7 @@ enum message_cte message_decoder_parse_cte(struct message_header_line *hdr)
 			message_cte = MESSAGE_CTE_QP;
 		break;
 	}
+	rfc822_parser_deinit(&parser);
 	return message_cte;
 }
 
@@ -137,8 +138,10 @@ parse_content_type(struct message_decoder_context *ctx,
 	str = t_str_new(64);
 	ret = rfc822_parse_content_type(&parser, str);
 	ctx->content_type = i_strdup(str_c(str));
-	if (ret < 0)
+	if (ret < 0) {
+		rfc822_parser_deinit(&parser);
 		return;
+	}
 
 	rfc2231_parse(&parser, &results);
 	for (; *results != NULL; results += 2) {
@@ -147,6 +150,7 @@ parse_content_type(struct message_decoder_context *ctx,
 			break;
 		}
 	}
+	rfc822_parser_deinit(&parser);
 }
 
 static bool message_decode_header(struct message_decoder_context *ctx,
diff --git a/src/lib-mail/message-id.c b/src/lib-mail/message-id.c
index 545f6c711..2d83e4cfd 100644
--- a/src/lib-mail/message-id.c
+++ b/src/lib-mail/message-id.c
@@ -9,6 +9,7 @@ static bool get_untokenized_msgid(const char **msgid_p, string_t *msgid)
 {
 	struct rfc822_parser_context parser;
 	int ret;
+	bool success = FALSE;
 
 	rfc822_parser_init(&parser, (const unsigned char *)*msgid_p,
 			   strlen(*msgid_p), NULL);
@@ -27,23 +28,19 @@ static bool get_untokenized_msgid(const char **msgid_p, string_t *msgid)
 		ret = rfc822_parse_quoted_string(&parser, msgid);
 	else
 		ret = rfc822_parse_dot_atom(&parser, msgid);
-	if (ret <= 0)
-		return FALSE;
-
-	if (*parser.data != '@')
-		return FALSE;
-	str_append_c(msgid, '@');
-	parser.data++;
-	rfc822_skip_lwsp(&parser);
-
-	if (rfc822_parse_dot_atom(&parser, msgid) <= 0)
-		return FALSE;
-
-	if (*parser.data != '>')
-		return FALSE;
-
-	*msgid_p = (const char *)parser.data + 1;
-	return TRUE;
+	if (ret > 0 && *parser.data == '@') {
+		str_append_c(msgid, '@');
+		parser.data++;
+		rfc822_skip_lwsp(&parser);
+
+		if (rfc822_parse_dot_atom(&parser, msgid) > 0 &&
+		    *parser.data == '>') {
+			*msgid_p = (const char *)parser.data + 1;
+			success = TRUE;
+		}
+	}
+	rfc822_parser_deinit(&parser);
+	return success;
 }
 
 static void strip_lwsp(char *str)
diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c
index 1ab18c7d9..a86ac5120 100644
--- a/src/lib-mail/message-parser.c
+++ b/src/lib-mail/message-parser.c
@@ -499,11 +499,12 @@ static void parse_content_type(struct message_parser_ctx *ctx,
 			ctx->part->flags |= MESSAGE_PART_FLAG_MULTIPART_DIGEST;
 	}
 
-	if (ret < 0)
-		return;
-	if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 ||
-	    ctx->last_boundary != NULL)
+	if (ret < 0 ||
+	    (ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 ||
+	    ctx->last_boundary != NULL) {
+		rfc822_parser_deinit(&parser);
 		return;
+	}
 
 	rfc2231_parse(&parser, &results);
 	for (; *results != NULL; results += 2) {
@@ -513,6 +514,7 @@ static void parse_content_type(struct message_parser_ctx *ctx,
 			break;
 		}
 	}
+	rfc822_parser_deinit(&parser);
 }
 
 static bool block_is_at_eoh(const struct message_block *block)
diff --git a/src/lib-mail/message-search.c b/src/lib-mail/message-search.c
index 45c85e4c3..f7a7481a5 100644
--- a/src/lib-mail/message-search.c
+++ b/src/lib-mail/message-search.c
@@ -61,6 +61,7 @@ static void parse_content_type(struct message_search_context *ctx,
 	ctx->content_type_text =
 		strncasecmp(str_c(content_type), "text/", 5) == 0 ||
 		strncasecmp(str_c(content_type), "message/", 8) == 0;
+	rfc822_parser_deinit(&parser);
 }
 
 static void handle_header(struct message_search_context *ctx,
diff --git a/src/lib-mail/rfc2231-parser.c b/src/lib-mail/rfc2231-parser.c
index 7d9d246b4..95e7c734f 100644
--- a/src/lib-mail/rfc2231-parser.c
+++ b/src/lib-mail/rfc2231-parser.c
@@ -59,7 +59,7 @@ int rfc2231_parse(struct rfc822_parser_context *ctx,
 		if (ret < 0) {
 			/* try to continue anyway.. */
 			broken = TRUE;
-			if (ctx->data == ctx->end)
+			if (ctx->data >= ctx->end)
 				break;
 			ctx->data++;
 			continue;
diff --git a/src/lib-mail/rfc822-parser.c b/src/lib-mail/rfc822-parser.c
index 3bd070d70..6b4ef1578 100644
--- a/src/lib-mail/rfc822-parser.c
+++ b/src/lib-mail/rfc822-parser.c
@@ -72,7 +72,7 @@ int rfc822_skip_comment(struct rfc822_parser_context *ctx)
 		str_truncate(ctx->last_comment, 0);
 
 	start = ++ctx->data;
-	for (; ctx->data != ctx->end; ctx->data++) {
+	for (; ctx->data < ctx->end; ctx->data++) {
 		switch (*ctx->data) {
 		case '(':
 			level++;
@@ -84,7 +84,7 @@ int rfc822_skip_comment(struct rfc822_parser_context *ctx)
 						     ctx->data - start);
 				}
 				ctx->data++;
-				return ctx->data != ctx->end;
+				return ctx->data < ctx->end ? 1 : 0;
 			}
 			break;
 		case '\\':
@@ -95,7 +95,7 @@ int rfc822_skip_comment(struct rfc822_parser_context *ctx)
 			start = ctx->data + 1;
 
 			ctx->data++;
-			if (ctx->data == ctx->end)
+			if (ctx->data >= ctx->end)
 				return -1;
 			break;
 		}
@@ -107,7 +107,7 @@ int rfc822_skip_comment(struct rfc822_parser_context *ctx)
 
 int rfc822_skip_lwsp(struct rfc822_parser_context *ctx)
 {
-	for (; ctx->data != ctx->end;) {
+	for (; ctx->data < ctx->end;) {
 		if (*ctx->data == ' ' || *ctx->data == '\t' ||
 		    *ctx->data == '\r' || *ctx->data == '\n') {
                         ctx->data++;
@@ -120,7 +120,7 @@ int rfc822_skip_lwsp(struct rfc822_parser_context *ctx)
 		if (rfc822_skip_comment(ctx) < 0)
 			return -1;
 	}
-	return ctx->data != ctx->end;
+	return ctx->data < ctx->end ? 1 : 0;
 }
 
 int rfc822_parse_atom(struct rfc822_parser_context *ctx, string_t *str)
@@ -132,10 +132,10 @@ int rfc822_parse_atom(struct rfc822_parser_context *ctx, string_t *str)
 	   atext           =
 	     ; Any character except controls, SP, and specials.
 	*/
-	if (ctx->data == ctx->end || !IS_ATEXT(*ctx->data))
+	if (ctx->data >= ctx->end || !IS_ATEXT(*ctx->data))
 		return -1;
 
-	for (start = ctx->data++; ctx->data != ctx->end; ctx->data++) {
+	for (start = ctx->data++; ctx->data < ctx->end; ctx->data++) {
 		if (IS_ATEXT(*ctx->data))
 			continue;
 
@@ -161,10 +161,10 @@ int rfc822_parse_dot_atom(struct rfc822_parser_context *ctx, string_t *str)
 
 	   For RFC-822 compatibility allow LWSP around '.'
 	*/
-	if (ctx->data == ctx->end || !IS_ATEXT(*ctx->data))
+	if (ctx->data >= ctx->end || !IS_ATEXT(*ctx->data))
 		return -1;
 
-	for (start = ctx->data++; ctx->data != ctx->end; ) {
+	for (start = ctx->data++; ctx->data < ctx->end; ) {
 		if (IS_ATEXT(*ctx->data)) {
 			ctx->data++;
 			continue;
@@ -194,7 +194,7 @@ int rfc822_parse_mime_token(struct rfc822_parser_context *ctx, string_t *str)
 {
 	const unsigned char *start;
 
-	for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
+	for (start = ctx->data; ctx->data < ctx->end; ctx->data++) {
 		if (IS_ATEXT_NON_TSPECIAL(*ctx->data) || *ctx->data == '.')
 			continue;
 
@@ -211,10 +211,11 @@ int rfc822_parse_quoted_string(struct rfc822_parser_context *ctx, string_t *str)
 	const unsigned char *start;
 	size_t len;
 
+	i_assert(ctx->data < ctx->end);
 	i_assert(*ctx->data == '"');
 	ctx->data++;
 
-	for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
+	for (start = ctx->data; ctx->data < ctx->end; ctx->data++) {
 		switch (*ctx->data) {
 		case '"':
 			str_append_n(str, start, ctx->data - start);
@@ -230,7 +231,7 @@ int rfc822_parse_quoted_string(struct rfc822_parser_context *ctx, string_t *str)
 			break;
 		case '\\':
 			ctx->data++;
-			if (ctx->data == ctx->end)
+			if (ctx->data >= ctx->end)
 				return -1;
 
 			str_append_n(str, start, ctx->data - start - 1);
@@ -256,7 +257,7 @@ rfc822_parse_atom_or_dot(struct rfc822_parser_context *ctx, string_t *str)
 	   The difference between this function and rfc822_parse_dot_atom()
 	   is that this doesn't just silently skip over all the whitespace.
 	*/
-	for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
+	for (start = ctx->data; ctx->data < ctx->end; ctx->data++) {
 		if (IS_ATEXT(*ctx->data) || *ctx->data == '.')
 			continue;
 
@@ -278,7 +279,7 @@ int rfc822_parse_phrase(struct rfc822_parser_context *ctx, string_t *str)
 	   obs-phrase = word *(word / "." / CFWS)
 	*/
 
-	if (ctx->data == ctx->end)
+	if (ctx->data >= ctx->end)
 		return 0;
 	if (*ctx->data == '.')
 		return -1;
@@ -313,12 +314,13 @@ rfc822_parse_domain_literal(struct rfc822_parser_context *ctx, string_t *str)
 			     %d94-126        ;  characters not including "[",
 					     ;  "]", or "\"
 	*/
+	i_assert(ctx->data < ctx->end);
 	i_assert(*ctx->data == '[');
 
-	for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
+	for (start = ctx->data; ctx->data < ctx->end; ctx->data++) {
 		if (*ctx->data == '\\') {
 			ctx->data++;
-			if (ctx->data == ctx->end)
+			if (ctx->data >= ctx->end)
 				break;
 		} else if (*ctx->data == ']') {
 			ctx->data++;
@@ -338,6 +340,7 @@ int rfc822_parse_domain(struct rfc822_parser_context *ctx, string_t *str)
 	   domain-literal  = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
 	   obs-domain      = atom *("." atom)
 	*/
+	i_assert(ctx->data < ctx->end);
 	i_assert(*ctx->data == '@');
 	ctx->data++;
 
@@ -386,7 +389,7 @@ int rfc822_parse_content_param(struct rfc822_parser_context *ctx,
 	*key_r = NULL;
 	*value_r = NULL;
 
-	if (ctx->data == ctx->end)
+	if (ctx->data >= ctx->end)
 		return 0;
 	if (*ctx->data != ';')
 		return -1;
@@ -409,10 +412,10 @@ int rfc822_parse_content_param(struct rfc822_parser_context *ctx,
 		/* broken / no value */
 	} else if (*ctx->data == '"') {
 		ret = rfc822_parse_quoted_string(ctx, tmp);
-	} else if (ctx->data != ctx->end && *ctx->data == '=') {
+	} else if (ctx->data < ctx->end && *ctx->data == '=') {
 		/* workaround for broken input:
 		   name==?utf-8?b?...?= */
-		while (ctx->data != ctx->end && *ctx->data != ';' &&
+		while (ctx->data < ctx->end && *ctx->data != ';' &&
 		       *ctx->data != ' ' && *ctx->data != '\t' &&
 		       *ctx->data != '\r' && *ctx->data != '\n') {
 			str_append_c(tmp, *ctx->data);
diff --git a/src/lib-mail/rfc822-parser.h b/src/lib-mail/rfc822-parser.h
index 87e9aa23d..466a68af5 100644
--- a/src/lib-mail/rfc822-parser.h
+++ b/src/lib-mail/rfc822-parser.h
@@ -16,6 +16,14 @@ extern unsigned char rfc822_atext_chars[256];
 void rfc822_parser_init(struct rfc822_parser_context *ctx,
 			const unsigned char *data, size_t size,
 			string_t *last_comment) ATTR_NULL(4);
+static inline void rfc822_parser_deinit(struct rfc822_parser_context *ctx)
+{
+	/* make sure the parsing didn't trigger a bug that caused reading
+	   past the end pointer. */
+	i_assert(ctx->data <= ctx->end);
+	/* make sure the parser is no longer accessed */
+	ctx->data = ctx->end = NULL;
+}
 
 /* The functions below return 1 = more data available, 0 = no more data
    available (but a value might have been returned now), -1 = invalid input.
diff --git a/src/lib-mail/test-rfc2231-parser.c b/src/lib-mail/test-rfc2231-parser.c
index 88d58068b..a2ece097a 100644
--- a/src/lib-mail/test-rfc2231-parser.c
+++ b/src/lib-mail/test-rfc2231-parser.c
@@ -36,6 +36,7 @@ static void test_rfc2231_parser(void)
 	test_assert(rfc2231_parse(&parser, &result) == 0);
 	for (i = 0; output[i] != NULL && result[i] != NULL; i++)
 		test_assert(strcmp(output[i], result[i]) == 0);
+	rfc822_parser_deinit(&parser);
 	test_assert(output[i] == NULL && result[i] == NULL);
 	test_end();
 }
diff --git a/src/lib-mail/test-rfc822-parser.c b/src/lib-mail/test-rfc822-parser.c
index e42e1f4ed..3cf975029 100644
--- a/src/lib-mail/test-rfc822-parser.c
+++ b/src/lib-mail/test-rfc822-parser.c
@@ -31,6 +31,7 @@ static void test_rfc822_parse_quoted_string(void)
 		test_assert_idx(rfc822_parse_quoted_string(&parser, str) == tests[i].ret, i);
 		test_assert_idx(tests[i].ret < 0 ||
 				strcmp(tests[i].output, str_c(str)) == 0, i);
+		rfc822_parser_deinit(&parser);
 		str_truncate(str, 0);
 	}
 	test_end();
@@ -60,6 +61,7 @@ static void test_rfc822_parse_content_param(void)
 		test_assert_idx(strcmp(output[i].value, value) == 0, i);
 		i++;
 	}
+	rfc822_parser_deinit(&parser);
 	test_assert(ret == 0);
 	test_assert(i == N_ELEMENTS(output));
 	test_end();
diff --git a/src/plugins/fts/fts-build-mail.c b/src/plugins/fts/fts-build-mail.c
index ad989c97a..9084b1664 100644
--- a/src/plugins/fts/fts-build-mail.c
+++ b/src/plugins/fts/fts-build-mail.c
@@ -56,6 +56,7 @@ static void fts_build_parse_content_type(struct fts_mail_build_context *ctx,
 		(void)rfc822_parse_content_type(&parser, content_type);
 		ctx->content_type = str_lcase(i_strdup(str_c(content_type)));
 	} T_END;
+	rfc822_parser_deinit(&parser);
 }
 
 static void
diff --git a/src/plugins/fts/fts-parser-script.c b/src/plugins/fts/fts-parser-script.c
index 4259454e5..2b55783b6 100644
--- a/src/plugins/fts/fts-parser-script.c
+++ b/src/plugins/fts/fts-parser-script.c
@@ -173,8 +173,10 @@ static void parse_content_disposition(const char *content_disposition,
 
 	/* type; param; param; .. */
 	str = t_str_new(32);
-	if (rfc822_parse_mime_token(&parser, str) < 0)
+	if (rfc822_parse_mime_token(&parser, str) < 0) {
+		rfc822_parser_deinit(&parser);
 		return;
+	}
 
 	rfc2231_parse(&parser, &results);
 	filename2 = NULL;
@@ -191,6 +193,7 @@ static void parse_content_disposition(const char *content_disposition,
 		   much about the filename actually, just about its extension */
 		*filename_r = filename2;
 	}
+	rfc822_parser_deinit(&parser);
 }
 
 static struct fts_parser *
-- 
2.15.1

