From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Kirill Shcherbatov Subject: [PATCH v3 3/6] box: rework func object as a function frontend Date: Thu, 13 Jun 2019 17:08:23 +0300 Message-Id: <536bcc9ce5085679877fa91cea36f021168ffc2b.1560433806.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit To: tarantool-patches@freelists.org, vdavydov.dev@gmail.com Cc: Kirill Shcherbatov List-ID: The function func object used to provide a call method only for C functions. In scope of this patch it reworked to be a uniform function call frontend both for C and Lua functions. Introduced classes func_c and func_lua, that provide own constructors which produce implementation-specific object with call and destroy methods. Needed for #4182, #1260 --- src/box/alter.cc | 1 + src/box/call.c | 105 ++--------------------------- src/box/func.c | 165 ++++++++++++++++++++++++++++++++++++++++----- src/box/func.h | 25 ++++--- src/box/func_def.h | 2 + src/box/lua/call.c | 46 +++++++++++++ src/box/lua/call.h | 5 ++ src/box/session.cc | 11 ++- src/box/session.h | 8 +++ 9 files changed, 235 insertions(+), 133 deletions(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 18b50a5da..7b0eb3334 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -2552,6 +2552,7 @@ func_def_new_from_tuple(struct tuple *tuple) func_def_get_ids_from_tuple(tuple, &def->fid, &def->uid); memcpy(def->name, name, len); def->name[len] = 0; + def->name_len = len; if (tuple_field_count(tuple) > BOX_FUNC_FIELD_SETUID) def->setuid = tuple_field_u32_xc(tuple, BOX_FUNC_FIELD_SETUID); else diff --git a/src/box/call.c b/src/box/call.c index 1ab3993da..42db883e1 100644 --- a/src/box/call.c +++ b/src/box/call.c @@ -43,68 +43,6 @@ #include "small/obuf.h" #include "tt_static.h" -/** - * Find a function by name and check "EXECUTE" permissions. - * - * @param name function name - * @param name_len length of @a name - * @param[out] funcp function object - * Sic: *pfunc == NULL means that perhaps the user has a global - * "EXECUTE" privilege, so no specific grant to a function. - * - * @retval -1 on access denied - * @retval 0 on success - */ -static inline int -access_check_func(const char *name, uint32_t name_len, struct func **funcp) -{ - struct func *func = func_by_name(name, name_len); - struct credentials *credentials = effective_user(); - /* - * If the user has universal access, don't bother with checks. - * No special check for ADMIN user is necessary - * since ADMIN has universal access. - */ - if ((credentials->universal_access & (PRIV_X | PRIV_U)) == - (PRIV_X | PRIV_U)) { - - *funcp = func; - return 0; - } - user_access_t access = PRIV_X | PRIV_U; - /* Check access for all functions. */ - access &= ~entity_access_get(SC_FUNCTION)[credentials->auth_token].effective; - user_access_t func_access = access & ~credentials->universal_access; - if (func == NULL || - /* Check for missing Usage access, ignore owner rights. */ - func_access & PRIV_U || - /* Check for missing specific access, respect owner rights. */ - (func->def->uid != credentials->uid && - func_access & ~func->access[credentials->auth_token].effective)) { - - /* Access violation, report error. */ - struct user *user = user_find(credentials->uid); - if (user != NULL) { - if (!(access & credentials->universal_access)) { - diag_set(AccessDeniedError, - priv_name(PRIV_U), - schema_object_name(SC_UNIVERSE), "", - user->def->name); - } else { - diag_set(AccessDeniedError, - priv_name(PRIV_X), - schema_object_name(SC_FUNCTION), - tt_cstr(name, name_len), - user->def->name); - } - } - return -1; - } - - *funcp = func; - return 0; -} - int box_module_reload(const char *name) { @@ -137,53 +75,18 @@ box_process_call(struct call_request *request, struct port *port) assert(name != NULL); uint32_t name_len = mp_decode_strl(&name); - struct func *func = NULL; - /** - * Sic: func == NULL means that perhaps the user has a global - * "EXECUTE" privilege, so no specific grant to a function. - */ - if (access_check_func(name, name_len, &func) != 0) - return -1; /* permission denied */ - - /** - * Change the current user id if the function is - * a set-definer-uid one. If the function is not - * defined, it's obviously not a setuid one. - */ - struct credentials *orig_credentials = NULL; - if (func && func->def->setuid) { - orig_credentials = effective_user(); - /* Remember and change the current user id. */ - if (func->owner_credentials.auth_token >= BOX_USER_MAX) { - /* - * Fill the cache upon first access, since - * when func is created, no user may - * be around to fill it (recovery of - * system spaces from a snapshot). - */ - struct user *owner = user_find(func->def->uid); - if (owner == NULL) - return -1; - credentials_init(&func->owner_credentials, - owner->auth_token, - owner->def->uid); - } - fiber_set_user(fiber(), &func->owner_credentials); - } - int rc; struct port in_port; port_msgpack_create(&in_port, request->args, request->args_end - request->args); - if (func && func->def->language == FUNC_LANGUAGE_C) { + struct func *func = func_by_name(name, name_len); + if (func != NULL) { rc = func_call(func, &in_port, port); - } else { + } else if ((rc = access_check_universe_object(PRIV_X | PRIV_U, + SC_FUNCTION, tt_cstr(name, name_len))) == 0) { rc = box_lua_call(name, name_len, &in_port, port); } port_destroy(&in_port); - /* Restore the original user */ - if (orig_credentials) - fiber_set_user(fiber(), orig_credentials); if (rc != 0) { txn_rollback(); diff --git a/src/box/func.c b/src/box/func.c index 740fad3d7..20ff56668 100644 --- a/src/box/func.c +++ b/src/box/func.c @@ -32,11 +32,14 @@ #include "trivia/config.h" #include "assoc.h" #include "lua/utils.h" +#include "lua/call.h" #include "error.h" #include "diag.h" #include "fiber.h" #include "port.h" #include "txn.h" +#include "schema.h" +#include "session.h" #include /** @@ -51,6 +54,24 @@ struct func_name { const char *package_end; }; +struct func_c { + /** Function object base class. */ + struct func base; + /** + * Anchor for module membership. + */ + struct rlist item; + /** + * For C functions, the body of the function. + */ + box_function_f func; + /** + * Each stored function keeps a handle to the + * dynamic library for the C callback. + */ + struct module *module; +}; + /*** * Split function name to symbol and package names. * For example, str = foo.bar.baz => sym = baz, package = foo.bar @@ -315,10 +336,10 @@ module_reload(const char *package, const char *package_end, struct module **modu if (new_module == NULL) return -1; - struct func *func, *tmp_func; + struct func_c *func, *tmp_func; rlist_foreach_entry_safe(func, &old_module->funcs, item, tmp_func) { struct func_name name; - func_split_name(func->def->name, &name); + func_split_name(func->base.def->name, &name); func->func = module_sym(new_module, name.sym); if (func->func == NULL) goto restore; @@ -339,7 +360,7 @@ restore: */ do { struct func_name name; - func_split_name(func->def->name, &name); + func_split_name(func->base.def->name, &name); func->func = module_sym(old_module, name.sym); if (func->func == NULL) { /* @@ -352,20 +373,27 @@ restore: func->module = old_module; rlist_move(&old_module->funcs, &func->item); } while (func != rlist_first_entry(&old_module->funcs, - struct func, item)); + struct func_c, item)); assert(rlist_empty(&new_module->funcs)); module_delete(new_module); return -1; } +struct func * +func_c_new(struct func_def *def); + struct func * func_new(struct func_def *def) { - struct func *func = (struct func *) malloc(sizeof(struct func)); - if (func == NULL) { - diag_set(OutOfMemory, sizeof(*func), "malloc", "func"); - return NULL; + struct func *func; + if (def->language == FUNC_LANGUAGE_C) { + func = func_c_new(def); + } else { + assert(def->language == FUNC_LANGUAGE_LUA); + func = func_lua_new(def); } + if (func == NULL) + return NULL; func->def = def; /** Nobody has access to the function but the owner. */ memset(func->access, 0, sizeof(func->access)); @@ -381,19 +409,39 @@ func_new(struct func_def *def) * checks (see user_has_data()). */ func->owner_credentials.auth_token = BOX_USER_MAX; /* invalid value */ + return func; +} + +static struct func_vtab func_c_vtab; + +struct func * +func_c_new(struct func_def *def) +{ + (void) def; + assert(def->language == FUNC_LANGUAGE_C); + struct func_c *func = (struct func_c *) malloc(sizeof(struct func_c)); + if (func == NULL) { + diag_set(OutOfMemory, sizeof(*func), "malloc", "func"); + return NULL; + } + func->base.vtab = &func_c_vtab; func->func = NULL; func->module = NULL; - return func; + return &func->base; } static void -func_unload(struct func *func) +func_c_unload(struct func *base) { + assert(base->vtab == &func_c_vtab); + assert(base != NULL && base->def->language == FUNC_LANGUAGE_C); + struct func_c *func = (struct func_c *) base; + if (func->module) { rlist_del(&func->item); if (rlist_empty(&func->module->funcs)) { struct func_name name; - func_split_name(func->def->name, &name); + func_split_name(func->base.def->name, &name); module_cache_del(name.package, name.package_end); } module_gc(func->module); @@ -407,12 +455,15 @@ func_unload(struct func *func) * symbol from it). */ static int -func_load(struct func *func) +func_c_load(struct func *base) { + assert(base->vtab == &func_c_vtab); + assert(base != NULL && base->def->language == FUNC_LANGUAGE_C); + struct func_c *func = (struct func_c *) base; assert(func->func == NULL); struct func_name name; - func_split_name(func->def->name, &name); + func_split_name(func->base.def->name, &name); struct module *module = module_cache_find(name.package, name.package_end); @@ -436,13 +487,16 @@ func_load(struct func *func) } int -func_call(struct func *func, struct port *in_port, struct port *out_port) +func_c_call(struct func *base, struct port *in_port, struct port *out_port) { - assert(func != NULL && func->def->language == FUNC_LANGUAGE_C); + assert(base->vtab == &func_c_vtab); + assert(base != NULL && base->def->language == FUNC_LANGUAGE_C); + struct func_c *func = (struct func_c *) base; + /* Transaction is not started. */ assert(!in_txn()); if (func->func == NULL) { - if (func_load(func) != 0) + if (func_c_load(base) != 0) return -1; } @@ -472,10 +526,87 @@ func_call(struct func *func, struct port *in_port, struct port *out_port) return rc; } +static struct func_vtab func_c_vtab = { + .call = func_c_call, + .destroy = func_c_unload, +}; + void func_delete(struct func *func) { - func_unload(func); + func->vtab->destroy(func); free(func->def); free(func); } + +/** Check "EXECUTE" permissions for a given function. */ +static int +func_access_check(struct func *base) +{ + struct credentials *credentials = effective_user(); + /* + * If the user has universal access, don't bother with + * checks. No special check for ADMIN user is necessary + * since ADMIN has universal access. + */ + if ((credentials->universal_access & (PRIV_X | PRIV_U)) == + (PRIV_X | PRIV_U)) + return 0; + user_access_t access = PRIV_X | PRIV_U; + /* Check access for all functions. */ + access &= ~entity_access_get(SC_FUNCTION)[ + credentials->auth_token].effective; + user_access_t func_access = access & ~credentials->universal_access; + if (/* Check for missing Usage access, ignore owner rights. */ + func_access & PRIV_U || + /* Check for missing specific access, respect owner rights. */ + (base->def->uid != credentials->uid && + func_access & ~base->access[credentials->auth_token].effective)) { + /* Access violation, report error. */ + struct user *user = user_find(credentials->uid); + if (user != NULL) { + diag_set(AccessDeniedError, priv_name(PRIV_X), + schema_object_name(SC_FUNCTION), + base->def->name, user->def->name); + } + return -1; + } + return 0; +} + +int +func_call(struct func *base, struct port *in_port, struct port *out_port) +{ + if (func_access_check(base) != 0) + return -1; + /** + * Change the current user id if the function is + * a set-definer-uid one. If the function is not + * defined, it's obviously not a setuid one. + */ + struct credentials *orig_credentials = NULL; + if (base->def->setuid) { + orig_credentials = effective_user(); + /* Remember and change the current user id. */ + if (base->owner_credentials.auth_token >= BOX_USER_MAX) { + /* + * Fill the cache upon first access, since + * when func is created, no user may + * be around to fill it (recovery of + * system spaces from a snapshot). + */ + struct user *owner = user_find(base->def->uid); + if (owner == NULL) + return -1; + credentials_init(&base->owner_credentials, + owner->auth_token, + owner->def->uid); + } + fiber_set_user(fiber(), &base->owner_credentials); + } + int rc = base->vtab->call(base, in_port, out_port); + /* Restore the original user */ + if (orig_credentials) + fiber_set_user(fiber(), orig_credentials); + return rc; +} diff --git a/src/box/func.h b/src/box/func.h index d38a2b283..1271bde67 100644 --- a/src/box/func.h +++ b/src/box/func.h @@ -43,6 +43,7 @@ extern "C" { #endif /* defined(__cplusplus) */ struct port; +struct func; struct box_function_ctx { struct port *port; @@ -62,24 +63,22 @@ struct module { bool is_unloading; }; +/** Virtual method table for func object. */ +struct func_vtab { + /** Call function with given arguments. */ + int (*call)(struct func *func, struct port *in_port, + struct port *out_port); + /** Release implementation-specific function context. */ + void (*destroy)(struct func *func); +}; + /** * Stored function. */ struct func { struct func_def *def; - /** - * Anchor for module membership. - */ - struct rlist item; - /** - * For C functions, the body of the function. - */ - box_function_f func; - /** - * Each stored function keeps a handle to the - * dynamic library for the C callback. - */ - struct module *module; + /** Virtual method table. */ + struct func_vtab *vtab; /** * Authentication id of the owner of the function, * used for set-user-id functions. diff --git a/src/box/func_def.h b/src/box/func_def.h index 4c9738aea..7df6678d8 100644 --- a/src/box/func_def.h +++ b/src/box/func_def.h @@ -67,6 +67,8 @@ struct func_def { * The language of the stored function. */ enum func_language language; + /** The length of the function name. */ + uint32_t name_len; /** Function name. */ char name[0]; }; diff --git a/src/box/lua/call.c b/src/box/lua/call.c index e32845095..8d0328ef7 100644 --- a/src/box/lua/call.c +++ b/src/box/lua/call.c @@ -31,6 +31,8 @@ #include "box/lua/call.h" #include "box/call.h" #include "box/error.h" +#include "box/func.h" +#include "box/func_def.h" #include "fiber.h" #include "lua/utils.h" @@ -523,6 +525,50 @@ box_lua_eval(const char *expr, uint32_t expr_len, return box_process_lua(execute_lua_eval, &ctx, in_port, out_port); } +struct func_lua { + /** Function object base class. */ + struct func base; +}; + +static struct func_vtab func_lua_vtab; + +struct func * +func_lua_new(struct func_def *def) +{ + (void) def; + assert(def->language == FUNC_LANGUAGE_LUA); + struct func_lua *func = + (struct func_lua *) malloc(sizeof(struct func_lua)); + if (func == NULL) { + diag_set(OutOfMemory, sizeof(*func), "malloc", "func"); + return NULL; + } + func->base.vtab = &func_lua_vtab; + return &func->base; +} + +static void +func_lua_unload(struct func *base) +{ + assert(base != NULL && base->def->language == FUNC_LANGUAGE_LUA); + assert(base->vtab == &func_lua_vtab); + (void) base; +} + +static inline int +func_lua_call(struct func *func, struct port *in_port, struct port *out_port) +{ + assert(func != NULL && func->def->language == FUNC_LANGUAGE_LUA); + assert(func->vtab == &func_lua_vtab); + return box_lua_call(func->def->name, func->def->name_len, + in_port, out_port); +} + +static struct func_vtab func_lua_vtab = { + .call = func_lua_call, + .destroy = func_lua_unload, +}; + static int lbox_module_reload(lua_State *L) { diff --git a/src/box/lua/call.h b/src/box/lua/call.h index 1801d5090..ef881a048 100644 --- a/src/box/lua/call.h +++ b/src/box/lua/call.h @@ -44,6 +44,7 @@ box_lua_call_init(struct lua_State *L); struct port; struct call_request; +struct func_def; /** * Invoke a Lua stored procedure from the binary protocol @@ -57,6 +58,10 @@ int box_lua_eval(const char *expr, uint32_t expr_len, struct port *in_port, struct port *out_port); +/** Construct a Lua function object. */ +struct func * +func_lua_new(struct func_def *def); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/src/box/session.cc b/src/box/session.cc index 4bb13d031..7ec03b4d0 100644 --- a/src/box/session.cc +++ b/src/box/session.cc @@ -297,7 +297,8 @@ access_check_session(struct user *user) } int -access_check_universe(user_access_t access) +access_check_universe_object(user_access_t access, enum schema_object_type type, + const char *object_name) { struct credentials *credentials = effective_user(); access |= PRIV_U; @@ -313,7 +314,7 @@ access_check_universe(user_access_t access) if (user != NULL) { diag_set(AccessDeniedError, priv_name(denied_access), - schema_object_name(SC_UNIVERSE), "", + schema_object_name(type), object_name, user->def->name); } else { /* @@ -328,6 +329,12 @@ access_check_universe(user_access_t access) return 0; } +int +access_check_universe(user_access_t access) +{ + return access_check_universe_object(access, SC_UNIVERSE, ""); +} + int generic_session_push(struct session *session, uint64_t sync, struct port *port) { diff --git a/src/box/session.h b/src/box/session.h index 3a7397146..6bafb8561 100644 --- a/src/box/session.h +++ b/src/box/session.h @@ -312,6 +312,14 @@ access_check_session(struct user *user); int access_check_universe(user_access_t access); +/** + * Check whether or not the current user can be granted + * the requested access to the universe object. + */ +int +access_check_universe_object(user_access_t access, enum schema_object_type type, + const char *object_name); + static inline int session_push(struct session *session, uint64_t sync, struct port *port) { -- 2.21.0