From: Ilya Markov <imarkov@tarantool.org> To: georgy@tarantool.org Cc: tarantool-patches@freelists.org Subject: [tarantool-patches] [security 1/1] security: Refactor system space access checks Date: Thu, 29 Mar 2018 12:54:34 +0300 [thread overview] Message-ID: <8d1e74cf161d8f9fdb313cd8721f009ad99a2097.1522317191.git.imarkov@tarantool.org> (raw) From: imarkov <imarkov@tarantool.org> Processes of creating, dropping objects, granting contain work with system spaces which so far must be available only to admin or user who has write access to universe. This patch removes the need of special accesses to universe. Introduce virtual access_check function in space object and separate its implementation for user spaces and system ones. Move checks on write to system spaces to on_replace triggers. Follow-up #945 Closes #3250 --- branch gh-3250-system-space-access src/box/alter.cc | 286 ++++++++++++++++++++++++++---------------- src/box/lua/schema.lua | 12 +- src/box/schema.cc | 104 +++++++++++++++ src/box/schema.h | 14 +++ src/box/space.c | 3 +- src/box/space.h | 17 ++- src/box/sysview_engine.c | 1 + src/box/user.h | 7 ++ test/box/access.result | 34 ++--- test/box/access.test.lua | 15 ++- test/box/access_misc.result | 46 +++++-- test/box/access_misc.test.lua | 11 +- test/box/net.box.result | 6 + test/box/net.box.test.lua | 2 + test/box/role.result | 25 +++- test/box/role.test.lua | 13 +- test/box/sequence.result | 17 ++- test/box/sequence.test.lua | 10 +- test/engine/truncate.result | 2 +- 19 files changed, 450 insertions(+), 175 deletions(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 8455373..f1a4001 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -64,50 +64,37 @@ static void access_check_ddl(const char *name, uint32_t owner_uid, enum schema_object_type type, - enum priv_type priv_type, - bool is_17_compat_mode) + enum priv_type priv_type, uint32_t system_space_id) { struct credentials *cr = effective_user(); user_access_t has_access = cr->universal_access; + + struct space *space = space_by_id(system_space_id); + if (space != NULL) { + has_access |= space->access[cr->auth_token].effective; + } /* - * XXX: pre 1.7.7 there was no specific 'CREATE' or - * 'ALTER' ACL, instead, read and write access on universe - * was used to allow create/alter. - * For backward compatibility, if a user has read and write - * access on the universe, grant it CREATE access - * automatically. - * The legacy fix does not affect sequences since they - * were added in 1.7.7 only. + * If a user has write access on the universe, + * grant it CREATE, DROP, ALTER access automatically. */ - if (is_17_compat_mode && has_access & PRIV_R && has_access & PRIV_W) - has_access |= PRIV_C | PRIV_A; + if (has_access & PRIV_W) + has_access |= PRIV_C | PRIV_A | PRIV_D; - user_access_t access = ((PRIV_U | (user_access_t) priv_type) & - ~has_access); + user_access_t access = ((user_access_t) priv_type & ~has_access); bool is_owner = owner_uid == cr->uid || cr->uid == ADMIN; /* * Only the owner of the object or someone who has - * specific DDL privilege on the object can execute - * DDL. If a user has no USAGE access and is owner, - * deny access as well. + * specific DDL privilege on the object can execute DDL. */ - if (access == 0 || (is_owner && !(access & PRIV_U))) + if (access == 0 || is_owner) return; /* Access granted. */ struct user *user = user_find_xc(cr->uid); - if (is_owner) { - tnt_raise(AccessDeniedError, - priv_name(PRIV_U), - schema_object_name(SC_UNIVERSE), - "", - user->def->name); - } else { - tnt_raise(AccessDeniedError, - priv_name(access), - schema_object_name(type), - name, - user->def->name); - } + tnt_raise(AccessDeniedError, + priv_name(access), + schema_object_name(type), + name, + user->def->name); } /** @@ -1496,7 +1483,9 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) struct space_def *def = space_def_new_from_tuple(new_tuple, ER_CREATE_SPACE, region); - access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_C, true); + /* When object is not created, we assume ADMIN owns it */ + access_check_ddl(def->name, ADMIN, SC_SPACE, PRIV_C, + BOX_SPACE_ID); auto def_guard = make_scoped_guard([=] { space_def_delete(def); }); RLIST_HEAD(empty_list); @@ -1523,7 +1512,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) txn_on_rollback(txn, on_rollback); } else if (new_tuple == NULL) { /* DELETE */ access_check_ddl(old_space->def->name, old_space->def->uid, - SC_SPACE, PRIV_D, true); + SC_SPACE, PRIV_D, BOX_SPACE_ID); /* Verify that the space is empty (has no indexes) */ if (old_space->index_count) { tnt_raise(ClientError, ER_DROP_SPACE, @@ -1562,7 +1551,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) struct space_def *def = space_def_new_from_tuple(new_tuple, ER_ALTER_SPACE, region); - access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_A, true); + access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_A, + BOX_SPACE_ID); auto def_guard = make_scoped_guard([=] { space_def_delete(def); }); /* @@ -1660,7 +1650,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) if (old_tuple && new_tuple) priv_type = PRIV_A; access_check_ddl(old_space->def->name, old_space->def->uid, SC_SPACE, - priv_type, true); + priv_type, BOX_SPACE_ID); struct index *old_index = space_index(old_space, iid); /* @@ -1894,10 +1884,16 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event) space_name(old_space)); /* - * Check if a write privilege was given, raise an error if not. + * Perform checks specific for space. Then perform usual check_ddl. */ - access_check_space_xc(old_space, PRIV_W); - + credentials *cr = effective_user(); + uint32_t has_access = cr->universal_access | + old_space->access[cr->auth_token].effective; + if (!(has_access & (PRIV_W | PRIV_D))) { + access_check_ddl(old_space->def->name, old_space->def->uid, + SC_SPACE, + PRIV_D, BOX_SPACE_ID); + } /* * Truncate counter is updated - truncate the space. */ @@ -2133,7 +2129,9 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event) struct user *old_user = user_by_id(uid); if (new_tuple != NULL && old_user == NULL) { /* INSERT */ struct user_def *user = user_def_new_from_tuple(new_tuple); - access_check_ddl(user->name, user->owner, SC_USER, PRIV_C, true); + /* When object is not created, we assume ADMIN owns it */ + access_check_ddl(user->name, ADMIN, SC_USER, PRIV_C, + BOX_USER_ID); auto def_guard = make_scoped_guard([=] { free(user); }); (void) user_cache_replace(user); def_guard.is_active = false; @@ -2142,7 +2140,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event) txn_on_rollback(txn, on_rollback); } else if (new_tuple == NULL) { /* DELETE */ access_check_ddl(old_user->def->name, old_user->def->owner, - SC_USER, PRIV_D, true); + SC_USER, PRIV_D, BOX_USER_ID); /* Can't drop guest or super user */ if (uid <= (uint32_t) BOX_SYSTEM_USER_ID_MAX || uid == SUPER) { tnt_raise(ClientError, ER_DROP_USER, @@ -2169,7 +2167,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event) */ struct user_def *user = user_def_new_from_tuple(new_tuple); access_check_ddl(user->name, user->uid, SC_USER, PRIV_A, - true); + BOX_USER_ID); auto def_guard = make_scoped_guard([=] { free(user); }); struct trigger *on_commit = txn_alter_trigger_new(user_cache_alter_user, NULL); @@ -2271,7 +2269,9 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) struct func *old_func = func_by_id(fid); if (new_tuple != NULL && old_func == NULL) { /* INSERT */ struct func_def *def = func_def_new_from_tuple(new_tuple); - access_check_ddl(def->name, def->uid, SC_FUNCTION, PRIV_C, true); + /* When object is not created, we assume ADMIN owns it */ + access_check_ddl(def->name, ADMIN, SC_FUNCTION, PRIV_C, + BOX_FUNC_ID); auto def_guard = make_scoped_guard([=] { free(def); }); func_cache_replace(def); def_guard.is_active = false; @@ -2285,8 +2285,8 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) * Can only delete func if you're the one * who created it or a superuser. */ - access_check_ddl(old_func->def->name, uid, SC_FUNCTION, - PRIV_D, true); + access_check_ddl(old_func->def->name, uid, SC_FUNCTION, PRIV_D, + BOX_FUNC_ID); /* Can only delete func if it has no grants. */ if (schema_find_grants("function", old_func->def->fid)) { tnt_raise(ClientError, ER_DROP_FUNCTION, @@ -2300,7 +2300,7 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) struct func_def *def = func_def_new_from_tuple(new_tuple); auto def_guard = make_scoped_guard([=] { free(def); }); access_check_ddl(def->name, def->uid, SC_FUNCTION, PRIV_A, - true); + BOX_FUNC_ID); struct trigger *on_commit = txn_alter_trigger_new(func_cache_replace_func, NULL); txn_on_commit(txn, on_commit); @@ -2436,17 +2436,21 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event) struct tuple *new_tuple = stmt->new_tuple; txn_check_singlestatement_xc(txn, "Space _collation"); struct coll *old_coll = NULL; + struct credentials *cr = effective_user(); if (old_tuple != NULL) { /* TODO: Check that no index uses the collation */ uint32_t old_id = tuple_field_u32_xc(old_tuple, BOX_COLLATION_FIELD_ID); old_coll = coll_by_id(old_id); assert(old_coll != NULL); - access_check_ddl(old_coll->name, old_coll->owner_id, - SC_COLLATION, - new_tuple == NULL ? PRIV_D: PRIV_A, - false); - + if (cr->uid != ADMIN && old_coll->owner_id != cr->uid) { + struct user *user = user_find_xc(cr->uid); + tnt_raise(AccessDeniedError, + priv_name(new_tuple == NULL ? PRIV_D : PRIV_A), + schema_object_name(SC_COLLATION), + old_coll->name, + user->def->name); + } struct trigger *on_commit = txn_alter_trigger_new(coll_cache_delete_coll, old_coll); txn_on_commit(txn, on_commit); @@ -2465,8 +2469,14 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event) struct coll_def new_def; coll_def_new_from_tuple(new_tuple, &new_def); - access_check_ddl(new_def.name, new_def.owner_id, SC_COLLATION, - old_tuple == NULL ? PRIV_C : PRIV_A, false); + if (cr->uid != ADMIN && new_def.owner_id != cr->uid) { + struct user *user = user_find_xc(new_def.owner_id); + tnt_raise(AccessDeniedError, + priv_name(old_tuple == NULL ? PRIV_C : PRIV_A), + schema_object_name(SC_COLLATION), + new_def.name, + user->def->name); + } struct coll *new_coll = coll_new(&new_def); if (new_coll == NULL) diag_raise(); @@ -2509,6 +2519,35 @@ priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple) priv->access = tuple_field_u32_xc(tuple, BOX_PRIV_FIELD_ACCESS); } +/** + * Handles case, when current user is not an owner of object and not ADMIN + * and wants to add or modify privileges. + * + * The strategy is following: + * When user has write, create, drop privileges in hierarchy over object, + * she is able to pass privileges. + * This case is required for creating, dropping users. + * TODO: we have to diffirentiate granting, revoking options. As so far, + * this is security breach, user who has create privilege may delete privileges + * of other users. Moreover, we have to differentiate promotion/demotion in altering + * privillege. + */ +static inline void +priv_check_not_owner(struct user *grantor, struct priv_def *priv, + const char *name, enum priv_type priv_type) +{ + uint32_t access = schema_find_access(priv->object_type, + priv->object_id, + grantor); + if (priv->grantee_id == ADMIN || !(access & (PRIV_W | PRIV_C | PRIV_D))) { + tnt_raise(AccessDeniedError, + priv_name(priv_type), + schema_object_name(priv->object_type), + name, + grantor->def->name); + } +} + /* * This function checks that: * - a privilege is granted from an existing user to an existing @@ -2522,35 +2561,33 @@ priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple) static void priv_def_check(struct priv_def *priv, enum priv_type priv_type) { - struct user *grantor = user_find_xc(priv->grantor_id); /* May be a role */ struct user *grantee = user_by_id(priv->grantee_id); + credentials *cr = effective_user(); + struct user *grantor = user_find_xc(cr->uid); + if (grantee == NULL) { tnt_raise(ClientError, ER_NO_SUCH_USER, int2str(priv->grantee_id)); } const char *name = schema_find_name(priv->object_type, priv->object_id); - access_check_ddl(name, grantor->def->uid, priv->object_type, priv_type, - false); + switch (priv->object_type) { case SC_UNIVERSE: + { if (grantor->def->uid != ADMIN) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_UNIVERSE), - name, - grantor->def->name); + priv_check_not_owner(grantor, priv, name, priv_type); } break; + } case SC_SPACE: { - struct space *space = space_cache_find_xc(priv->object_id); + struct space *space = space_cache_find_xc( + priv->object_id); if (space->def->uid != grantor->def->uid && grantor->def->uid != ADMIN) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_SPACE), name, - grantor->def->name); + priv_check_not_owner(grantor, priv, name, + priv_type); } break; } @@ -2559,22 +2596,19 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type) struct func *func = func_cache_find(priv->object_id); if (func->def->uid != grantor->def->uid && grantor->def->uid != ADMIN) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_FUNCTION), name, - grantor->def->name); + priv_check_not_owner(grantor, priv, name, + priv_type); } break; } case SC_SEQUENCE: { - struct sequence *seq = sequence_cache_find(priv->object_id); + struct sequence *seq = sequence_cache_find( + priv->object_id); if (seq->def->uid != grantor->def->uid && grantor->def->uid != ADMIN) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_SEQUENCE), name, - grantor->def->name); + priv_check_not_owner(grantor, priv, name, + priv_type); } break; } @@ -2587,16 +2621,23 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type) int2str(priv->object_id)); } /* - * Only the creator of the role can grant or revoke it. - * Everyone can grant 'PUBLIC' role. + * Only the creator or owner of grantee of the role + * or user with appropriate privileges can grant or revoke it. + * If role is 'PUBLIC' it is allowed to grant for anyone, + * Revoke 'PUBLIC' is allowed only to users who has drop access. + * The latter case is require for dropping users. */ if (role->def->owner != grantor->def->uid && - grantor->def->uid != ADMIN && - (role->def->uid != PUBLIC || priv->access != PRIV_X)) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_ROLE), name, - grantor->def->name); + grantor->def->uid != grantee->def->owner && + !((role->def->uid == PUBLIC) && + (((cr->universal_access & PRIV_D) + || priv_type == PRIV_GRANT) + && priv->access == PRIV_X))) { + tnt_raise(AccessDeniedError, + priv_name(priv_type), + schema_object_name(SC_ROLE), + name, + grantor->def->name); } /* Not necessary to do during revoke, but who cares. */ role_check(grantee, role); @@ -2713,6 +2754,7 @@ on_replace_dd_priv(struct trigger * /* trigger */, void *event) } else { /* modify */ priv_def_create_from_tuple(&priv, new_tuple); priv_def_check(&priv, PRIV_GRANT); + /*TODO: check that modified by owner of object or ADMIN or grantor of privillege*/ struct trigger *on_commit = txn_alter_trigger_new(modify_priv, NULL); txn_on_commit(txn, on_commit); @@ -2742,31 +2784,54 @@ on_replace_dd_schema(struct trigger * /* trigger */, void *event) struct tuple *new_tuple = stmt->new_tuple; const char *key = tuple_field_cstr_xc(new_tuple ? new_tuple : old_tuple, BOX_SCHEMA_FIELD_KEY); - if (strcmp(key, "cluster") == 0) { - if (new_tuple == NULL) - tnt_raise(ClientError, ER_REPLICASET_UUID_IS_RO); - tt_uuid uu; - tuple_field_uuid_xc(new_tuple, BOX_CLUSTER_FIELD_UUID, &uu); - REPLICASET_UUID = uu; - } else if (strcmp(key, "version") == 0) { - if (new_tuple != NULL) { - uint32_t major, minor, patch; - if (tuple_field_u32(new_tuple, 1, &major) != 0 || - tuple_field_u32(new_tuple, 2, &minor) != 0) - tnt_raise(ClientError, ER_WRONG_DD_VERSION); - /* Version can be major.minor with no patch. */ - if (tuple_field_u32(new_tuple, 3, &patch) != 0) - patch = 0; - dd_version_id = version_id(major, minor, patch); - } else { - assert(old_tuple != NULL); - /* - * _schema:delete({'version'}) for - * example, for box.internal.bootstrap(). - */ - dd_version_id = tarantool_version_id(); + credentials *cr = effective_user(); + if (strncmp(key, "max_id", 6) == 0) { + if (!(cr->universal_access & (PRIV_W | PRIV_C))) + goto error; + } else { + uint32_t access = cr->universal_access; + struct space *schema_space = space_by_id(BOX_SCHEMA_ID); + access |= schema_space->access[cr->auth_token].effective; + if (~access & PRIV_W) + goto error; + else if (strcmp(key, "cluster") == 0) { + if (new_tuple == NULL) + tnt_raise(ClientError, + ER_REPLICASET_UUID_IS_RO); + tt_uuid uu; + tuple_field_uuid_xc(new_tuple, BOX_CLUSTER_FIELD_UUID, + &uu); + REPLICASET_UUID = uu; + } else if (strcmp(key, "version") == 0) { + if (new_tuple != NULL) { + uint32_t major, minor, patch; + if (tuple_field_u32(new_tuple, 1, &major) != + 0 || + tuple_field_u32(new_tuple, 2, &minor) != 0) + tnt_raise(ClientError, + ER_WRONG_DD_VERSION); + /* Version can be major.minor with no patch. */ + if (tuple_field_u32(new_tuple, 3, &patch) != 0) + patch = 0; + dd_version_id = version_id(major, minor, patch); + } else { + assert(old_tuple != NULL); + /* + * _schema:delete({'version'}) for + * example, for box.internal.bootstrap(). + */ + dd_version_id = tarantool_version_id(); + } } } + return; +error: + struct user *user = user_find_xc(cr->uid); + tnt_raise(AccessDeniedError, + priv_name(PRIV_W), + schema_object_name(SC_SPACE), + "_schema", + user->def->name); } /** @@ -2993,6 +3058,9 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event) new_def = sequence_def_new_from_tuple(new_tuple, ER_CREATE_SEQUENCE); assert(sequence_by_id(new_def->id) == NULL); + /* When object is not created, we assume ADMIN owns it */ + access_check_ddl(new_def->name, ADMIN, SC_SEQUENCE, + PRIV_C, BOX_SEQUENCE_ID); sequence_cache_replace(new_def); alter->new_def = new_def; } else if (old_tuple != NULL && new_tuple == NULL) { /* DELETE */ @@ -3001,7 +3069,7 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event) struct sequence *seq = sequence_by_id(id); assert(seq != NULL); access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE, - PRIV_D, false); + PRIV_D, BOX_SEQUENCE_ID); if (space_has_data(BOX_SEQUENCE_DATA_ID, 0, id)) tnt_raise(ClientError, ER_DROP_SEQUENCE, seq->def->name, "the sequence has data"); @@ -3018,7 +3086,7 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event) struct sequence *seq = sequence_by_id(new_def->id); assert(seq != NULL); access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE, - PRIV_A, false); + PRIV_A, BOX_SEQUENCE_ID); alter->old_def = seq->def; alter->new_def = new_def; } @@ -3098,10 +3166,10 @@ on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event) /* Check we have the correct access type on the sequence. * */ access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE, priv_type, - false); + BOX_SEQUENCE_ID); /** Check we have alter access on space. */ access_check_ddl(space->def->name, space->def->uid, SC_SPACE, PRIV_A, - false); + BOX_SPACE_ID); struct trigger *on_commit = txn_alter_trigger_new(on_commit_dd_space_sequence, space); diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 1d245e3..c285ccf 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1934,10 +1934,7 @@ box.schema.user.create = function(name, opts) uid = _user:auto_increment{session.euid(), name, 'user', auth_mech_list}[1] -- grant role 'public' to the user box.schema.user.grant(uid, 'public') - -- we have to grant global privileges from setuid function, since - -- only admin has the ownership over universe and we don't have - -- grant option - box.session.su('admin', box.schema.user.grant, uid, 'session,usage', 'universe', + box.schema.user.grant(uid, 'session,usage', 'universe', nil, {if_not_exists=true}) end @@ -2055,12 +2052,9 @@ local function drop(uid, opts) for k, tuple in pairs(sequences) do box.schema.sequence.drop(tuple[1]) end - -- xxx: hack, we have to revoke session and usage privileges - -- of a user using a setuid function in absence of create/drop - -- privileges and grant option if box.space._vuser:get{uid}[4] == 'user' then - box.session.su('admin', box.schema.user.revoke, uid, - 'session,usage', 'universe', nil, {if_exists = true}) + box.schema.user.revoke(uid, 'session,usage', 'universe', + nil, {if_exists = true}) end local privs = _vpriv.index.primary:select{uid} for k, tuple in pairs(privs) do diff --git a/src/box/schema.cc b/src/box/schema.cc index 2e8533b..dfa9f4f 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -37,6 +37,7 @@ #include "scoped_guard.h" #include "version.h" #include "user.h" +#include "session.h" #include <stdio.h> /** * @module Data Dictionary @@ -520,6 +521,70 @@ sequence_cache_delete(uint32_t id) } } + +/** + * Runs simple read, usage checks. + * All other checks are postponed. + */ +static int +sys_read_access_check(struct space *space, user_access_t access) +{ + credentials *cr = effective_user(); + if (~cr->universal_access & PRIV_U) { + struct user *user = user_find(cr->uid); + if (user != NULL) + diag_set(AccessDeniedError, + priv_name(PRIV_U), + schema_object_name(SC_UNIVERSE), + "", + user->def->name); + return -1; + } + if (access == PRIV_R) { + uint32_t access = cr->universal_access | + space->access[cr->auth_token].effective; + if (~access & PRIV_R) { + struct user *user = user_find(cr->uid); + if (user != NULL) + diag_set(AccessDeniedError, + priv_name(PRIV_R), + schema_object_name(SC_SPACE), + space->def->name, + user->def->name); + return -1; + } + } + /* Other checks are postponed to trigger*/ + return 0; +} + +/** + * Runs simple read, usage, write checks. + * All other checks are postponed. + */ +static int +sys_read_write_access_check(struct space *space, user_access_t access) +{ + credentials *cr = effective_user(); + if (access == PRIV_W) { + uint32_t has_access = space->access[cr->auth_token].effective + | cr->universal_access; + if (~has_access & access) { + struct user *user = user_find(cr->uid); + if (user != NULL) { + diag_set(AccessDeniedError, + priv_name(access), + schema_object_name(SC_SPACE), + space->def->name, + user->def->name); + } + return -1; + } + return 0; + } + return sys_read_access_check(space, access); +} + const char * schema_find_name(enum schema_object_type type, uint32_t object_id) { @@ -562,3 +627,42 @@ schema_find_name(enum schema_object_type type, uint32_t object_id) return "(nil)"; } +uint32_t +schema_find_access(enum schema_object_type type, uint32_t object_id, + struct user *grantor) +{ + struct priv_def def; + def.object_type = type; + def.object_id = object_id; + def.grantor_id = grantor->def->uid; + uint32_t access = universe.access[grantor->auth_token].effective; + struct access *obj_access = access_find(&def); + if (obj_access != NULL) + access |= obj_access->effective; + return access; +} + +access_check_func_t +get_access_check_func(uint32_t space_id) +{ + switch (space_id) { + case BOX_CLUSTER_ID: + case BOX_COLLATION_ID: + return sys_read_write_access_check; + case BOX_SCHEMA_ID: + case BOX_SPACE_ID: + case BOX_TRUNCATE_ID: + case BOX_INDEX_ID: + case BOX_FUNC_ID: + case BOX_USER_ID: + case BOX_SEQUENCE_ID: + case BOX_SEQUENCE_DATA_ID: + case BOX_SPACE_SEQUENCE_ID: + case BOX_PRIV_ID: + /* Specialized access checks will be performed + * in trigger. */ + return sys_read_access_check; + default: + return access_check_user_space; + } +} diff --git a/src/box/schema.h b/src/box/schema.h index 2b87f5f..5b85e39 100644 --- a/src/box/schema.h +++ b/src/box/schema.h @@ -36,6 +36,7 @@ #include "error.h" #include "space.h" #include "latch.h" +#include "user.h" #if defined(__cplusplus) extern "C" { @@ -103,6 +104,13 @@ schema_find_name(enum schema_object_type type, uint32_t object_id); */ struct sequence * sequence_by_id(uint32_t id); + +uint32_t +schema_find_access(enum schema_object_type type, uint32_t object_id, + struct user *grantor); + +int +access_check_user_space(struct space *space, user_access_t access); #if defined(__cplusplus) } /* extern "C" */ @@ -205,6 +213,12 @@ sequence_cache_delete(uint32_t id); #endif /* defined(__cplusplus) */ /** + * Utility function used to specify access_check function for system space. + */ +access_check_func_t +get_access_check_func(uint32_t space_id); + +/** * Triggers fired after committing a change in space definition. * The space is passed to the trigger callback in the event * argument. It is the new space in case of create/update or diff --git a/src/box/space.c b/src/box/space.c index cc6cbed..a7a0930 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -44,7 +44,7 @@ #include "iproto_constants.h" int -access_check_space(struct space *space, user_access_t access) +access_check_user_space(struct space *space, user_access_t access) { struct credentials *cr = effective_user(); /* Any space access also requires global USAGE privilege. */ @@ -132,6 +132,7 @@ space_create(struct space *space, struct engine *engine, rlist_create(&space->on_replace); rlist_create(&space->on_stmt_begin); space->run_triggers = true; + space->access_check = get_access_check_func(def->id); space->format = format; if (format != NULL) diff --git a/src/box/space.h b/src/box/space.h index 6408eed..5335b31 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -49,6 +49,8 @@ struct request; struct port; struct tuple; +typedef int (*access_check_func_t)(struct space *space, user_access_t access); + struct space_vtab { /** Free a space instance. */ void (*destroy)(struct space *); @@ -139,6 +141,7 @@ struct space_vtab { struct space { /** Virtual function table. */ const struct space_vtab *vtab; + access_check_func_t access_check; /** Cached runtime access information. */ struct access access[BOX_USER_MAX]; /** Engine used by this space. */ @@ -291,8 +294,11 @@ index_name_by_id(struct space *space, uint32_t id); * Check whether or not the current user can be granted * the requested access to the space. */ -int -access_check_space(struct space *space, user_access_t access); +static inline int +access_check_space(struct space *space, user_access_t access) +{ + return space->access_check(space, access); +} static inline int space_apply_initial_join_row(struct space *space, struct request *request) @@ -424,6 +430,12 @@ space_swap_index(struct space *lhs, struct space *rhs, void space_fill_index_map(struct space *space); +/* + * Utility function used to specify access_check function for system space. + */ +access_check_func_t +get_access_check_func(uint32_t space_id); + #if defined(__cplusplus) } /* extern "C" */ @@ -540,4 +552,5 @@ space_prepare_alter_xc(struct space *old_space, struct space *new_space) #endif /* defined(__cplusplus) */ + #endif /* TARANTOOL_BOX_SPACE_H_INCLUDED */ diff --git a/src/box/sysview_engine.c b/src/box/sysview_engine.c index b478e78..467aa13 100644 --- a/src/box/sysview_engine.c +++ b/src/box/sysview_engine.c @@ -230,6 +230,7 @@ sysview_engine_create_space(struct engine *engine, struct space_def *def, free(space); return NULL; } + space->access_check = access_check_user_space; return space; } diff --git a/src/box/user.h b/src/box/user.h index 07c4dc5..254f0c9 100644 --- a/src/box/user.h +++ b/src/box/user.h @@ -199,6 +199,13 @@ priv_grant(struct user *grantee, struct priv_def *priv); void priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple); +/** + * Find the corresponding access structure + * given object type and object id. + */ +struct access * +access_find(struct priv_def *priv); + /* }}} */ #endif /* defined(__cplusplus) */ diff --git a/test/box/access.result b/test/box/access.result index 4354b05..305c86a 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -378,7 +378,6 @@ box.schema.user.create('grantee') ... box.schema.user.grant('grantee', 'read, write, execute', 'universe') --- -- error: Grant access to universe '' is denied for user 'grantor' ... session.su('grantee') --- @@ -1174,11 +1173,11 @@ box.schema.space.create('test') ... box.schema.user.create('test') --- -- error: Write access to space '_user' is denied for user 'guest' +- error: Create access to user 'test' is denied for user 'guest' ... box.schema.func.create('test') --- -- error: Write access to space '_func' is denied for user 'guest' +- error: Create access to function 'test' is denied for user 'guest' ... box.session.su('admin') --- @@ -1380,21 +1379,20 @@ box.schema.space.create("test_space") ... box.schema.user.create('test_user') --- -- error: Write access to space '_user' is denied for user 'tester' +- error: Create access to user 'test_user' is denied for user 'tester' ... box.schema.func.create('test_func') --- -- error: Write access to space '_func' is denied for user 'tester' +- error: Create access to function 'test_func' is denied for user 'tester' +... +box.schema.sequence.create('test_seq') +--- +- error: Create access to sequence 'test_seq' is denied for user 'tester' ... box.session.su("admin") --- ... --- --- FIXME 2.0: we still need to grant 'write' on universe --- explicitly since we still use process_rw to write to system --- tables from ddl --- -box.schema.user.grant("tester", "create,write", "universe") +box.schema.user.grant("tester", "create", "universe") --- ... box.session.su("tester") @@ -1442,23 +1440,29 @@ s:drop() ... seq:drop() --- -- error: Drop access to sequence 'test' is denied for user 'tester' +- error: Sequence '1' does not exist ... box.schema.user.drop("test") --- -- error: Revoke access to role 'public' is denied for user 'tester' +- error: User 'test' is not found ... box.schema.func.drop("test") --- -- error: Drop access to function 'test' is denied for user 'tester' +- error: Function 'test' does not exist ... box.session.su("admin") --- ... +box.schema.user.revoke("tester", "create", "universe") +--- +... box.schema.user.grant("tester", "drop", "universe") --- ... --- successful drop +-- successful truncate, drop +box.session.su("tester", s.truncate, s) +--- +... box.session.su("tester", s.drop, s) --- ... diff --git a/test/box/access.test.lua b/test/box/access.test.lua index e3ad687..8509990 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -519,14 +519,10 @@ box.session.su("tester") box.schema.space.create("test_space") box.schema.user.create('test_user') box.schema.func.create('test_func') +box.schema.sequence.create('test_seq') box.session.su("admin") --- --- FIXME 2.0: we still need to grant 'write' on universe --- explicitly since we still use process_rw to write to system --- tables from ddl --- -box.schema.user.grant("tester", "create,write", "universe") +box.schema.user.grant("tester", "create", "universe") box.session.su("tester") -- successful create s1 = box.schema.space.create("test_space") @@ -554,10 +550,13 @@ s:drop() seq:drop() box.schema.user.drop("test") box.schema.func.drop("test") - box.session.su("admin") + + +box.schema.user.revoke("tester", "create", "universe") box.schema.user.grant("tester", "drop", "universe") --- successful drop +-- successful truncate, drop +box.session.su("tester", s.truncate, s) box.session.su("tester", s.drop, s) box.session.su("tester", seq.drop, seq) box.session.su("tester", box.schema.user.drop, "test") diff --git a/test/box/access_misc.result b/test/box/access_misc.result index abc0be7..2f94b63 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -61,6 +61,9 @@ box.schema.user.grant('testus', 'read', 'space', 'admin_space') --- - error: User 'testus' already has read access on space 'admin_space' ... +box.schema.user.grant('testus', 'read', 'universe') +--- +... session.su('testus') --- ... @@ -78,7 +81,7 @@ s:delete(1) ... s:drop() --- -- error: Write access to space '_space_sequence' is denied for user 'testus' +- error: Drop access to space 'admin_space' is denied for user 'testus' ... -- -- Check double revoke @@ -93,6 +96,9 @@ box.schema.user.revoke('testus', 'read', 'space', 'admin_space') --- - error: User 'testus' does not have read access on space 'admin_space' ... +box.schema.user.revoke('testus', 'read', 'universe') +--- +... session.su('testus') --- ... @@ -124,9 +130,18 @@ s:insert({3}) --- - [3] ... +session.su('admin') +--- +... +box.schema.user.grant('testus', 'read', 'universe') +--- +... +session.su('testus') +--- +... s:drop() --- -- error: Write access to space '_space_sequence' is denied for user 'testus' +- error: Drop access to space 'admin_space' is denied for user 'testus' ... session.su('admin') --- @@ -167,9 +182,18 @@ s:delete({3}) --- - error: Write access to space 'admin_space' is denied for user 'guest' ... +session.su('admin') +--- +... +box.schema.user.grant('guest', 'read', 'universe') +--- +... +session.su('guest') +--- +... s:drop() --- -- error: Write access to space '_space_sequence' is denied for user 'guest' +- error: Drop access to space 'admin_space' is denied for user 'guest' ... gs = box.schema.space.create('guest_space') --- @@ -177,11 +201,14 @@ gs = box.schema.space.create('guest_space') ... box.schema.func.create('guest_func') --- -- error: Write access to space '_func' is denied for user 'guest' +- error: Create access to function 'guest_func' is denied for user 'guest' ... session.su('admin') --- ... +box.schema.user.revoke('guest', 'read', 'universe') +--- +... s:select() --- - - [2] @@ -277,7 +304,7 @@ session.su('admin') box.schema.user.create('someuser') --- ... -box.schema.user.grant('someuser', 'read, write, execute', 'universe') +box.schema.user.grant('someuser', 'read', 'universe') --- ... session.su('someuser') @@ -344,7 +371,7 @@ testuser_uid = session.uid() ... _ = box.space._user:delete(2) --- -- error: Drop access to user 'public' is denied for user 'testuser' +- error: 'Failed to drop user or role ''public'': the user or the role is a system' ... box.space._user:select(1) --- @@ -381,7 +408,7 @@ session.su('testuser') ... _ = box.space._user:delete(2) --- -- error: Write access to space '_user' is denied for user 'testuser' +- error: Drop access to user 'public' is denied for user 'testuser' ... box.space._user:select(1) --- @@ -389,7 +416,8 @@ box.space._user:select(1) ... box.space._user:insert{uid, session.uid(), 'someone2', 'user'} --- -- error: Write access to space '_user' is denied for user 'testuser' +- error: Tuple field count 4 is less than required by space format or defined indexes + (expected at least 5) ... session.su('admin') --- @@ -409,7 +437,7 @@ box.space._index:select(272) ... box.space._index:insert{512, 1,'owner','tree', 1, 1, 0,'unsigned'} --- -- error: Write access to space '_index' is denied for user 'testuser' +- error: 'Tuple field 5 type does not match one required by operation: expected map' ... session.su('admin') --- diff --git a/test/box/access_misc.test.lua b/test/box/access_misc.test.lua index cf6447e..e78058a 100644 --- a/test/box/access_misc.test.lua +++ b/test/box/access_misc.test.lua @@ -27,6 +27,7 @@ s:insert({2}) -- box.schema.user.grant('testus', 'read', 'space', 'admin_space') box.schema.user.grant('testus', 'read', 'space', 'admin_space') +box.schema.user.grant('testus', 'read', 'universe') session.su('testus') s:select(1) @@ -39,6 +40,7 @@ s:drop() session.su('admin') box.schema.user.revoke('testus', 'read', 'space', 'admin_space') box.schema.user.revoke('testus', 'read', 'space', 'admin_space') +box.schema.user.revoke('testus', 'read', 'universe') session.su('testus') s:select(1) @@ -52,6 +54,9 @@ session.su('testus') s:select(1) s:delete(1) s:insert({3}) +session.su('admin') +box.schema.user.grant('testus', 'read', 'universe') +session.su('testus') s:drop() session.su('admin') -- @@ -68,11 +73,15 @@ box.space._user:select(1) s:select(1) s:insert({4}) s:delete({3}) +session.su('admin') +box.schema.user.grant('guest', 'read', 'universe') +session.su('guest') s:drop() gs = box.schema.space.create('guest_space') box.schema.func.create('guest_func') session.su('admin') +box.schema.user.revoke('guest', 'read', 'universe') s:select() -- -- Create user with universe read&write grants @@ -116,7 +125,7 @@ box.schema.func.create('uniuser_func') session.su('admin') box.schema.user.create('someuser') -box.schema.user.grant('someuser', 'read, write, execute', 'universe') +box.schema.user.grant('someuser', 'read', 'universe') session.su('someuser') -- -- Check drop objects of another user diff --git a/test/box/net.box.result b/test/box/net.box.result index 46d85b3..1b62584 100644 --- a/test/box/net.box.result +++ b/test/box/net.box.result @@ -2140,6 +2140,9 @@ box.schema.user.grant('guest', 'write', 'space', '_space') box.schema.user.grant('guest', 'write', 'space', '_schema') --- ... +box.schema.user.grant('guest', 'create', 'universe') +--- +... count = 0 --- ... @@ -2188,6 +2191,9 @@ box.schema.user.revoke('guest', 'write', 'space', '_space') box.schema.user.revoke('guest', 'write', 'space', '_schema') --- ... +box.schema.user.revoke('guest', 'create', 'universe') +--- +... c:close() --- ... diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua index 87e26f8..f9272f2 100644 --- a/test/box/net.box.test.lua +++ b/test/box/net.box.test.lua @@ -877,6 +877,7 @@ box.session.on_disconnect(nil, on_disconnect) -- box.schema.user.grant('guest', 'write', 'space', '_space') box.schema.user.grant('guest', 'write', 'space', '_schema') +box.schema.user.grant('guest', 'create', 'universe') count = 0 function create_space(name) count = count + 1 box.schema.create_space(name) return true end c = net.connect(box.cfg.listen) @@ -891,6 +892,7 @@ box.space.test2:drop() box.space.test3:drop() box.schema.user.revoke('guest', 'write', 'space', '_space') box.schema.user.revoke('guest', 'write', 'space', '_schema') +box.schema.user.revoke('guest', 'create', 'universe') c:close() -- diff --git a/test/box/role.result b/test/box/role.result index 806cea9..b8055ff 100644 --- a/test/box/role.result +++ b/test/box/role.result @@ -671,7 +671,7 @@ box.session.su('admin') _ = box.schema.space.create('test') --- ... -box.schema.user.grant('john', 'read,write,execute', 'universe') +box.schema.user.grant('john', 'read', 'universe') --- ... box.session.su('john') @@ -695,14 +695,33 @@ box.schema.user.grant('grantee', 'public') - error: User 'grantee' already has role 'public' ... -- --- revoking role 'public' is another deal - only the --- superuser can do that, and even that would be useless, +-- revoking role 'public' is another deal - the +-- superuser or creator of user can do that, and even that would be useless, -- since one can still re-grant it back to oneself. -- box.schema.user.revoke('grantee', 'public') --- - error: Revoke access to role 'public' is denied for user 'john' ... +box.session.su("admin") +--- +... +box.schema.user.grant("john", "create", 'universe') +--- +... +box.session.su('john') +--- +... +box.schema.user.create("grantee2") +--- +... +-- must be ok +box.schema.user.revoke('grantee2', 'public') +--- +... +box.schema.user.drop('grantee2') +--- +... box.session.su('admin') --- ... diff --git a/test/box/role.test.lua b/test/box/role.test.lua index e97339f..abdd1b1 100644 --- a/test/box/role.test.lua +++ b/test/box/role.test.lua @@ -261,7 +261,7 @@ box.schema.user.grant('grantee', 'role') -- box.session.su('admin') _ = box.schema.space.create('test') -box.schema.user.grant('john', 'read,write,execute', 'universe') +box.schema.user.grant('john', 'read', 'universe') box.session.su('john') box.schema.user.grant('grantee', 'role') box.schema.user.grant('grantee', 'read', 'space', 'test') @@ -272,11 +272,18 @@ box.schema.user.grant('grantee', 'read', 'space', 'test') -- box.schema.user.grant('grantee', 'public') -- --- revoking role 'public' is another deal - only the --- superuser can do that, and even that would be useless, +-- revoking role 'public' is another deal: +-- the superuser or creator of user can do that, and even that would be useless, -- since one can still re-grant it back to oneself. -- box.schema.user.revoke('grantee', 'public') +box.session.su("admin") +box.schema.user.grant("john", "create", 'universe') +box.session.su('john') +box.schema.user.create("grantee2") +-- must be ok +box.schema.user.revoke('grantee2', 'public') +box.schema.user.drop('grantee2') box.session.su('admin') box.schema.user.drop('john') diff --git a/test/box/sequence.result b/test/box/sequence.result index a509207..631a8dd 100644 --- a/test/box/sequence.result +++ b/test/box/sequence.result @@ -1447,7 +1447,7 @@ box.session.su('admin') --- ... -- A user cannot alter sequences created by other users. -box.schema.user.grant('user', 'read,write', 'universe') +box.schema.user.grant('user', 'read', 'universe') --- ... box.session.su('user') @@ -1518,7 +1518,7 @@ s1 = box.schema.space.create('space1') _ = s1:create_index('pk') --- ... -box.schema.user.grant('user', 'read,write', 'universe') +box.schema.user.grant('user', 'read, create', 'universe') --- ... box.session.su('user') @@ -1532,15 +1532,14 @@ s2 = box.schema.space.create('space2') ... _ = s2:create_index('pk', {sequence = 'seq1'}) -- error --- -- error: Create access to sequence 'seq1' is denied for user 'user' ... s1.index.pk:alter({sequence = 'seq1'}) -- error --- -- error: Create access to sequence 'seq1' is denied for user 'user' +- error: Alter access to space 'space1' is denied for user 'user' ... box.space._space_sequence:replace{s1.id, sq1.id, false} -- error --- -- error: Create access to sequence 'seq1' is denied for user 'user' +- error: Alter access to space 'space1' is denied for user 'user' ... box.space._space_sequence:replace{s1.id, sq2.id, false} -- error --- @@ -1548,7 +1547,7 @@ box.space._space_sequence:replace{s1.id, sq2.id, false} -- error ... box.space._space_sequence:replace{s2.id, sq1.id, false} -- error --- -- error: Create access to sequence 'seq1' is denied for user 'user' +- error: Alter access to sequence 'seq1' is denied for user 'user' ... s2.index.pk:alter({sequence = 'seq2'}) -- ok --- @@ -1559,7 +1558,7 @@ box.session.su('admin') -- If the user owns a sequence attached to a space, -- it can use it for auto increment, otherwise it -- needs privileges. -box.schema.user.revoke('user', 'read,write', 'universe') +box.schema.user.revoke('user', 'read,create', 'universe') --- ... box.session.su('user') @@ -1697,10 +1696,10 @@ box.schema.user.create('user1') box.schema.user.create('user2') --- ... -box.schema.user.grant('user1', 'read,write', 'universe') +box.schema.user.grant('user1', 'read, create', 'universe') --- ... -box.schema.user.grant('user2', 'read,write', 'universe') +box.schema.user.grant('user2', 'read', 'universe') --- ... box.session.su('user1') diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua index 26147bb..f6d4c29 100644 --- a/test/box/sequence.test.lua +++ b/test/box/sequence.test.lua @@ -481,7 +481,7 @@ sq:reset() -- error box.session.su('admin') -- A user cannot alter sequences created by other users. -box.schema.user.grant('user', 'read,write', 'universe') +box.schema.user.grant('user', 'read', 'universe') box.session.su('user') sq:alter{step = 2} -- error sq:drop() -- error @@ -507,7 +507,7 @@ sq:drop() sq1 = box.schema.sequence.create('seq1') s1 = box.schema.space.create('space1') _ = s1:create_index('pk') -box.schema.user.grant('user', 'read,write', 'universe') +box.schema.user.grant('user', 'read, create', 'universe') box.session.su('user') sq2 = box.schema.sequence.create('seq2') s2 = box.schema.space.create('space2') @@ -522,7 +522,7 @@ box.session.su('admin') -- If the user owns a sequence attached to a space, -- it can use it for auto increment, otherwise it -- needs privileges. -box.schema.user.revoke('user', 'read,write', 'universe') +box.schema.user.revoke('user', 'read,create', 'universe') box.session.su('user') s2:insert{nil, 1} -- ok: {1, 1} box.session.su('admin') @@ -570,8 +570,8 @@ box.sequence -- to a sequence. box.schema.user.create('user1') box.schema.user.create('user2') -box.schema.user.grant('user1', 'read,write', 'universe') -box.schema.user.grant('user2', 'read,write', 'universe') +box.schema.user.grant('user1', 'read, create', 'universe') +box.schema.user.grant('user2', 'read', 'universe') box.session.su('user1') sq = box.schema.sequence.create('test') box.session.su('user2') diff --git a/test/engine/truncate.result b/test/engine/truncate.result index b6e1a99..1547b8c 100644 --- a/test/engine/truncate.result +++ b/test/engine/truncate.result @@ -506,7 +506,7 @@ con = require('net.box').connect(box.cfg.listen) ... con:eval([[box.space.access_truncate:truncate()]]) --- -- error: Write access to space 'access_truncate' is denied for user 'guest' +- error: Drop access to space 'access_truncate' is denied for user 'guest' ... con.space.access_truncate:select() --- -- 2.7.4
reply other threads:[~2018-03-29 9:54 UTC|newest] Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=8d1e74cf161d8f9fdb313cd8721f009ad99a2097.1522317191.git.imarkov@tarantool.org \ --to=imarkov@tarantool.org \ --cc=georgy@tarantool.org \ --cc=tarantool-patches@freelists.org \ --subject='Re: [tarantool-patches] [security 1/1] security: Refactor system space access checks' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox