From 2049b9f39ef7e47ebcf0a2022c408d3311b4c8bc Mon Sep 17 00:00:00 2001
From: Vladimir Davydov <vdavydov@tarantool.org>
Date: Mon, 16 May 2022 14:50:23 +0300
Subject: [PATCH] iproto: log error before iproto_write_error

iproto_write_error() may reset diag (for example, if the client closes
the socket), thus invalidating the error we are going to log and leading
to a use after free bug. We must log the error before trying to send it
to the client.

The bug was introduced by commit 9b4ab9fe0ef05 ("iproto: use iostream
abstraction"), which switched iproto_write_error() from plain write() to
the iostream_write(), which may set diag.

Closes #6890

NO_DOC=bug fix
NO_CHANGELOG=unreleased

(cherry picked from commit 731170591d00ad89752242ee8bb4572803d9aa55)
---
 src/box/iproto.cc                             |  4 +--
 ..._6890_iproto_error_use_after_free_test.lua | 31 +++++++++++++++++++
 2 files changed, 33 insertions(+), 2 deletions(-)
 create mode 100644 test/box-luatest/gh_6890_iproto_error_use_after_free_test.lua

diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 1b7ee72cf2..40154d7fe3 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -1230,8 +1230,8 @@ iproto_connection_resume(struct iproto_connection *con)
 	 */
 	if (iproto_enqueue_batch(con, con->p_ibuf) != 0) {
 		struct error *e = box_error_last();
-		iproto_write_error(&con->io, e, ::schema_version, 0);
 		error_log(e);
+		iproto_write_error(&con->io, e, ::schema_version, 0);
 		iproto_connection_close(con);
 	}
 }
@@ -1317,9 +1317,9 @@ iproto_connection_on_input(ev_loop *loop, struct ev_io *watcher,
 		if (iproto_enqueue_batch(con, in) != 0)
 			diag_raise();
 	} catch (Exception *e) {
+		e->log();
 		/* Best effort at sending the error message to the client. */
 		iproto_write_error(io, e, ::schema_version, 0);
-		e->log();
 		iproto_connection_close(con);
 	}
 }
diff --git a/test/box-luatest/gh_6890_iproto_error_use_after_free_test.lua b/test/box-luatest/gh_6890_iproto_error_use_after_free_test.lua
new file mode 100644
index 0000000000..4da08b1ca7
--- /dev/null
+++ b/test/box-luatest/gh_6890_iproto_error_use_after_free_test.lua
@@ -0,0 +1,31 @@
+local msgpack = require('msgpack')
+local server = require('test.luatest_helpers.server')
+local socket = require('socket')
+local uri = require('uri')
+local t = require('luatest')
+local g = t.group()
+
+g.before_all(function()
+    g.server = server:new({alias = 'master'})
+    g.server:start()
+end)
+
+g.after_all(function()
+    g.server:drop()
+end)
+
+g.test_iproto_error_use_after_free = function()
+    -- Connect to the server.
+    local u = uri.parse(g.server.net_box_uri)
+    local s = socket.tcp_connect(u.host, u.service)
+    t.assert_is_not(s, nil)
+    -- Skip the greeting.
+    t.assert_equals(#s:read(128), 128)
+    -- Send an invalid request and immediately close the socket so that
+    -- the server fails to send the error back.
+    local d = msgpack.encode('foo')
+    t.assert_equals(s:write(d), #d)
+    s:close()
+    -- Check that the server logs the proper error.
+    t.assert(g.server:grep_log('ER_INVALID_MSGPACK'))
+end
-- 
GitLab