From 0c3f9b37cd19f6f0731dbc7ce1fc7a6bb65fd406 Mon Sep 17 00:00:00 2001
From: Cyrill Gorcunov <gorcunov@gmail.com>
Date: Fri, 8 Jul 2022 00:12:51 +0300
Subject: [PATCH] log: free resources while event loop is running

The 'log' module uses fibers internally for logs rotation sake and
before we can free log's resources (on program exit) we need to wait
until rotation is complete, which implies that events loop is still
running. But we break the event loop in `on_shutdown_f` trigger and
calling any events based functionality later cause unexpected results
because fibers are no loner valid to use. Thus move `say_logger_free`
call into `on_shutdown_f` body where fibers are still alive.

N.B. Testing the issue is sensitive to timings, during local tests
found that minimal delay 1ms is enough to trigger, thus ERRINJ_LOG_ROTATE
get increased.

Fixes #4450

NO_DOC=bugfix

Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
---
 .../unreleased/gh-4450-logger-sigsegv.md      |  4 ++
 src/lib/core/say.c                            | 17 ++++++-
 src/main.cc                                   |  2 +-
 test/app-tap/gh-4450-log-rotate-exit.test.lua | 46 +++++++++++++++++++
 test/app-tap/suite.ini                        |  2 +-
 5 files changed, 67 insertions(+), 4 deletions(-)
 create mode 100644 changelogs/unreleased/gh-4450-logger-sigsegv.md
 create mode 100755 test/app-tap/gh-4450-log-rotate-exit.test.lua

diff --git a/changelogs/unreleased/gh-4450-logger-sigsegv.md b/changelogs/unreleased/gh-4450-logger-sigsegv.md
new file mode 100644
index 0000000000..3b898a605a
--- /dev/null
+++ b/changelogs/unreleased/gh-4450-logger-sigsegv.md
@@ -0,0 +1,4 @@
+## bugfix/core
+
+*  Fix potential nil dereference and crash in case of an active
+   log rotation during the program exit stage (gh-4450).
diff --git a/src/lib/core/say.c b/src/lib/core/say.c
index 69c826264b..e38237e358 100644
--- a/src/lib/core/say.c
+++ b/src/lib/core/say.c
@@ -267,7 +267,7 @@ log_rotate(struct log *log)
 	if (pm_atomic_load(&log->type) != SAY_LOGGER_FILE)
 		return 0;
 
-	ERROR_INJECT(ERRINJ_LOG_ROTATE, { usleep(10); });
+	ERROR_INJECT(ERRINJ_LOG_ROTATE, { usleep(1000); });
 
 	int fd = open(log->path, O_WRONLY | O_APPEND | O_CREAT,
 		      S_IRUSR | S_IWUSR | S_IRGRP);
@@ -750,11 +750,24 @@ say_logger_init(const char *init_str, int level, int nonblock,
 	panic("failed to initialize logging subsystem");
 }
 
+/*
+ * Logger uses events loop and to free resources we need
+ * to wait until logs rotation is complete. Thus make sure
+ * this routine is called before events loop is finished.
+ */
 void
 say_logger_free(void)
 {
-	if (say_logger_initialized())
+	if (say_logger_initialized()) {
 		log_destroy(&log_std);
+		/*
+		 * Once destroyed switch to boot logger
+		 * because boot logger is safe to use
+		 * anytime and might be suitable for
+		 * debugging.
+		 */
+		log_default = &log_boot;
+	}
 }
 
 /** {{{ Formatters */
diff --git a/src/main.cc b/src/main.cc
index 6b5f5ede85..3c52d1a80e 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -141,6 +141,7 @@ on_shutdown_f(va_list ap)
 	(void) ap;
 	trigger_fiber_run(&box_on_shutdown_trigger_list, NULL,
 			  on_shutdown_trigger_timeout);
+	say_logger_free();
 	ev_break(loop(), EVBREAK_ALL);
 	return 0;
 }
@@ -583,7 +584,6 @@ tarantool_free(void)
 	memtx_tx_manager_free();
 	coll_free();
 	systemd_free();
-	say_logger_free();
 	fiber_free();
 	memory_free();
 	random_free();
diff --git a/test/app-tap/gh-4450-log-rotate-exit.test.lua b/test/app-tap/gh-4450-log-rotate-exit.test.lua
new file mode 100755
index 0000000000..a1ca9abee2
--- /dev/null
+++ b/test/app-tap/gh-4450-log-rotate-exit.test.lua
@@ -0,0 +1,46 @@
+#!/usr/bin/env tarantool
+
+local tap = require('tap')
+local test = tap.test("gh-4450-log-rotate-exit")
+local fio = require('fio')
+local tarantool_bin = arg[-1]
+
+test:plan(1)
+
+local function run_script(code)
+    local dir = fio.tempdir()
+    local script_path = fio.pathjoin(dir, 'gh-4450-script.lua')
+    local script = fio.open(script_path,
+        {'O_CREAT', 'O_WRONLY', 'O_APPEND'},
+        tonumber('0777', 8))
+    script:write(code)
+    script:close()
+    local output_file = fio.pathjoin(fio.cwd(), 'gh-4450-out.txt')
+    local cmd = [[/bin/sh -c 'cd "%s" && "%s" ./gh-4450-script.lua 0> %s 2> %s']]
+    local code = os.execute(
+        string.format(cmd, dir, tarantool_bin, output_file, output_file)
+    )
+    fio.rmtree(dir)
+    local out_fd = fio.open(output_file, {'O_RDONLY'})
+    local output = out_fd:read(100000)
+    out_fd:close()
+    return code, output
+end
+
+--
+-- gh-4382: an error in main script should be handled gracefully,
+-- with proper logging.
+--
+local code = "log = require('log')\n"
+code = code .. "box.cfg{log = 'gh-4550.log'}\n"
+code = code .. "box.error.injection.set('ERRINJ_LOG_ROTATE', true)\n"
+code = code .. "log.rotate()\n"
+code = code .. "os.exit()\n"
+local _, output = run_script(code)
+
+test:diag(output)
+test:ok(not string.find(output, "SEGV_MAPERR"),
+        "program exited without SEGV_MAPERR")
+
+test:check()
+os.exit(0)
diff --git a/test/app-tap/suite.ini b/test/app-tap/suite.ini
index 06f549ca36..3263185067 100644
--- a/test/app-tap/suite.ini
+++ b/test/app-tap/suite.ini
@@ -4,7 +4,7 @@ description = application server tests (TAP)
 lua_libs = lua/require_mod.lua lua/serializer_test.lua lua/process_timeout.lua
 is_parallel = True
 use_unix_sockets_iproto = True
-release_disabled = gh-5040-inter-mode-isatty-via-errinj.test.lua gh-2717-no-quit-sigint.test.lua
+release_disabled = gh-5040-inter-mode-isatty-via-errinj.test.lua gh-2717-no-quit-sigint.test.lua gh-4450-log-rotate-exit.test.lua
 fragile = {
     "retries": 10,
     "tests": {
-- 
GitLab