From fabb121c81cc90c5bef08ada46263cf2e1fb8358 Mon Sep 17 00:00:00 2001
From: Konstantin Osipov <kostja@tarantool.org>
Date: Mon, 10 Nov 2014 17:59:23 +0300
Subject: [PATCH] A fix for gh-617 - add a separate fiber key for the current
 user.

Use cached user credentials for global access checks and store
them in a separate fiber key.

This fixes gh-617 and speeds up privilege checks.

@todo: we need to add a linked list of all user sessions
to the user cache, as well as all user setuid functions.
These lists could be used to update privilege cache,
get/update per-user statistics, disconnect sessions
of a dropped user.
---
 src/box/alter.cc                | 15 ++++--
 src/box/authentication.cc       |  4 +-
 src/box/box.cc                  | 19 ++++---
 src/box/box.h                   |  4 +-
 src/box/iproto.cc               | 13 ++---
 src/box/key_def.h               | 23 ++++++--
 src/box/lua/call.cc             | 40 ++++++--------
 src/box/lua/session.cc          | 39 ++++++++------
 src/box/recovery.cc             | 20 +++----
 src/box/replica.cc              |  4 +-
 src/box/session.cc              | 80 ++++++++++++++--------------
 src/box/session.h               | 93 +++++++++++++++++++--------------
 src/box/space.cc                |  7 +--
 src/box/user_cache.h            | 15 ------
 src/box/user_def.h              |  8 +++
 src/fiber.h                     |  4 +-
 src/lib/salad/rlist.h           |  1 +
 src/lua/fiber.cc                |  5 +-
 src/lua/init.cc                 |  4 --
 test/app/console.test.lua       |  2 +-
 test/box/session.storage.result |  2 +-
 21 files changed, 220 insertions(+), 182 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 3145e879bd..80eff00981 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -72,14 +72,15 @@
 void
 access_check_ddl(uint32_t owner_uid)
 {
-	struct user_def *user = user();
+	struct current_user *user = current_user();
 	/*
 	 * Only the creator of the space or superuser can modify
 	 * the space, since we don't have ALTER privilege.
 	 */
 	if (owner_uid != user->uid && user->uid != ADMIN) {
+		struct user_def *def = user_cache_find(user->uid);
 		tnt_raise(ClientError, ER_ACCESS_DENIED,
-			  "Create or drop", user->name);
+			  "Create or drop", def->name);
 	}
 }
 
@@ -1321,6 +1322,7 @@ func_def_create_from_tuple(struct func_def *func, struct tuple *tuple)
 {
 	func->fid = tuple_field_u32(tuple, ID);
 	func->uid = tuple_field_u32(tuple, UID);
+	func->setuid = false;
 	/*
 	 * Do not initialize the privilege cache right away since
 	 * when loading up a function definition during recovery,
@@ -1332,8 +1334,7 @@ func_def_create_from_tuple(struct func_def *func, struct tuple *tuple)
 	 * Later on consistency of the cache is ensured by DDL
 	 * checks (see user_has_data()).
 	 */
-	func->auth_token = BOX_USER_MAX; /* invalid value */
-	func->setuid = false;
+	func->setuid_user.auth_token = BOX_USER_MAX; /* invalid value */
 	const char *name = tuple_field_cstr(tuple, NAME);
 	uint32_t len = strlen(name);
 	if (len >= sizeof(func->name)) {
@@ -1498,8 +1499,14 @@ grant_or_revoke(struct priv_def *priv)
 	struct access *access = NULL;
 	switch (priv->object_type) {
 	case SC_UNIVERSE:
+	{
 		access = &grantee->universal_access;
+		/** Update cache at least in the current session. */
+		struct current_user *user = current_user();
+		if (grantee->uid == user->uid)
+			user->universal_access = priv->access;
 		break;
+	}
 	case SC_SPACE:
 	{
 		struct space *space = space_by_id(priv->object_id);
diff --git a/src/box/authentication.cc b/src/box/authentication.cc
index fa64b1e3ff..b35669ad64 100644
--- a/src/box/authentication.cc
+++ b/src/box/authentication.cc
@@ -35,7 +35,7 @@ authenticate(const char *user_name, uint32_t len,
 	     const char *tuple, const char * /* tuple_end */)
 {
 	struct user_def *user = user_cache_find_by_name(user_name, len);
-	struct session *session = session();
+	struct session *session = current_session();
 	uint32_t part_count = mp_decode_array(&tuple);
 	if (part_count < 2) {
 		/* Expected at least: authentication mechanism and data. */
@@ -54,6 +54,6 @@ authenticate(const char *user_name, uint32_t len,
 	if (scramble_check(scramble, session->salt, user->hash2))
 		tnt_raise(ClientError, ER_PASSWORD_MISMATCH, user->name);
 
-	session_set_user(session, user);
+	current_user_init(&session->user, user);
 }
 
diff --git a/src/box/box.cc b/src/box/box.cc
index 559ce39a63..de3f1c33ca 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -331,7 +331,7 @@ box_on_cluster_join(const tt_uuid *server_uuid)
 }
 
 void
-box_process_join(struct xrow_header *header)
+box_process_join(int fd, struct xrow_header *header)
 {
 	assert(header->type == IPROTO_JOIN);
 	struct tt_uuid server_uuid = uuid_nil;
@@ -339,15 +339,15 @@ box_process_join(struct xrow_header *header)
 
 	box_on_cluster_join(&server_uuid);
 
-	/* process JOIN request via replication relay */
-	replication_join(session()->fd, header);
+	/* Process JOIN request via replication relay */
+	replication_join(fd, header);
 }
 
 void
-box_process_subscribe(struct xrow_header *header)
+box_process_subscribe(int fd, struct xrow_header *header)
 {
 	/* process SUBSCRIBE request via replication relay */
-	replication_subscribe(session()->fd, header);
+	replication_subscribe(fd, header);
 }
 
 /** Replace the current server id in _cluster */
@@ -384,6 +384,7 @@ box_free(void)
 {
 	if (recovery == NULL)
 		return;
+	session_free();
 	user_cache_free();
 	schema_free();
 	tuple_free();
@@ -391,7 +392,6 @@ box_free(void)
 	recovery = NULL;
 	engine_shutdown();
 	stat_free();
-	session_free();
 }
 
 static void
@@ -411,7 +411,6 @@ box_init()
 	box_check_config();
 	title("loading", NULL);
 
-	session_init();
 	replication_prefork(cfg_gets("snap_dir"), cfg_gets("wal_dir"));
 	stat_init();
 
@@ -423,6 +422,12 @@ box_init()
 
 	schema_init();
 	user_cache_init();
+	/*
+	 * The order is important: to initialize sessions,
+	 * we need to access the admin user, which is used
+	 * as a default session user when running triggers.
+	 */
+	session_init();
 
 	/* recovery initialization */
 	recovery = recovery_new(cfg_gets("snap_dir"), cfg_gets("wal_dir"),
diff --git a/src/box/box.h b/src/box/box.h
index a2ebc2f289..23ad5451e5 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -101,10 +101,10 @@ void
 box_leave_local_standby_mode(void *data __attribute__((unused)));
 
 void
-box_process_join(struct xrow_header *header);
+box_process_join(int fd, struct xrow_header *header);
 
 void
-box_process_subscribe(struct xrow_header *header);
+box_process_subscribe(int fd, struct xrow_header *header);
 
 /**
  * Check Lua configuration before initialization or
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 866a3be5d3..4b561b5231 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -211,6 +211,7 @@ iproto_queue_handler(va_list ap)
 	while ((request = iproto_queue_pop(i_queue))) {
 		IprotoRequestGuard guard(request);
 		fiber_set_session(fiber(), request->session);
+		fiber_set_user(fiber(), &request->session->user);
 		request->process(request);
 	}
 	/** Put the current fiber into a queue fiber cache. */
@@ -347,8 +348,8 @@ iproto_connection_delete(struct iproto_connection *con)
 	assert(iproto_connection_is_idle(con));
 	assert(!evio_is_active(&con->output));
 	if (con->session) {
-		fiber_set_session(fiber(), con->session);
-		session_run_on_disconnect_triggers(con->session);
+		if (! rlist_empty(&session_on_disconnect))
+			session_run_on_disconnect_triggers(con->session);
 		session_destroy(con->session);
 	}
 	iobuf_delete(con->iobuf[0]);
@@ -710,12 +711,12 @@ iproto_process_admin(struct iproto_request *ireq)
 					  ireq->header.sync);
 			break;
 		case IPROTO_JOIN:
-			box_process_join(&ireq->header);
+			box_process_join(con->input.fd, &ireq->header);
 			/* TODO: check requests in `con; queue */
 			iproto_connection_shutdown(con);
 			return;
 		case IPROTO_SUBSCRIBE:
-			box_process_subscribe(&ireq->header);
+			box_process_subscribe(con->input.fd, &ireq->header);
 			/* TODO: check requests in `con; queue */
 			iproto_connection_shutdown(con);
 			return;
@@ -770,8 +771,8 @@ iproto_process_connect(struct iproto_request *request)
 		con->session = session_create(fd, con->cookie);
 		coio_write(&con->input, iproto_greeting(con->session->salt),
 			   IPROTO_GREETING_SIZE);
-		fiber_set_session(fiber(), con->session);
-		session_run_on_connect_triggers(con->session);
+		if (! rlist_empty(&session_on_connect))
+			session_run_on_connect_triggers(con->session);
 	} catch (ClientError *e) {
 		iproto_reply_error(&iobuf->out, e, request->header.type);
 		try {
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 63e7251357..91982d07eb 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -291,11 +291,28 @@ struct access {
 	uint8_t effective;
 };
 
+/**
+ * Effective session user. A cache of user data
+ * and access stored in session and fiber local storage.
+ * Differs from the authenticated user when executing
+ * setuid functions.
+ */
+struct current_user {
+	/** A look up key to quickly find session user. */
+	uint8_t auth_token;
+	/**
+	 * Cached global grants, to avoid an extra look up
+	 * when checking global grants.
+	 */
+	uint8_t universal_access;
+	/** User id of the authenticated user. */
+	uint32_t uid;
+};
+
 /**
  * Definition of a function. Function body is not stored
  * or replicated (yet).
  */
-
 struct func_def {
 	/** Function id. */
 	uint32_t fid;
@@ -303,14 +320,14 @@ struct func_def {
 	uint32_t uid;
 	/**
 	 * True if the function requires change of user id before
-	 * invocaction.
+	 * invocation.
 	 */
 	bool setuid;
 	/**
 	 * Authentication id of the owner of the function,
 	 * used for set-user-id functions.
 	 */
-	uint8_t auth_token;
+	struct current_user setuid_user;
 	/** Function name. */
 	char name[BOX_NAME_MAX + 1];
 	/**
diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index 6c661edcb2..00a661a72c 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -471,21 +471,17 @@ struct SetuidGuard
 {
 	/** True if the function was set-user-id one. */
 	bool setuid;
-	/** Original authentication token, only set if setuid = true. */
-	uint8_t orig_auth_token;
-	/** Original user id, only set if setuid = true. */
-	uint32_t orig_uid;
+	struct current_user *orig_user;
 
 	inline SetuidGuard(const char *name, uint32_t name_len,
-			   struct user_def *user, uint8_t access);
+			   uint8_t access);
 	inline ~SetuidGuard();
 };
 
 SetuidGuard::SetuidGuard(const char *name, uint32_t name_len,
-			 struct user_def *user, uint8_t access)
+			 uint8_t access)
 	:setuid(false)
-	,orig_auth_token(GUEST) /* silence gnu warning */
-	,orig_uid(GUEST)
+	,orig_user(current_user())
 {
 
 	/*
@@ -493,9 +489,9 @@ SetuidGuard::SetuidGuard(const char *name, uint32_t name_len,
 	 * No special check for ADMIN user is necessary
 	 * since ADMIN has universal access.
 	 */
-	if (user->universal_access.effective & PRIV_ALL)
+	if (orig_user->universal_access & PRIV_ALL)
 		return;
-	access &= ~user->universal_access.effective;
+	access &= ~orig_user->universal_access;
 	/*
 	 * We need to look up the function by name even if
 	 * the user has access to it, since it could require
@@ -511,40 +507,37 @@ SetuidGuard::SetuidGuard(const char *name, uint32_t name_len,
 		 */
 		return;
 	}
-	if (func == NULL || (func->uid != user->uid &&
-	     access & ~func->access[user->auth_token].effective)) {
+	if (func == NULL || (func->uid != orig_user->uid &&
+	     access & ~func->access[orig_user->auth_token].effective)) {
 		/* Access violation, report error. */
 		char name_buf[BOX_NAME_MAX + 1];
 		snprintf(name_buf, sizeof(name_buf), "%.*s", name_len, name);
+		struct user_def *def = user_cache_find(orig_user->uid);
 
 		tnt_raise(ClientError, ER_FUNCTION_ACCESS_DENIED,
-			  priv_name(access), user->name, name_buf);
+			  priv_name(access), def->name, name_buf);
 	}
 	if (func->setuid) {
 		/** Remember and change the current user id. */
-		if (unlikely(func->auth_token >= BOX_USER_MAX)) {
+		if (unlikely(func->setuid_user.auth_token >= BOX_USER_MAX)) {
 			/*
 			 * Fill the cache upon first access, since
 			 * when func_def is created, no user may
 			 * be around to fill it (recovery of
 			 * system spaces from a snapshot).
 			 */
-			struct user_def *owner = user_by_id(func->uid);
-			assert(owner != NULL); /* checked by user_has_data() */
-			func->auth_token = owner->auth_token;
-			assert(owner->auth_token < BOX_USER_MAX);
+			struct user_def *owner = user_cache_find(func->uid);
+			current_user_init(&func->setuid_user, owner);
 		}
 		setuid = true;
-		orig_auth_token = user->auth_token;
-		orig_uid = user->uid;
-		session_set_user(session(), user_by_token(func->auth_token));
+		fiber_set_user(fiber(), &func->setuid_user);
 	}
 }
 
 SetuidGuard::~SetuidGuard()
 {
 	if (setuid)
-		session_set_user(session(), user_by_token(orig_auth_token));
+		fiber_set_user(fiber(), orig_user);
 }
 
 /**
@@ -554,7 +547,6 @@ SetuidGuard::~SetuidGuard()
 void
 box_lua_call(struct request *request, struct port *port)
 {
-	struct user_def *user = user();
 	lua_State *L = lua_newthread(tarantool_L);
 	LuarefGuard coro_ref(tarantool_L);
 	const char *name = request->key;
@@ -569,7 +561,7 @@ box_lua_call(struct request *request, struct port *port)
 	 * https://github.com/tarantool/tarantool/issues/300
 	 * - if a function does not exist, say it first.
 	 */
-	SetuidGuard setuid(name, name_len, user, PRIV_X);
+	SetuidGuard setuid(name, name_len, PRIV_X);
 	/* Push the rest of args (a tuple). */
 	const char *args = request->tuple;
 	uint32_t arg_count = mp_decode_array(&args);
diff --git a/src/box/lua/session.cc b/src/box/lua/session.cc
index c6f1edff31..c0ac116d54 100644
--- a/src/box/lua/session.cc
+++ b/src/box/lua/session.cc
@@ -56,23 +56,31 @@ static const char *sessionlib_name = "box.session";
 static int
 lbox_session_id(struct lua_State *L)
 {
-	lua_pushnumber(L, session()->id);
+	lua_pushnumber(L, current_session()->id);
 	return 1;
 }
 
-/** Session user id. */
+/**
+ * Session user id.
+ * Note: effective user id (current_user()->uid)
+ * may be different in a setuid function.
+ */
 static int
 lbox_session_uid(struct lua_State *L)
 {
-	lua_pushnumber(L, session()->uid);
+	lua_pushnumber(L, current_session()->user.uid);
 	return 1;
 }
 
-/** Session user id. */
+/**
+ * Session user name.
+ * Note: effective user name may be different in
+ * a setuid function.
+ */
 static int
 lbox_session_user(struct lua_State *L)
 {
-	struct user_def *user = user_by_id(session()->uid);
+	struct user_def *user = user_by_id(current_session()->user.uid);
 	if (user)
 		lua_pushstring(L, user->name);
 	else
@@ -86,7 +94,7 @@ lbox_session_su(struct lua_State *L)
 {
 	if (lua_gettop(L) != 1)
 		luaL_error(L, "session.su(): bad arguments");
-	struct session *session = session();
+	struct session *session = current_session();
 	if (session == NULL)
 		luaL_error(L, "session.su(): session does not exit");
 	struct user_def *user;
@@ -97,7 +105,7 @@ lbox_session_su(struct lua_State *L)
 	} else {
 		user = user_cache_find(lua_tointeger(L, 1));
 	}
-	session_set_user(session, user);
+	current_user_init(&session->user, user);
 	return 0;
 }
 
@@ -143,15 +151,14 @@ lbox_session_peer(struct lua_State *L)
 		luaL_error(L, "session.peer(sid): bad arguments");
 
 	int fd;
-	if (lua_gettop(L) == 1) {
-		struct session *session = session_find(luaL_checkint(L, 1));
-		if (session == NULL)
-			luaL_error(L, "session.peer(): session does not exit");
-		fd = session->fd;
-	} else {
-		fd = session()->fd;
-	}
-
+	struct session *session;
+	if (lua_gettop(L) == 1)
+		session = session_find(luaL_checkint(L, 1));
+	else
+		session = current_session();
+	if (session == NULL)
+		luaL_error(L, "session.peer(): session does not exit");
+	fd = session->fd;
 	if (fd < 0) {
 		lua_pushnil(L); /* no associated peer */
 		return 1;
diff --git a/src/box/recovery.cc b/src/box/recovery.cc
index e39be264d3..cbfa110c2f 100644
--- a/src/box/recovery.cc
+++ b/src/box/recovery.cc
@@ -532,7 +532,6 @@ recovery_finalize(struct recovery_state *r)
  * locally or send to the replica.
  */
 struct wal_watcher {
-	struct session *session;
 	/**
 	 * Rescan the WAL directory in search for new WAL files
 	 * every wal_dir_rescan_delay seconds.
@@ -574,10 +573,15 @@ recovery_rescan_dir(ev_loop * loop, ev_timer *w, int /* revents */)
 	struct wal_watcher *watcher = r->watcher;
 	struct log_io *save_current_wal = r->current_wal;
 
-	/** To process transactions, we need a working session. */
-	fiber_set_session(fiber(), r->watcher->session);
+	/**
+	 * local hot standby is running from an ev
+	 * watcher, without fiber infrastructure (todo: fix),
+	 * but to run queries we need at least a current
+	 * user.
+	 */
+	fiber_set_user(fiber(), &admin_user);
 	int result = recover_remaining_wals(r);
-	fiber_set_session(fiber(), NULL);
+	fiber_set_user(fiber(), NULL);
 	if (result < 0)
 		panic("recover failed: %i", result);
 	if (save_current_wal != r->current_wal) {
@@ -593,9 +597,9 @@ recovery_rescan_file(ev_loop * loop, ev_stat *w, int /* revents */)
 {
 	struct recovery_state *r = (struct recovery_state *) w->data;
 	struct wal_watcher *watcher = r->watcher;
-	fiber_set_session(fiber(), r->watcher->session);
+	fiber_set_user(fiber(), &admin_user);
 	int result = recover_wal(r, r->current_wal);
-	fiber_set_session(fiber(), NULL);
+	fiber_set_user(fiber(), NULL);
 	if (result < 0)
 		panic("recover failed");
 	if (result == LOG_EOF) {
@@ -616,8 +620,6 @@ recovery_follow_local(struct recovery_state *r, ev_tstamp wal_dir_rescan_delay)
 
 	struct wal_watcher  *watcher = r->watcher= &wal_watcher;
 
-	r->watcher->session = session_create(-1, 0);
-
 	ev_timer_init(&watcher->dir_timer, recovery_rescan_dir,
 		      wal_dir_rescan_delay, wal_dir_rescan_delay);
 	watcher->dir_timer.data = watcher->stat.data = r;
@@ -638,8 +640,6 @@ recovery_stop_local(struct recovery_state *r)
 	ev_timer_stop(loop(), &watcher->dir_timer);
 	if (ev_is_active(&watcher->stat))
 		ev_stat_stop(loop(), &watcher->stat);
-	session_destroy(watcher->session);
-	watcher->session = NULL;
 
 	r->watcher = NULL;
 }
diff --git a/src/box/replica.cc b/src/box/replica.cc
index deff8eafbd..727a70999b 100644
--- a/src/box/replica.cc
+++ b/src/box/replica.cc
@@ -40,9 +40,9 @@
 #include "recovery.h"
 #include "xrow.h"
 #include "msgpuck/msgpuck.h"
-#include "session.h"
 #include "box/cluster.h"
 #include "iproto_constants.h"
+#include "box/session.h"
 
 static const int RECONNECT_DELAY = 1.0;
 
@@ -223,8 +223,6 @@ pull_from_remote(va_list ap)
 	struct ev_io coio;
 	struct iobuf *iobuf = NULL;
 	ev_loop *loop = loop();
-	/** This fiber executes transactions. */
-	SessionGuard session_guard(-1, 0);
 
 	coio_init(&coio);
 
diff --git a/src/box/session.cc b/src/box/session.cc
index e790da629e..56f2324f8c 100644
--- a/src/box/session.cc
+++ b/src/box/session.cc
@@ -55,17 +55,18 @@ sid_max()
 }
 
 static void
-session_on_stop(struct trigger * trigger, void *event)
+session_on_stop(struct trigger *trigger, void * /* event */)
 {
-	(void) event;
-	/* Remove on_stop trigger from fiber */
+	/*
+	 * Remove on_stop trigger from the fiber, otherwise the
+	 * fiber will attempt to destroy the trigger eventually,
+	 * after the trigger and its memory is long gone.
+	 */
 	trigger_clear(trigger);
-	struct session *session = fiber_get_session(fiber());
-	if (session == NULL)
-		return;
-	/* Destroy session */
+	struct session *session = (struct session *)
+		fiber_get_key(fiber(), FIBER_KEY_SESSION);
+	/* Destroy the session */
 	session_destroy(session);
-	fiber_set_session(fiber(), NULL);
 }
 
 struct session *
@@ -76,12 +77,10 @@ session_create(int fd, uint64_t cookie)
 	session->id = sid_max();
 	session->fd =  fd;
 	session->cookie = cookie;
-	session->fiber_on_stop = {
-		rlist_nil, session_on_stop, NULL, NULL
-	};
 	/* For on_connect triggers. */
-	session_set_user(session, user_by_token(ADMIN));
-	random_bytes(session->salt, SESSION_SEED_SIZE);
+	current_user_init(&session->user, user_by_token(GUEST));
+	if (fd >= 0)
+		random_bytes(session->salt, SESSION_SEED_SIZE);
 	struct mh_i32ptr_node_t node;
 	node.key = session->id;
 	node.val = session;
@@ -96,11 +95,35 @@ session_create(int fd, uint64_t cookie)
 	return session;
 }
 
+struct session *
+session_create_on_demand()
+{
+	/* Create session on demand */
+	struct session *s = session_create(-1, 0);
+	s->fiber_on_stop = {
+		rlist_nil, session_on_stop, NULL, NULL
+	};
+	/* Add a trigger to destroy session on fiber stop */
+	trigger_add(&fiber()->on_stop, &s->fiber_on_stop);
+	current_user_init(&s->user, user_by_token(ADMIN));
+	fiber_set_session(fiber(), s);
+	fiber_set_user(fiber(), &s->user);
+	return s;
+}
+
+/**
+ * To quickly switch to admin user when executing
+ * on_connect/on_disconnect triggers in iproto.
+ */
+struct current_user admin_user;
+
 void
 session_run_on_disconnect_triggers(struct session *session)
 {
+	struct fiber *fiber = fiber();
 	/* For triggers. */
-	session_set_user(session, user_by_token(ADMIN));
+	fiber_set_session(fiber, session);
+	fiber_set_user(fiber, &admin_user);
 	try {
 		trigger_run(&session_on_disconnect, NULL);
 	} catch (Exception *e) {
@@ -114,9 +137,11 @@ session_run_on_disconnect_triggers(struct session *session)
 void
 session_run_on_connect_triggers(struct session *session)
 {
+	struct fiber *fiber = fiber();
+	fiber_set_session(fiber, session);
+	fiber_set_user(fiber, &admin_user);
 	trigger_run(&session_on_connect, NULL);
 	/* Set session user to guest, until it is authenticated. */
-	session_set_user(session, user_by_token(GUEST));
 }
 
 void
@@ -144,6 +169,7 @@ session_init()
 	if (session_registry == NULL)
 		panic("out of memory");
 	mempool_create(&session_pool, &cord()->slabc, sizeof(struct session));
+	current_user_init(&admin_user, user_by_token(ADMIN));
 }
 
 void
@@ -152,27 +178,3 @@ session_free()
 	if (session_registry)
 		mh_i32ptr_delete(session_registry);
 }
-
-SessionGuard::SessionGuard(int fd, uint64_t cookie)
-{
-	session = session_create(fd, cookie);
-	fiber_set_session(fiber(), session);
-}
-
-SessionGuard::~SessionGuard()
-{
-	assert(session == fiber_get_session(fiber()));
-	session_destroy(session);
-	fiber_set_session(fiber(), NULL);
-}
-
-SessionGuardWithTriggers::SessionGuardWithTriggers(int fd, uint64_t cookie)
-	:SessionGuard(fd, cookie)
-{
-	session_run_on_connect_triggers(session);
-}
-
-SessionGuardWithTriggers::~SessionGuardWithTriggers()
-{
-	session_run_on_disconnect_triggers(session);
-}
diff --git a/src/box/session.h b/src/box/session.h
index 99d583f3b4..ff0c1a1636 100644
--- a/src/box/session.h
+++ b/src/box/session.h
@@ -40,11 +40,11 @@ enum {	SESSION_SEED_SIZE = 32, SESSION_DELIM_SIZE = 16 };
  * Abstraction of a single user session:
  * for now, only provides accounting of established
  * sessions and on-connect/on-disconnect event
- * handling, in future: user credentials, protocol, etc.
+ * handling, user credentials. In future: the
+ * client/server protocol, etc.
  * Session identifiers grow monotonically.
  * 0 sid is reserved to mean 'no session'.
  */
-
 struct session {
 	/** Session id. */
 	uint32_t id;
@@ -54,10 +54,8 @@ struct session {
 	uint64_t cookie;
 	/** Authentication salt. */
 	char salt[SESSION_SEED_SIZE];
-	/** A look up key to quickly find session user. */
-	uint8_t auth_token;
-	/** User id of the authenticated user. */
-	uint32_t uid;
+	/** Cached user id and global grants */
+	struct current_user user;
 	/** Trigger for fiber on_stop to cleanup created on-demand session */
 	struct trigger fiber_on_stop;
 };
@@ -94,14 +92,6 @@ session_destroy(struct session *);
 struct session *
 session_find(uint32_t sid);
 
-/** Set session auth token and user id. */
-static inline void
-session_set_user(struct session *session, struct user_def *user)
-{
-	session->auth_token = user->auth_token;
-	session->uid = user->uid;
-}
-
 /** Global on-connect triggers. */
 extern struct rlist session_on_connect;
 
@@ -125,10 +115,10 @@ session_free();
 void
 session_storage_cleanup(int sid);
 
-static inline struct session *
-fiber_get_session(struct fiber *fiber)
+static inline void
+fiber_set_user(struct fiber *fiber, struct current_user *user)
 {
-	return (struct session *) fiber_get_key(fiber, FIBER_KEY_SESSION);
+	fiber_set_key(fiber, FIBER_KEY_USER, user);
 }
 
 static inline void
@@ -137,29 +127,56 @@ fiber_set_session(struct fiber *fiber, struct session *session)
 	fiber_set_key(fiber, FIBER_KEY_SESSION, session);
 }
 
-#define session() ({\
-	struct session *s = fiber_get_session(fiber());				\
-	/* Create session on demand */						\
-	if (s == NULL) {							\
-		s = session_create(-1, 0);					\
-		fiber_set_session(fiber(), s);					\
-		/* Add a trigger to destroy session on fiber stop */		\
-		trigger_add(&fiber()->on_stop, &s->fiber_on_stop);		\
-	}									\
-	s; })
-
-/** A helper class to create and set session in single-session fibers. */
-struct SessionGuard
+/**
+ * Create a new session on demand, and set fiber on_stop
+ * trigger to destroy it when this fiber ends.
+ */
+struct session *
+session_create_on_demand();
+
+/*
+ * For use in local hot standby, which runs directly
+ * from ev watchers (without current fiber), but needs
+ * to execute transactions.
+ */
+extern struct current_user admin_user;
+
+/*
+ * When creating a new fiber, the database (box)
+ * may not be initialized yet. When later on
+ * this fiber attempts to access the database,
+ * we have no other choice but initialize fiber-specific
+ * database state (something like a database connection)
+ * on demand. This is why this function needs to
+ * check whether or not the current session exists
+ * and create it otherwise.
+ */
+static inline struct session *
+current_session()
 {
-	struct session *session;
-	SessionGuard(int fd, uint64_t cookie);
-	~SessionGuard();
-};
+	struct session *s = (struct session *)
+		fiber_get_key(fiber(), FIBER_KEY_SESSION);
+	return s ? s : session_create_on_demand();
+}
 
-struct SessionGuardWithTriggers: public SessionGuard
+/*
+ * Return the current user. Create it if it doesn't
+ * exist yet.
+ * The same rationale for initializing the current
+ * user on demand as in current_session() applies.
+ */
+static inline struct current_user *
+current_user()
 {
-	SessionGuardWithTriggers(int fd, uint64_t cookie);
-	~SessionGuardWithTriggers();
-};
+	struct current_user *u =
+		(struct current_user *) fiber_get_key(fiber(),
+						      FIBER_KEY_USER);
+	if (u == NULL) {
+		session_create_on_demand();
+		u = (struct current_user *) fiber_get_key(fiber(),
+							  FIBER_KEY_USER);
+	}
+	return u;
+}
 
 #endif /* INCLUDES_TARANTOOL_SESSION_H */
diff --git a/src/box/space.cc b/src/box/space.cc
index 72f5f5490b..0713814cda 100644
--- a/src/box/space.cc
+++ b/src/box/space.cc
@@ -40,7 +40,7 @@
 void
 access_check_space(struct space *space, uint8_t access)
 {
-	struct user_def *user = user();
+	struct current_user *user = current_user();
 	/*
 	 * If a user has a global permission, clear the respective
 	 * privilege from the list of privileges required
@@ -48,11 +48,12 @@ access_check_space(struct space *space, uint8_t access)
 	 * No special check for ADMIN user is necessary
 	 * since ADMIN has universal access.
 	 */
-	access &= ~user->universal_access.effective;
+	access &= ~user->universal_access;
 	if (access && space->def.uid != user->uid &&
 	    access & ~space->access[user->auth_token].effective) {
+		struct user_def *def = user_cache_find(user->uid);
 		tnt_raise(ClientError, ER_SPACE_ACCESS_DENIED,
-			  priv_name(access), user->name, space->def.name);
+			  priv_name(access), def->name, space->def.name);
 	}
 }
 
diff --git a/src/box/user_cache.h b/src/box/user_cache.h
index aa5172cbb5..d3da115278 100644
--- a/src/box/user_cache.h
+++ b/src/box/user_cache.h
@@ -79,21 +79,6 @@ user_cache_find(uint32_t uid);
 struct user_def *
 user_cache_find_by_name(const char *name, uint32_t len);
 
-/**
- * Return the current user.
- */
-#define user()							\
-({								\
-	struct session *s = session();				\
-	struct user_def *u = user_by_token(s->auth_token);	\
-	if (u->auth_token != s->auth_token ||			\
-	    u->uid != s->uid) {					\
-		tnt_raise(ClientError, ER_NO_SUCH_USER,		\
-			  int2str(s->uid));			\
-	}							\
-	u;							\
-})
-
 /** Initialize the user cache and access control subsystem. */
 void
 user_cache_init();
diff --git a/src/box/user_def.h b/src/box/user_def.h
index 31ac5c4905..26de9fa1ad 100644
--- a/src/box/user_def.h
+++ b/src/box/user_def.h
@@ -73,4 +73,12 @@ struct user_def {
 /** Predefined user ids. */
 enum { GUEST = 0, ADMIN =  1, PUBLIC = 2 /* role */ };
 
+static inline void
+current_user_init(struct current_user *user, struct user_def *def)
+{
+	user->auth_token = def->auth_token;
+	user->universal_access = def->universal_access.effective;
+	user->uid = def->uid;
+}
+
 #endif /* TARANTOOL_BOX_USER_DEF_H_INCLUDED */
diff --git a/src/fiber.h b/src/fiber.h
index eda96e01b6..9db3e82477 100644
--- a/src/fiber.h
+++ b/src/fiber.h
@@ -85,7 +85,9 @@ enum fiber_key {
 	FIBER_KEY_LUA_STORAGE = 1,
 	/** transaction */
 	FIBER_KEY_TXN = 2,
-	FIBER_KEY_MAX = 3
+	/** User global privilege and authentication token */
+	FIBER_KEY_USER = 3,
+	FIBER_KEY_MAX = 4
 };
 
 struct fiber {
diff --git a/src/lib/salad/rlist.h b/src/lib/salad/rlist.h
index 7952d07cf5..b9a973b01d 100644
--- a/src/lib/salad/rlist.h
+++ b/src/lib/salad/rlist.h
@@ -47,6 +47,7 @@ struct rlist {
 	struct rlist *next;
 };
 
+/* Used for static initialization of an empty list. */
 extern struct rlist rlist_nil;
 
 /**
diff --git a/src/lua/fiber.cc b/src/lua/fiber.cc
index 6d9e422018..03a155d397 100644
--- a/src/lua/fiber.cc
+++ b/src/lua/fiber.cc
@@ -260,7 +260,7 @@ lbox_fiber_info(struct lua_State *L)
 }
 
 static void
-box_lua_fiber_run_detached(va_list ap)
+box_lua_fiber_run(va_list ap)
 {
 	LuarefGuard coro_guard(va_arg(ap, int));
 	struct lua_State *L = va_arg(ap, struct lua_State *);
@@ -270,7 +270,6 @@ box_lua_fiber_run_detached(va_list ap)
 			fiber_get_key(fiber(), FIBER_KEY_LUA_STORAGE);
 		if (storage_ref > 0)
 			lua_unref(L, storage_ref);
-		fiber_set_key(fiber(), FIBER_KEY_LUA_STORAGE, NULL);
 	});
 
 	try {
@@ -293,7 +292,7 @@ lbox_fiber_create(struct lua_State *L)
 		luaL_error(L, "fiber.create(function, ...): bad arguments");
 	fiber_checkstack();
 
-	struct fiber *f = fiber_new("lua", box_lua_fiber_run_detached);
+	struct fiber *f = fiber_new("lua", box_lua_fiber_run);
 	/* Not a system fiber. */
 	f->flags |= FIBER_USER_MODE;
 	struct lua_State *child_L = lua_newthread(L);
diff --git a/src/lua/init.cc b/src/lua/init.cc
index 17d3cc78f5..cab1410d5d 100644
--- a/src/lua/init.cc
+++ b/src/lua/init.cc
@@ -57,10 +57,6 @@ extern "C" {
 #include "lua/pickle.h"
 #include "lua/fio.h"
 
-#include <ctype.h>
-#include "small/region.h"
-#include <stdio.h>
-#include <unistd.h>
 #include <readline/readline.h>
 #include <readline/history.h>
 
diff --git a/test/app/console.test.lua b/test/app/console.test.lua
index 5a611cc4e4..148926c580 100755
--- a/test/app/console.test.lua
+++ b/test/app/console.test.lua
@@ -72,7 +72,7 @@ test:ok(yaml.decode(client:read(EOL))[1].error:find('access denied'),
 box.schema.user.create('test', { password = 'pass' })
 client:write(string.format("require('console').connect('test:pass@%s')\n",
     IPROTO_SOCKET))
--- error: Execute access denied for user 'tester' to function 'dostring
+-- error: Execute access denied for user 'test' to function 'dostring
 test:ok(yaml.decode(client:read(EOL))[1].error:find('access denied'),
     'remote access denied')
 
diff --git a/test/box/session.storage.result b/test/box/session.storage.result
index b10f215b10..a292e1b2f2 100644
--- a/test/box/session.storage.result
+++ b/test/box/session.storage.result
@@ -31,7 +31,7 @@ all = getmetatable(session).aggregate_storage
 ...
 dump(all)
 ---
-- '''[null,null,null,{"abc":"cde"}]'''
+- '''[null,null,{"abc":"cde"}]'''
 ...
 --# create connection second to default
 --# set connection second
-- 
GitLab