Blob Blame History Raw
diff -up dovecot-2.3.8/src/lib-mail/message-decoder.c.CVE_2020_12100prereq dovecot-2.3.8/src/lib-mail/message-decoder.c
--- dovecot-2.3.8/src/lib-mail/message-decoder.c.CVE_2020_12100prereq	2019-10-08 10:46:18.000000000 +0200
+++ dovecot-2.3.8/src/lib-mail/message-decoder.c	2020-08-07 17:48:58.320126698 +0200
@@ -13,9 +13,6 @@
 #include "message-header-decode.h"
 #include "message-decoder.h"
 
-/* base64 takes max 4 bytes per character, q-p takes max 3. */
-#define MAX_ENCODING_BUF_SIZE 3
-
 struct message_decoder_context {
 	enum message_decoder_flags flags;
 	normalizer_func_t *normalizer;
@@ -30,7 +27,7 @@ struct message_decoder_context {
 	size_t translation_size;
 
 	struct qp_decoder *qp;
-	buffer_t *encoding_buf;
+	struct base64_decoder base64_decoder;
 
 	char *content_type, *content_charset;
 	enum message_cte message_cte;
@@ -53,7 +50,7 @@ message_decoder_init(normalizer_func_t *
 	ctx->normalizer = normalizer;
 	ctx->buf = buffer_create_dynamic(default_pool, 8192);
 	ctx->buf2 = buffer_create_dynamic(default_pool, 8192);
-	ctx->encoding_buf = buffer_create_dynamic(default_pool, 128);
+	base64_decode_init(&ctx->base64_decoder, &base64_scheme, 0);
 	return ctx;
 }
 
@@ -68,7 +65,6 @@ void message_decoder_deinit(struct messa
 	if (ctx->qp != NULL)
 		qp_decoder_deinit(&ctx->qp);
 
-	buffer_free(&ctx->encoding_buf);
 	buffer_free(&ctx->buf);
 	buffer_free(&ctx->buf2);
 	i_free(ctx->charset_trans_charset);
@@ -273,14 +269,9 @@ static bool message_decode_body(struct m
 				struct message_block *input,
 				struct message_block *output)
 {
-	struct base64_decoder b64dec;
 	const unsigned char *data = NULL;
-	size_t pos = 0, size = 0;
+	size_t pos, size = 0;
 	const char *error;
-	int ret;
-
-	if (ctx->encoding_buf->used != 0)
-		buffer_append(ctx->encoding_buf, input->data, input->size);
 
 	switch (ctx->message_cte) {
 	case MESSAGE_CTE_UNKNOWN:
@@ -289,12 +280,10 @@ static bool message_decode_body(struct m
 
 	case MESSAGE_CTE_78BIT:
 	case MESSAGE_CTE_BINARY:
-		i_assert(ctx->encoding_buf->used == 0);
 		data = input->data;
-		size = pos = input->size;
+		size = input->size;
 		break;
 	case MESSAGE_CTE_QP: {
-		i_assert(ctx->encoding_buf->used == 0);
 		buffer_set_used_size(ctx->buf, 0);
 		if (ctx->qp == NULL)
 			ctx->qp = qp_decoder_init(ctx->buf);
@@ -302,45 +291,24 @@ static bool message_decode_body(struct m
 				      &pos, &error);
 		data = ctx->buf->data;
 		size = ctx->buf->used;
-		/* eat away all input. qp-decoder buffers it internally. */
-		pos = input->size;
 		break;
 	}
 	case MESSAGE_CTE_BASE64:
 		buffer_set_used_size(ctx->buf, 0);
-		base64_decode_init(&b64dec, &base64_scheme, 0);
-		if (ctx->encoding_buf->used != 0) {
-			ret = base64_decode_more(&b64dec,
-						 ctx->encoding_buf->data,
-						 ctx->encoding_buf->used,
-						 &pos, ctx->buf);
-		} else {
-			ret = base64_decode_more(&b64dec,
-						 input->data, input->size,
-						 &pos, ctx->buf);
-		}
-		if (ret < 0 || base64_decode_finish(&b64dec) < 0) {
-			/* corrupted base64 data, don't bother with
-			   the rest of it */
-			return FALSE;
-		}
-		if (ret == 0) {
-			/* end of base64 input */
-			pos = input->size;
-			buffer_set_used_size(ctx->encoding_buf, 0);
+		if (!base64_decode_is_finished(&ctx->base64_decoder)) {
+			if (base64_decode_more(&ctx->base64_decoder,
+					       input->data, input->size,
+					       &pos, ctx->buf) <= 0) {
+				/* ignore the rest of the input in this
+				   MIME part */
+				(void)base64_decode_finish(&ctx->base64_decoder);
+			}
 		}
 		data = ctx->buf->data;
 		size = ctx->buf->used;
 		break;
 	}
 
-	if (ctx->encoding_buf->used != 0)
-		buffer_delete(ctx->encoding_buf, 0, pos);
-	else if (pos != input->size) {
-		buffer_append(ctx->encoding_buf,
-			      input->data + pos, input->size - pos);
-	}
-
 	if (ctx->binary_input) {
 		output->data = data;
 		output->size = size;
@@ -402,10 +370,11 @@ void message_decoder_decode_reset(struct
 {
 	const char *error;
 
+	base64_decode_reset(&ctx->base64_decoder);
+
 	if (ctx->qp != NULL)
 		(void)qp_decoder_finish(ctx->qp, &error);
 	i_free_and_null(ctx->content_type);
 	i_free_and_null(ctx->content_charset);
 	ctx->message_cte = MESSAGE_CTE_78BIT;
-	buffer_set_used_size(ctx->encoding_buf, 0);
 }
diff -up dovecot-2.3.8/src/lib-mail/test-message-decoder.c.CVE_2020_12100prereq dovecot-2.3.8/src/lib-mail/test-message-decoder.c
--- dovecot-2.3.8/src/lib-mail/test-message-decoder.c.CVE_2020_12100prereq	2019-10-08 10:46:18.000000000 +0200
+++ dovecot-2.3.8/src/lib-mail/test-message-decoder.c	2020-08-07 17:50:04.612248484 +0200
@@ -1,7 +1,8 @@
 /* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
-#include "buffer.h"
+#include "str.h"
+#include "istream.h"
 #include "charset-utf8.h"
 #include "message-parser.h"
 #include "message-header-decode.h"
@@ -82,6 +83,66 @@ static void test_message_decoder(void)
 	test_end();
 }
 
+static void test_message_decoder_multipart(void)
+{
+	static const char test_message_input[] =
+		"Content-Type: multipart/mixed; boundary=foo\n"
+		"\n"
+		"--foo\n"
+		"Content-Transfer-Encoding: quoted-printable\n"
+		"Content-Type: text/plain; charset=utf-8\n"
+		"\n"
+		"p=C3=A4iv=C3=A4=C3=A4\n"
+		"\n"
+		"--foo\n"
+		"Content-Transfer-Encoding: base64\n"
+		"Content-Type: text/plain; charset=utf-8\n"
+		"\n"
+		"ecO2dMOkIHZhYW4uCg== ignored\n"
+		"--foo\n"
+		"Content-Transfer-Encoding: base64\n"
+		"Content-Type: text/plain; charset=utf-8\n"
+		"\n"
+		"?garbage\n"
+		"--foo--\n";
+	struct message_parser_ctx *parser;
+	struct message_decoder_context *decoder;
+	struct message_part *parts;
+	struct message_block input, output;
+	struct istream *istream;
+	string_t *str_out = t_str_new(20);
+	int ret;
+
+	test_begin("message decoder multipart");
+
+	istream = test_istream_create(test_message_input);
+	parser = message_parser_init(pool_datastack_create(), istream, 0, 0);
+	decoder = message_decoder_init(NULL, 0);
+
+	test_istream_set_allow_eof(istream, FALSE);
+	for (size_t i = 0; i < sizeof(test_message_input); i++) {
+		if (i == sizeof(test_message_input)-1)
+			test_istream_set_allow_eof(istream, TRUE);
+		test_istream_set_size(istream, i);
+		while ((ret = message_parser_parse_next_block(parser, &input)) > 0) {
+			if (message_decoder_decode_next_block(decoder, &input, &output) &&
+			    output.hdr == NULL && output.size > 0)
+				str_append_data(str_out, output.data, output.size);
+		}
+		if (i == sizeof(test_message_input)-1)
+			test_assert(ret == -1);
+		else
+			test_assert(ret == 0);
+	}
+	/* NOTE: qp-decoder decoder changes \n into \r\n */
+	test_assert_strcmp(str_c(str_out), "p\xC3\xA4iv\xC3\xA4\xC3\xA4\r\ny\xC3\xB6t\xC3\xA4 vaan.\n");
+
+	message_decoder_deinit(&decoder);
+	message_parser_deinit(&parser, &parts);
+	i_stream_unref(&istream);
+	test_end();
+}
+
 static void test_message_decoder_current_content_type(void)
 {
 	struct message_decoder_context *ctx;
@@ -149,6 +210,7 @@ int main(void)
 {
 	static void (*const test_functions[])(void) = {
 		test_message_decoder,
+		test_message_decoder_multipart,
 		test_message_decoder_current_content_type,
 		NULL
 	};