From b367fb9874f20c223d49989207d94eb654165535 Mon Sep 17 00:00:00 2001
From: Ilya Verbin <iverbin@tarantool.org>
Date: Mon, 21 Aug 2023 15:37:44 +0300
Subject: [PATCH] box: fix memory leak on error_unpack_unsafe() failure

Memory is leaked in the following scenario:
- MP_ERROR_STACK with 2 errors is passed to error_unpack_unsafe():
  1. A correct MP_MAP with MP_ERROR_* fields;
  2. Something unexpected, e.g. MP_INT;
- This first call to mp_decode_error_one() allocates memory for the first
  error in error_build_xc() -> `new ClientError()`;
- The second call to mp_decode_error_one() returns NULL, and
  error_unpack_unsafe() returns NULL too. Memory from the previous step
  is leaked.

Closes #8921

NO_DOC=bugfix
---
 ...h-8921-memory-leak-in-xrow_decode_error.md |   4 +
 src/box/mp_error.cc                           |  14 +-
 test/unit/xrow.cc                             | 144 +++++++++++++++++-
 3 files changed, 158 insertions(+), 4 deletions(-)
 create mode 100644 changelogs/unreleased/gh-8921-memory-leak-in-xrow_decode_error.md

diff --git a/changelogs/unreleased/gh-8921-memory-leak-in-xrow_decode_error.md b/changelogs/unreleased/gh-8921-memory-leak-in-xrow_decode_error.md
new file mode 100644
index 0000000000..5e766ff3a3
--- /dev/null
+++ b/changelogs/unreleased/gh-8921-memory-leak-in-xrow_decode_error.md
@@ -0,0 +1,4 @@
+## bugfix/core
+
+* Fixed the memory leak on unpacking an invalid MsgPack error extension
+  (gh-8921).
diff --git a/src/box/mp_error.cc b/src/box/mp_error.cc
index a5d2ade401..03b3625224 100644
--- a/src/box/mp_error.cc
+++ b/src/box/mp_error.cc
@@ -482,7 +482,7 @@ error_unpack_unsafe(const char **data)
 		if (mp_typeof(**data) != MP_UINT) {
 			diag_set(ClientError, ER_INVALID_MSGPACK,
 				 "Invalid MP_ERROR MsgPack format");
-			return NULL;
+			goto error;
 		}
 		uint64_t key = mp_decode_uint(data);
 		switch(key) {
@@ -490,14 +490,14 @@ error_unpack_unsafe(const char **data)
 			if (mp_typeof(**data) != MP_ARRAY) {
 				diag_set(ClientError, ER_INVALID_MSGPACK,
 					 "Invalid MP_ERROR MsgPack format");
-				return NULL;
+				goto error;
 			}
 			uint32_t stack_sz = mp_decode_array(data);
 			struct error *effect = NULL;
 			for (uint32_t i = 0; i < stack_sz; i++) {
 				struct error *cur = mp_decode_error_one(data);
 				if (cur == NULL)
-					return NULL;
+					goto error;
 				if (err == NULL) {
 					err = cur;
 					effect = cur;
@@ -514,6 +514,14 @@ error_unpack_unsafe(const char **data)
 		}
 	}
 	return err;
+
+error:
+	if (err != NULL) {
+		/* A hack to delete the error. */
+		error_ref(err);
+		error_unref(err);
+	}
+	return NULL;
 }
 
 struct error *
diff --git a/test/unit/xrow.cc b/test/unit/xrow.cc
index 4b2e68c170..25ea54db63 100644
--- a/test/unit/xrow.cc
+++ b/test/unit/xrow.cc
@@ -540,13 +540,151 @@ test_xrow_decode_unknown_key(void)
 	footer();
 }
 
+static void
+test_xrow_decode_error_1(void)
+{
+	header();
+	plan(1);
+
+	uint8_t data[] = {
+		0x81, /* MP_MAP of 1 element */
+		0x52, /* IPROTO_ERROR: */
+		0x00  /* MP_INT instead of MP_MAP */
+	};
+
+	struct iovec body;
+	body.iov_base = (void *)data;
+	body.iov_len = sizeof(data);
+
+	struct xrow_header row;
+	row.type = IPROTO_TYPE_ERROR | 111;
+	row.body[0] = body;
+	row.bodycnt = 1;
+
+	xrow_decode_error(&row);
+
+	struct error *e = diag_last_error(diag_get());
+	is(e->code, 111, "xrow_decode_error");
+	diag_destroy(diag_get());
+
+	check_plan();
+	footer();
+}
+
+static void
+test_xrow_decode_error_2(void)
+{
+	header();
+	plan(1);
+
+	uint8_t data[] = {
+		0x81, /* MP_MAP of 1 element */
+		0x52, /* IPROTO_ERROR: */
+		0x81, /* MP_MAP of 1 element */
+		0xa1  /* MP_STR instead of MP_UINT */
+	};
+
+	struct iovec body;
+	body.iov_base = (void *)data;
+	body.iov_len = sizeof(data);
+
+	struct xrow_header row;
+	row.type = IPROTO_TYPE_ERROR | 222;
+	row.body[0] = body;
+	row.bodycnt = 1;
+
+	xrow_decode_error(&row);
+
+	struct error *e = diag_last_error(diag_get());
+	is(e->code, 222, "xrow_decode_error");
+	diag_destroy(diag_get());
+
+	check_plan();
+	footer();
+}
+
+static void
+test_xrow_decode_error_3(void)
+{
+	header();
+	plan(1);
+
+	uint8_t data[] = {
+		0x81, /* MP_MAP of 1 element */
+		0x52, /* IPROTO_ERROR: */
+		0x81, /* MP_MAP of 1 element */
+		0x00, /* MP_ERROR_STACK: */
+		0x00  /* MP_INT instead of MP_ARRAY */
+	};
+
+	struct iovec body;
+	body.iov_base = (void *)data;
+	body.iov_len = sizeof(data);
+
+	struct xrow_header row;
+	row.type = IPROTO_TYPE_ERROR | 333;
+	row.body[0] = body;
+	row.bodycnt = 1;
+
+	xrow_decode_error(&row);
+
+	struct error *e = diag_last_error(diag_get());
+	is(e->code, 333, "xrow_decode_error");
+	diag_destroy(diag_get());
+
+	check_plan();
+	footer();
+}
+
+static void
+test_xrow_decode_error_4(void)
+{
+	header();
+	plan(1);
+
+	uint8_t data[] = {
+		0x81, /* MP_MAP of 1 element */
+		0x52, /* IPROTO_ERROR: */
+		0x81, /* MP_MAP of 1 element */
+		0x00, /* MP_ERROR_STACK: */
+		0x93, /* MP_ARRAY of 3 elements */
+		0x83, /* MP_MAP of 3 elements */
+		0x00, 0xa1, 0x00, /* MP_ERROR_TYPE: "" */
+		0x01, 0xa1, 0x00, /* MP_ERROR_FILE: "" */
+		0x03, 0xa1, 0x00, /* MP_ERROR_MESSAGE: "" */
+		0x83, /* MP_MAP of 3 elements */
+		0x00, 0xa1, 0x00, /* MP_ERROR_TYPE: "" */
+		0x01, 0xa1, 0x00, /* MP_ERROR_FILE: "" */
+		0x03, 0xa1, 0x00, /* MP_ERROR_MESSAGE: "" */
+		0x00 /* MP_INT instead of MP_MAP */
+	};
+
+	struct iovec body;
+	body.iov_base = (void *)data;
+	body.iov_len = sizeof(data);
+
+	struct xrow_header row;
+	row.type = IPROTO_TYPE_ERROR | 444;
+	row.body[0] = body;
+	row.bodycnt = 1;
+
+	xrow_decode_error(&row);
+
+	struct error *e = diag_last_error(diag_get());
+	is(e->code, 444, "xrow_decode_error");
+	diag_destroy(diag_get());
+
+	check_plan();
+	footer();
+}
+
 int
 main(void)
 {
 	memory_init();
 	fiber_init(fiber_c_invoke);
 	header();
-	plan(6);
+	plan(10);
 
 	random_init();
 
@@ -557,6 +695,10 @@ main(void)
 	test_xrow_fields();
 	test_xrow_encode_dml();
 	test_xrow_decode_unknown_key();
+	test_xrow_decode_error_1();
+	test_xrow_decode_error_2();
+	test_xrow_decode_error_3();
+	test_xrow_decode_error_4();
 
 	random_free();
 	fiber_free();
-- 
GitLab