contrib/sepgsql/database.c | 68 ++++++++++++++----- contrib/sepgsql/expected/create.out | 19 ++++++ contrib/sepgsql/hooks.c | 123 ++++++++++++++++++++++++++--------- contrib/sepgsql/sepgsql.h | 23 ++++++- contrib/sepgsql/sql/create.sql | 15 ++++ contrib/sepgsql/test_sepgsql | 2 +- src/backend/commands/dbcommands.c | 9 ++- src/include/catalog/objectaccess.h | 71 ++++++++++++++++++-- 8 files changed, 272 insertions(+), 58 deletions(-) diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c index 7f15d9c..ed3817b 100644 --- a/contrib/sepgsql/database.c +++ b/contrib/sepgsql/database.c @@ -15,40 +15,72 @@ #include "commands/seclabel.h" #include "sepgsql.h" -void -sepgsql_database_post_create(Oid databaseId) +/* + * sepgsql_database_prep_create + * + * This routine computes default security label and checks permissions to + * create a new database. + */ +const char * +sepgsql_database_prep_create(const char *datname, + Oid datsourceId, + Oid tablespaceId) { char *scontext = sepgsql_get_client_label(); char *tcontext; char *ncontext; + char audit_name[NAMEDATALEN + 16]; ObjectAddress object; - /* - * Compute a default security label of the newly created database - * based on a pair of security label of client and source database. - * - * XXX - Right now, this logic uses "template1" as its source, because - * here is no way to know the Oid of source database. - */ + /* check db_database:{getattr} permission on the source database */ object.classId = DatabaseRelationId; - object.objectId = TemplateDbOid; + object.objectId = datsourceId;; object.objectSubId = 0; - tcontext = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG); + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__GETATTR, + getObjectDescription(&object), + true); + /* + * Compute a default security label of the new database being + * constructed; based on a pair of security label of client and + * source database. + */ + tcontext = sepgsql_get_label(DatabaseRelationId, datsourceId, 0); + /* + * XXX - upcoming libselinux supports case handling when + * a new object that has a particular name. + */ ncontext = sepgsql_compute_create(scontext, tcontext, SEPG_CLASS_DB_DATABASE); - /* - * Assign the default security label on the new database - */ + /* check db_database:{create} permission */ + snprintf(audit_name, sizeof(audit_name), "database: %s", datname); + sepgsql_avc_check_perms_label(ncontext, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__CREATE, + audit_name, + true); + + return ncontext; +} + +/* + * sepgsql_database_post_create + * + * This routine assigns default security label of the database. + */ +void +sepgsql_database_post_create(Oid databaseId, const char *seclabel) +{ + ObjectAddress object; + object.classId = DatabaseRelationId; object.objectId = databaseId; object.objectSubId = 0; - SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); - - pfree(ncontext); - pfree(tcontext); + SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, seclabel); } /* diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out new file mode 100644 index 0000000..95fc4f1 --- /dev/null +++ b/contrib/sepgsql/expected/create.out @@ -0,0 +1,19 @@ +-- +-- Regression Test for Creation of Object Permission Checks +-- +-- confirm required permissions using audit messages +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0 +(1 row) + +SET sepgsql.debug_audit = true; +SET client_min_messages = LOG; +CREATE DATABASE regtest_sepgsql_test_database; +LOG: SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database template1" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database: regtest_sepgsql_test_database" +-- +-- clean-up +-- +DROP DATABASE IF EXISTS regtest_sepgsql_test_database; diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c index 331bbd7..886f1fa 100644 --- a/contrib/sepgsql/hooks.c +++ b/contrib/sepgsql/hooks.c @@ -107,6 +107,91 @@ sepgsql_client_auth(Port *port, int status) } /* + * sepgsql_object_prep_create + * + * Entrypoint of OAT_PREP_CREATE event via object_access_hook. + */ +static void +sepgsql_object_prep_create(Oid classId, Oid objectId, int subId, + Datum argument) +{ + ObjectAccessCreateObjectArgs *args + = (ObjectAccessCreateObjectArgs *)DatumGetPointer(argument); + sepgsql_creation_info *cinfo; + + if (next_object_access_hook) + (*next_object_access_hook) (OAT_PREP_CREATE, + classId, objectId, subId, + argument); + + /* + * create sepgsql's private data to be delivered to + * the post-creation hook. + */ + cinfo = palloc(sizeof(sepgsql_creation_info)); + cinfo->classId = classId; + cinfo->private_next = *(args->common.private); + + switch (classId) + { + case DatabaseRelationId: + cinfo->ncontext = + sepgsql_database_prep_create(args->pg_database.datname, + args->pg_database.datsourceId, + args->pg_database.tablespaceId); + break; + + default: + /* Ignore unsupported object classes */ + break; + } + *(args->common.private) = PointerGetDatum(cinfo); +} + +/* + * sepgsql_object_post_create + * + * Entrypoint of OAT_POST_CREATE event via object_access_hook. + */ +static void +sepgsql_object_post_create(Oid classId, Oid objectId, int subId, + Datum argument) +{ + sepgsql_creation_info *cinfo + = (sepgsql_creation_info *)DatumGetPointer(argument); + + if (next_object_access_hook) + (*next_object_access_hook) (OAT_POST_CREATE, + classId, objectId, subId, + !cinfo ? 0 : cinfo->private_next); + switch (classId) + { + case DatabaseRelationId: + sepgsql_database_post_create(objectId, cinfo->ncontext); + break; + + case NamespaceRelationId: + sepgsql_schema_post_create(objectId); + break; + + case RelationRelationId: + if (subId == 0) + sepgsql_relation_post_create(objectId); + else + sepgsql_attribute_post_create(objectId, subId); + break; + + case ProcedureRelationId: + sepgsql_proc_post_create(objectId); + break; + + default: + /* Ignore unsupported object classes */ + break; + } +} + +/* * sepgsql_object_access * * Entrypoint of the object_access_hook. This routine performs as @@ -114,41 +199,17 @@ sepgsql_client_auth(Port *port, int status) */ static void sepgsql_object_access(ObjectAccessType access, - Oid classId, - Oid objectId, - int subId) + Oid classId, Oid objectId, int subId, + Datum argument) { - if (next_object_access_hook) - (*next_object_access_hook) (access, classId, objectId, subId); - switch (access) { + case OAT_PREP_CREATE: + sepgsql_object_prep_create(classId, objectId, subId, argument); + break; + case OAT_POST_CREATE: - switch (classId) - { - case DatabaseRelationId: - sepgsql_database_post_create(objectId); - break; - - case NamespaceRelationId: - sepgsql_schema_post_create(objectId); - break; - - case RelationRelationId: - if (subId == 0) - sepgsql_relation_post_create(objectId); - else - sepgsql_attribute_post_create(objectId, subId); - break; - - case ProcedureRelationId: - sepgsql_proc_post_create(objectId); - break; - - default: - /* Ignore unsupported object classes */ - break; - } + sepgsql_object_post_create(classId, objectId, subId, argument); break; default: diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h index b4c1dfd..0d0eead 100644 --- a/contrib/sepgsql/sepgsql.h +++ b/contrib/sepgsql/sepgsql.h @@ -211,6 +211,24 @@ #define SEPG_DB_VIEW__EXPAND (1<<6) /* + * sepgsql_creation_info + * + * A data structure to store contextual information between prep-creation + * and post-creation hooks; mostly a security label to be assigned on the + * newly constructed object. + */ +typedef struct { + /* oid of the catalog that stores this new object */ + Oid classId; + + /* private field for stacking module */ + Datum private_next; + + /* a default security label to be assigned on */ + const char *ncontext; +} sepgsql_creation_info; + +/* * hooks.c */ extern bool sepgsql_get_permissive(void); @@ -286,7 +304,10 @@ extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort); /* * database.c */ -extern void sepgsql_database_post_create(Oid databaseId); +extern const char *sepgsql_database_prep_create(const char *datname, + Oid datsourceId, + Oid tablespaceId); +extern void sepgsql_database_post_create(Oid databaseId, const char *seclabel); extern void sepgsql_database_relabel(Oid databaseId, const char *seclabel); /* diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql new file mode 100644 index 0000000..e75d57b --- /dev/null +++ b/contrib/sepgsql/sql/create.sql @@ -0,0 +1,15 @@ +-- +-- Regression Test for Creation of Object Permission Checks +-- + +-- confirm required permissions using audit messages +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0 +SET sepgsql.debug_audit = true; +SET client_min_messages = LOG; + +CREATE DATABASE regtest_sepgsql_test_database; + +-- +-- clean-up +-- +DROP DATABASE IF EXISTS regtest_sepgsql_test_database; diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql index 9b7262a..52237e6 100755 --- a/contrib/sepgsql/test_sepgsql +++ b/contrib/sepgsql/test_sepgsql @@ -259,6 +259,6 @@ echo "found ${NUM}" echo echo "============== running sepgsql regression tests ==============" -make REGRESS="label dml misc" REGRESS_OPTS="--launcher ./launcher" installcheck +make REGRESS="label dml create misc" REGRESS_OPTS="--launcher ./launcher" installcheck # exit with the exit code provided by "make" diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index 4551db7..361a172 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -127,6 +127,7 @@ createdb(const CreatedbStmt *stmt) int dbconnlimit = -1; int notherbackends; int npreparedxacts; + Datum hook_private = 0; createdb_failure_params fparms; /* Extract options from the statement node tree */ @@ -424,6 +425,10 @@ createdb(const CreatedbStmt *stmt) /* Note there is no additional permission check in this path */ } + /* Prep-creation hook for new database */ + InvokePrepCreateDatabaseHook(&hook_private, dbname, src_dboid, + dst_deftablespace); + /* * Check for db name conflict. This is just to give a more friendly error * message than "unique index violation". There's a race condition but @@ -515,7 +520,9 @@ createdb(const CreatedbStmt *stmt) copyTemplateDependencies(src_dboid, dboid); /* Post creation hook for new database */ - InvokeObjectAccessHook(OAT_POST_CREATE, DatabaseRelationId, dboid, 0); + InvokeObjectAccessHookArg(OAT_POST_CREATE, + DatabaseRelationId, dboid, 0, + hook_private); /* * Force a checkpoint before starting the copy. This will force dirty diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h index 2925475..0531327 100644 --- a/src/include/catalog/objectaccess.h +++ b/src/include/catalog/objectaccess.h @@ -15,32 +15,91 @@ * performing certain actions on a SQL object. This is intended as * infrastructure for security or logging pluggins. * + * OAT_PREP_CREATE should be invoked around permission check of the core + * with arguments that describes about new object being constructed. + * Its private field can be used to deliver contextual information to + * post-creation hook. + * * OAT_POST_CREATE should be invoked just after the the object is created. * Typically, this is done after inserting the primary catalog records and - * associated dependencies. + * associated dependencies. If prep-creation hook was invoked in same + * context, its private valud shall be provided to the modules. * * Other types may be added in the future. */ typedef enum ObjectAccessType { + OAT_PREP_CREATE, OAT_POST_CREATE, } ObjectAccessType; /* - * Hook, and a macro to invoke it. + * ObjectAccessCreateObjectInfo + * + * It shall be used as argument of OAT_PREP_CREATE hook to inform about + * new object being constructed; prior to updates of system catalogs. + */ +typedef union +{ + struct { + Datum *private; /* private data to deliver contextual + * information into post creation hook */ + } common; + struct { + Datum *private; /* common */ + const char *datname; /* name of new database */ + Oid datsourceId; /* oid of source database */ + Oid tablespaceId; /* oid of default tablespace */ + } pg_database; +} ObjectAccessCreateObjectArgs; + +/* + * Hook, and macros to invoke it. */ typedef void (*object_access_hook_type) (ObjectAccessType access, - Oid classId, - Oid objectId, - int subId); + Oid classId, + Oid objectId, + int subId, + Datum argument); extern PGDLLIMPORT object_access_hook_type object_access_hook; #define InvokeObjectAccessHook(access,classId,objectId,subId) \ do { \ if (object_access_hook) \ - (*object_access_hook)((access),(classId),(objectId),(subId)); \ + (*object_access_hook)((access), \ + (classId),(objectId),(subId), \ + (Datum) 0); \ + } while(0) + +#define InvokeObjectAccessHookArg(access,classId,objectId,subId,argument) \ + do { \ + if (object_access_hook) \ + (*object_access_hook)((access), \ + (classId),(objectId),(subId), \ + (argument)); \ + } while(0) + +/* + * OAT_PREP_CREATE specific macros, because it takes various arguments + * depending on object classes. + */ +#define InvokePrepCreateDatabaseHook(_private,_datname,_source,_tablespace) \ + do { \ + if (object_access_hook) \ + { \ + ObjectAccessCreateObjectArgs __args; \ + \ + __args.pg_database.private = (_private); \ + __args.pg_database.datname = (_datname); \ + __args.pg_database.datsourceId = (_source); \ + __args.pg_database.tablespaceId = (_tablespace); \ + \ + (*object_access_hook)(OAT_PREP_CREATE, \ + DatabaseRelationId, InvalidOid, 0, \ + PointerGetDatum(&__args)); \ + } \ } while(0) #endif /* OBJECTACCESS_H */