diff --git a/CMakeLists.txt b/CMakeLists.txt
index fd8dcac63c3157995fff317ab21ac1cfd1679a50..357518f3c27a990026b9b55132e9a4b021125115 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -58,6 +58,8 @@ if (NOT READLINE_FOUND)
     message(FATAL_ERROR "readline library not found.")
 endif()
 
+option(ENABLE_VALGRIND "Enable integration with valgrind, a memory analyzing tool" OFF)
+
 check_symbol_exists(MAP_ANON sys/mman.h HAVE_MAP_ANON)
 check_symbol_exists(MAP_ANONYMOUS sys/mman.h HAVE_MAP_ANONYMOUS)
 check_include_file(sys/time.h HAVE_SYS_TIME_H)
@@ -376,6 +378,7 @@ message (STATUS "ENABLE_SSE2: ${ENABLE_SSE2}")
 message (STATUS "ENABLE_AVX: ${ENABLE_AVX}")
 message (STATUS "ENABLE_GCOV: ${ENABLE_GCOV}")
 message (STATUS "ENABLE_GPROF: ${ENABLE_GPROF}")
+message (STATUS "ENABLE_VALGRIND: ${ENABLE_VALGRIND}")
 message (STATUS "ENABLE_TRACE: ${ENABLE_TRACE}")
 message (STATUS "ENABLE_BACKTRACE: ${ENABLE_BACKTRACE} (symbol resolve: ${HAVE_BFD})")
 message (STATUS "ENABLE_BUNDLED_LUAJIT: ${ENABLE_BUNDLED_LUAJIT}")
diff --git a/cmake/luajit.cmake b/cmake/luajit.cmake
index 9a19ec76b6dc3199e4c9b20c3b6ae21685392a10..af55e0a9a11deb3b02672b1d1dfddf6e28fa8df2 100644
--- a/cmake/luajit.cmake
+++ b/cmake/luajit.cmake
@@ -131,14 +131,19 @@ message (STATUS "Use LuaJIT library: ${LUAJIT_LIB}")
 macro(luajit_build)
     set (luajit_buildoptions BUILDMODE=static)
     set (luajit_copt "")
+    set (luajit_xcflags "")
     if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
         set (luajit_buildoptions ${luajit_buildoptions} CCDEBUG=${CC_DEBUG_OPT})
         set (luajit_copt ${luajit_copt} -O1)
-        set (luajit_buildoptions ${luajit_buildoptions} XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT')
+        set (luajit_xcflags ${luajit_xcflags}
+            -DLUA_USE_APICHECK -DLUA_USE_ASSERT)
     else ()
         set (luajit_copt ${luajit_copt} -O2)
     endif()
-    set (luajit_copt ${luajit_copt} -I${PROJECT_SOURCE_DIR}/libobjc)
+    if (ENABLE_VALGRIND)
+        set (luajit_xcflags ${luajit_xcflags}
+            -DLUAJIT_USE_VALGRIND -DLUAJIT_USE_SYSMALLOC)
+    endif()
     set (luajit_target_cc "${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS}")
     # Use external unwind on all platforms.
     set (luajit_target_cc "${luajit_target_cc} -DLUAJIT_UNWIND_EXTERNAL=1")
@@ -164,6 +169,7 @@ macro(luajit_build)
     set (luajit_buildoptions ${luajit_buildoptions}
         CFLAGS=""
         CXXFLAGS=""
+        XCFLAGS="${luajit_xcflags}"
         HOST_CC="${luajit_host_cc}"
         TARGET_CC="${luajit_target_cc}"
         CCOPT="${luajit_copt}")
diff --git a/cmake/profile.cmake b/cmake/profile.cmake
index 94de41788032ed753dca12938923e20a738f11b9..c86a89d3d88f56ca219c2251eb03b33f10cc8a29 100644
--- a/cmake/profile.cmake
+++ b/cmake/profile.cmake
@@ -32,3 +32,12 @@ option(ENABLE_GPROF "Enable integration with gprof, a performance analyzing tool
 if (ENABLE_GPROF)
     add_compile_flags("C;CXX" "-pg")
 endif()
+
+option(ENABLE_VALGRIND "Enable integration with valgrind, a memory analyzing tool" OFF)
+if (ENABLE_VALGRIND)
+    check_include_file(valgrind/valgrind.h HAVE_VALGRIND_VALGRIND_H)
+    if (NOT HAVE_VALGRIND_VALGRIND_H)
+        message (FATAL_ERROR
+             "ENABLE_VALGRIND option is set but valgrind/valgrind.h is not found")
+        endif()
+endif()
diff --git a/src/box/access.h b/src/box/access.h
index 94c5b1d4851f4bd1717f50c5439e64d62261892e..b0cbf366c4cafdc94261813ff62523d5fc363e50 100644
--- a/src/box/access.h
+++ b/src/box/access.h
@@ -41,6 +41,8 @@ enum {
 	PRIV_W = 2,
 	/* CALL */
 	PRIV_X = 4,
+	/** Everything. */
+	PRIV_ALL = PRIV_R + PRIV_W + PRIV_X
 };
 
 /* Privilege name for error messages */
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 9201c9c32d337e09f181154433c742b502bc776e..858d14806e18434a95e91f206055f9888f3e5516 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -62,6 +62,9 @@
 #define PRIV_OBJECT_ID   3
 #define PRIV_ACCESS      4
 
+/** _func columns */
+#define FUNC_SETUID      3
+
 /* {{{ Auxiliary functions and methods. */
 
 void
@@ -1256,7 +1259,7 @@ static struct trigger drop_user_trigger =
 	{ rlist_nil, user_cache_remove_user, NULL, NULL };
 
 static void
-user_cache_replace_user(struct trigger * /* trigger */, void *event)
+user_cache_alter_user(struct trigger * /* trigger */, void *event)
 {
 	struct txn *txn = (struct txn *) event;
 	struct txn_stmt *stmt = txn_stmt(txn);
@@ -1266,7 +1269,7 @@ user_cache_replace_user(struct trigger * /* trigger */, void *event)
 }
 
 static struct trigger modify_user_trigger =
-	{ rlist_nil, user_cache_replace_user, NULL, NULL };
+	{ rlist_nil, user_cache_alter_user, NULL, NULL };
 
 /**
  * A trigger invoked on replace in the user table.
@@ -1324,6 +1327,8 @@ 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->auth_token = user_cache_find(func->uid)->auth_token;
+	func->setuid = false;
 	const char *name = tuple_field_cstr(tuple, NAME);
 	uint32_t len = strlen(name);
 	if (len >= sizeof(func->name)) {
@@ -1333,6 +1338,8 @@ func_def_create_from_tuple(struct func_def *func, struct tuple *tuple)
 	snprintf(func->name, sizeof(func->name), "%s", name);
 	/** Nobody has access to the function but the owner. */
 	memset(func->access, 0, sizeof(func->access));
+	if (tuple_field_count(tuple) > FUNC_SETUID)
+		func->setuid = tuple_field_u32(tuple, FUNC_SETUID);
 }
 
 /** Remove a function from function cache */
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 152e4a08b079cd4643b58c9ae83f175e837cbe21..63e7251357d554a31174d1ddac1f06980d7d7769 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -301,6 +301,16 @@ struct func_def {
 	uint32_t fid;
 	/** Owner of the function. */
 	uint32_t uid;
+	/**
+	 * True if the function requires change of user id before
+	 * invocaction.
+	 */
+	bool setuid;
+	/**
+	 * Authentication id of the owner of the function,
+	 * used for set-user-id functions.
+	 */
+	uint8_t auth_token;
 	/** Function name. */
 	char name[BOX_NAME_MAX + 1];
 	/**
diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index 4a394417a3afd9734ca1b8db70a208784a520f18..8fc9b24539fdc0487bfd979532084ab8ec3d06e8 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -459,26 +459,74 @@ lbox_call_loadproc(struct lua_State *L)
 	return box_lua_find(L, name, name + name_len);
 }
 
-static inline void
-access_check_func(const char *name, uint32_t name_len,
-		  struct user *user, uint8_t access)
+/**
+ * Check access to a function and change the current
+ * user id if the function is a set-definer-user-id one.
+ * The original user is restored in the destructor.
+ */
+struct SetuidGuard
 {
-	access &= ~user->universal_access.effective;
-	 /*
-	  * No special check for ADMIN user is necessary
-	  * since ADMIN has universal access.
-	  */
-	if (access == 0)
+	/** 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;
+
+	inline SetuidGuard(const char *name, uint32_t name_len,
+			   struct user *user, uint8_t access);
+	inline ~SetuidGuard();
+};
+
+SetuidGuard::SetuidGuard(const char *name, uint32_t name_len,
+			 struct user *user, uint8_t access)
+	:setuid(false)
+{
+	/*
+	 * If the user has universal access, don't bother with setuid.
+	 * No special check for ADMIN user is necessary
+	 * since ADMIN has universal access.
+	 */
+	if (user->universal_access.effective & PRIV_ALL)
 		return;
+	access &= ~user->universal_access.effective;
+	/*
+	 * We need to look up the function by name even if
+	 * the user has access to it, since it could require
+	 * a set of other user id.
+	 */
 	struct func_def *func = func_by_name(name, name_len);
+	if (func == NULL && access == 0) {
+		/**
+		 * Well, the function is not explicitly defined,
+		 * so it's obviously not a setuid one. Wasted
+		 * cycles on look up while the user had universal
+		 * access :(
+		 */
+		return;
+	}
 	if (func == NULL || (func->uid != user->uid &&
-		     access & ~func->access[user->auth_token].effective)) {
+	     access & ~func->access[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);
 
 		tnt_raise(ClientError, ER_FUNCTION_ACCESS_DENIED,
 			  priv_name(access), user->name, name_buf);
 	}
+	if (func->setuid) {
+		/** Remember and change the current user id. */
+		setuid = func->setuid;
+		orig_auth_token = user->auth_token;
+		orig_uid = user->uid;
+		session_set_user(session(), func->auth_token, func->uid);
+	}
+}
+
+SetuidGuard::~SetuidGuard()
+{
+	if (setuid)
+		session_set_user(session(), orig_auth_token, orig_uid);
 }
 
 /**
@@ -497,12 +545,13 @@ box_lua_call(struct request *request, struct port *port)
 	/* Try to find a function by name */
 	int oc = box_lua_find(L, name, name + name_len);
 	/**
-	 * Check access to the function. Sic: the order
+	 * Check access to the function and optionally change
+	 * execution time user id (set user id). Sic: the order
 	 * is important, as is described in
 	 * https://github.com/tarantool/tarantool/issues/300
 	 * - if a function does not exist, say it first.
 	 */
-	access_check_func(name, name_len, user, PRIV_X);
+	SetuidGuard setuid(name, name_len, user, 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/schema.lua b/src/box/lua/schema.lua
index c163e470ad0101e6c8d35411c4d3e0d7dc5991ed..bb1e9a46f2f4a0d167c9a0b39baad5a224a7494b 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -911,13 +911,16 @@ local function object_name(object_type, object_id)
 end
 
 box.schema.func = {}
-box.schema.func.create = function(name)
+box.schema.func.create = function(name, opts)
     local _func = box.space[box.schema.FUNC_ID]
     local func = _func.index.name:get{name}
     if func then
             box.error(box.error.FUNCTION_EXISTS, name)
     end
-    _func:auto_increment{session.uid(), name}
+    check_param_table(opts, { setuid = 'boolean' })
+    opts = update_param_table(opts, { setuid = false })
+    opts.setuid = opts.setuid and 1 or 0
+    _func:auto_increment{session.uid(), name, opts.setuid}
 end
 
 box.schema.func.drop = function(name)
@@ -926,8 +929,8 @@ box.schema.func.drop = function(name)
     local fid = object_resolve('function', name)
     local privs = _priv:select{}
     for k, tuple in pairs(privs) do
-        if tuple[2] == 'function' and tuple[3] == function_id then
-            box.schema.user.revoke(tuple[1], tuple[4], tuple[2], tuple[3])
+        if tuple[3] == 'function' and tuple[4] == fid then
+            box.schema.user.revoke(tuple[2], tuple[5], tuple[3], tuple[4])
         end
     end
     _func:delete{fid}
@@ -1082,6 +1085,10 @@ box.schema.role.drop = function(name)
     end
     return box.schema.user.drop(name)
 end
-box.schema.role.grant = box.schema.user.grant
-box.schema.role.revoke = box.schema.user.revoke
-box.schema.role .info = box.schema.user.info
+box.schema.role.grant = function(user_name, role_name, grantor)
+    return box.schema.user.grant(user_name, 'execute', 'role', role_name, grantor)
+end
+box.schema.role.revoke = function(user_name, role_name)
+    return box.schema.user.revoke(user_name, 'execute', 'role', role_name)
+end
+box.schema.role.info = box.schema.user.info
diff --git a/test/box/access.result b/test/box/access.result
index faee3a129f2324c892f37b44d95cf085d2e76ca1..b3310bbb6045b1582819abd0d10c082c1afde2f8 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -389,9 +389,6 @@ box.space._priv:select{id}
 ---
 - []
 ...
-session = nil
----
-...
 -- -----------------------------------------------------------
 -- Be a bit more rigorous in what is accepted in space _user
 -- -----------------------------------------------------------
@@ -413,3 +410,6 @@ box.space._user:insert{10, 1, 'name', 'role', 'password'}
 - error: 'Failed to create role ''name'': authentication data can not be set for a
     role'
 ...
+session = nil
+---
+...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index 347d166c0a3c88166b6755fcc480c7f054662121..5358c7d37b2e5a6272a0b86352ce452d6b5d10c7 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -167,7 +167,6 @@ box.schema.user.grant('user', 'read', 'universe')
 box.space._priv:select{id}
 box.schema.user.drop('user')
 box.space._priv:select{id}
-session = nil
 -- -----------------------------------------------------------
 -- Be a bit more rigorous in what is accepted in space _user
 -- -----------------------------------------------------------
@@ -175,3 +174,4 @@ box.space._user:insert{10, 1, 'name'}
 box.space._user:insert{10, 1, 'name', 'strange-object-type'}
 box.space._user:insert{10, 1, 'name', 'user', 'password'}
 box.space._user:insert{10, 1, 'name', 'role', 'password'}
+session = nil
diff --git a/test/box/access_bin.result b/test/box/access_bin.result
index 1e08ae289fd80d9d12e4f1b2de48722ecfb92b7c..23aa5b2d6f54fc0b81d192f24bec291cd079f5c2 100644
--- a/test/box/access_bin.result
+++ b/test/box/access_bin.result
@@ -28,3 +28,70 @@ c:close()
 box.schema.user.revoke('guest', 'read,write,execute', 'universe')
 ---
 ...
+-- gh-488 suid functions
+--
+setuid_space = box.schema.space.create('setuid_space')
+---
+...
+setuid_space:create_index('primary')
+---
+...
+setuid_func = function() return box.space.setuid_space:auto_increment{} end
+---
+...
+box.schema.func.create('setuid_func')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'setuid_func')
+---
+...
+c = net.box:new(0, box.cfg.listen)
+---
+...
+c:call("setuid_func")
+---
+- error: Read access denied for user 'guest' to space 'setuid_space'
+...
+session.su('guest')
+---
+...
+setuid_func()
+---
+- error: Read access denied for user 'guest' to space 'setuid_space'
+...
+session.su('admin')
+---
+...
+box.schema.func.drop('setuid_func')
+---
+...
+box.schema.func.create('setuid_func', { setuid = true })
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'setuid_func')
+---
+...
+c:call("setuid_func")
+---
+- - [1]
+...
+session.su('guest')
+---
+...
+setuid_func()
+---
+- error: Read access denied for user 'guest' to space 'setuid_space'
+...
+session.su('admin')
+---
+...
+c:close()
+---
+...
+box.schema.func.drop('setuid_func')
+---
+...
+setuid_space:drop()
+---
+...
+--
diff --git a/test/box/access_bin.test.lua b/test/box/access_bin.test.lua
index 692f60e332f787afcdaca4445aa05d82e9c98d03..f9841d7ea50220baa1ddb83ccf11d29607c37b0c 100644
--- a/test/box/access_bin.test.lua
+++ b/test/box/access_bin.test.lua
@@ -10,3 +10,27 @@ c:call("dostring", "session.su('admin')")
 c:call("dostring", "return session.user()")
 c:close()
 box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+
+-- gh-488 suid functions
+--
+setuid_space = box.schema.space.create('setuid_space')
+setuid_space:create_index('primary')
+setuid_func = function() return box.space.setuid_space:auto_increment{} end
+box.schema.func.create('setuid_func')
+box.schema.user.grant('guest', 'execute', 'function', 'setuid_func')
+c = net.box:new(0, box.cfg.listen)
+c:call("setuid_func")
+session.su('guest')
+setuid_func()
+session.su('admin')
+box.schema.func.drop('setuid_func')
+box.schema.func.create('setuid_func', { setuid = true })
+box.schema.user.grant('guest', 'execute', 'function', 'setuid_func')
+c:call("setuid_func")
+session.su('guest')
+setuid_func()
+session.su('admin')
+c:close()
+box.schema.func.drop('setuid_func')
+setuid_space:drop()
+--
diff --git a/test/box/role.result b/test/box/role.result
index 4ca69d3f8b6ecbebcdf2e4587eaa262c4aaef612..aae49703b603f01a4af9bd9c401979bfd03a52be 100644
--- a/test/box/role.result
+++ b/test/box/role.result
@@ -23,15 +23,15 @@ box.session.su('iddqd')
 -- test granting privilege to a role
 box.schema.role.grant('iddqd', 'execute', 'universe')
 ---
+- error: Role 'execute' is not found
 ...
 box.schema.role.info('iddqd')
 ---
-- - - execute
-    - universe
-    - 
+- []
 ...
 box.schema.role.revoke('iddqd', 'execute', 'universe')
 ---
+- error: Role 'execute' is not found
 ...
 box.schema.role.info('iddqd')
 ---
@@ -54,3 +54,19 @@ box.schema.user.info('tester')
     - role
     - iddqd
 ...
+-- test granting user to a user
+box.schema.user.grant('tester', 'execute', 'role', 'tester')
+---
+- error: Role 'tester' is not found
+...
+-- test granting a non-execute grant on a role - error
+box.schema.user.grant('tester', 'write', 'role', 'iddqd')
+---
+...
+box.schema.user.grant('tester', 'read', 'role', 'iddqd')
+---
+...
+-- test granting role to a role
+box.schema.user.grant('iddqd', 'execute', 'role', 'iddqd')
+---
+...
diff --git a/test/box/role.test.lua b/test/box/role.test.lua
index d60b7a6e9a85b2fde4e19737e371e179e0a19f58..9d63d60331337d8dd78be4c1a1d5f84f38023356 100644
--- a/test/box/role.test.lua
+++ b/test/box/role.test.lua
@@ -15,3 +15,10 @@ box.schema.user.create('tester')
 box.schema.user.info('tester')
 box.schema.user.grant('tester', 'execute', 'role', 'iddqd')
 box.schema.user.info('tester')
+-- test granting user to a user
+box.schema.user.grant('tester', 'execute', 'role', 'tester')
+-- test granting a non-execute grant on a role - error
+box.schema.user.grant('tester', 'write', 'role', 'iddqd')
+box.schema.user.grant('tester', 'read', 'role', 'iddqd')
+-- test granting role to a role
+box.schema.user.grant('iddqd', 'execute', 'role', 'iddqd')
diff --git a/third_party/luajit b/third_party/luajit
index 880ca300e8fb7b432b9d25ed377db2102e4cb63d..41156fe1cdd6b60a5e8d9855c57699e89ccfbf97 160000
--- a/third_party/luajit
+++ b/third_party/luajit
@@ -1 +1 @@
-Subproject commit 880ca300e8fb7b432b9d25ed377db2102e4cb63d
+Subproject commit 41156fe1cdd6b60a5e8d9855c57699e89ccfbf97