KaiGai Kohei, reviewed by Dimitri Fontaine and me.
*/
#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/pg_database.h"
+#include "catalog/indexing.h"
+#include "commands/dbcommands.h"
#include "commands/seclabel.h"
+#include "utils/fmgroids.h"
+#include "utils/tqual.h"
#include "sepgsql.h"
+/*
+ * sepgsql_database_post_create
+ *
+ * This routine assigns a default security label on a newly defined
+ * database, and check permission needed for its creation.
+ */
void
-sepgsql_database_post_create(Oid databaseId)
+sepgsql_database_post_create(Oid databaseId, const char *dtemplate)
{
- char *scontext = sepgsql_get_client_label();
- char *tcontext;
- char *ncontext;
- ObjectAddress object;
+ Relation rel;
+ ScanKeyData skey;
+ SysScanDesc sscan;
+ HeapTuple tuple;
+ char *tcontext;
+ char *ncontext;
+ char audit_name[NAMEDATALEN + 20];
+ ObjectAddress object;
+ Form_pg_database datForm;
+
+ /*
+ * Oid of the source database is not saved in pg_database catalog,
+ * so we collect its identifier using contextual information.
+ * If NULL, its default is "template1" according to createdb().
+ */
+ if (!dtemplate)
+ dtemplate = "template1";
+
+ object.classId = DatabaseRelationId;
+ object.objectId = get_database_oid(dtemplate, false);
+ object.objectSubId = 0;
+
+ tcontext = sepgsql_get_label(object.classId,
+ object.objectId,
+ object.objectSubId);
+ /*
+ * check db_database:{getattr} permission
+ */
+ snprintf(audit_name, sizeof(audit_name), "database %s", dtemplate);
+ sepgsql_avc_check_perms_label(tcontext,
+ SEPG_CLASS_DB_DATABASE,
+ SEPG_DB_DATABASE__GETATTR,
+ audit_name,
+ true);
/*
* 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.
+ * XXX - uncoming version of libselinux supports to take object
+ * name to handle special treatment on default security label.
*/
- object.classId = DatabaseRelationId;
- object.objectId = TemplateDbOid;
- object.objectSubId = 0;
- tcontext = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG);
+ rel = heap_open(DatabaseRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(databaseId));
+
+ sscan = systable_beginscan(rel, DatabaseOidIndexId, true,
+ SnapshotSelf, 1, &skey);
+ tuple = systable_getnext(sscan);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "catalog lookup failed for database %u", databaseId);
+
+ datForm = (Form_pg_database) GETSTRUCT(tuple);
- ncontext = sepgsql_compute_create(scontext, tcontext,
+ ncontext = sepgsql_compute_create(sepgsql_get_client_label(),
+ tcontext,
SEPG_CLASS_DB_DATABASE);
+ /*
+ * check db_database:{create} permission
+ */
+ snprintf(audit_name, sizeof(audit_name),
+ "database %s", NameStr(datForm->datname));
+ sepgsql_avc_check_perms_label(ncontext,
+ SEPG_CLASS_DB_DATABASE,
+ SEPG_DB_DATABASE__CREATE,
+ audit_name,
+ true);
+
+ systable_endscan(sscan);
+ heap_close(rel, AccessShareLock);
/*
* Assign the default security label on the new database
--- /dev/null
+--
+-- 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"
+CREATE SCHEMA regtest_schema;
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+SET search_path = regtest_schema, public;
+CREATE TABLE regtest_table (x serial primary key, y text);
+NOTICE: CREATE TABLE will create implicit sequence "regtest_table_x_seq" for serial column "regtest_table.x"
+LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_table_x_seq"
+LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="table regtest_table"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column tableoid"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmax"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmax"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmin"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmin"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column ctid"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column x"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column y"
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "regtest_table_pkey" for table "regtest_table"
+ALTER TABLE regtest_table ADD COLUMN z int;
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column z"
+CREATE TABLE regtest_table_2 (a int) WITH OIDS;
+LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="table regtest_table_2"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column tableoid"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column cmax"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column xmax"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column cmin"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column xmin"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column oid"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column ctid"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column a"
+-- corresponding toast table should not have label and permission checks
+ALTER TABLE regtest_table_2 ADD COLUMN b text;
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table_2 column b"
+-- VACUUM FULL internally create a new table and swap them later.
+VACUUM FULL regtest_table;
+CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100;
+LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="view regtest_view"
+CREATE SEQUENCE regtest_seq;
+LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_seq"
+CREATE TYPE regtest_comptype AS (a int, b text);
+CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql
+ AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END';
+LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="function regtest_func(text,integer[])"
+CREATE AGGREGATE regtest_agg (
+ sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond1 = '0'
+);
+LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema"
+LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="function regtest_agg(integer)"
+--
+-- clean-up
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
+DROP SCHEMA IF EXISTS regtest_schema CASCADE;
+NOTICE: drop cascades to 7 other objects
+DETAIL: drop cascades to table regtest_table
+drop cascades to table regtest_table_2
+drop cascades to view regtest_view
+drop cascades to sequence regtest_seq
+drop cascades to type regtest_comptype
+drop cascades to function regtest_func(text,integer[])
+drop cascades to function regtest_agg(integer)
static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
static fmgr_hook_type next_fmgr_hook = NULL;
static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
+static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
+
+/*
+ * Contextual information on DDL commands
+ */
+typedef struct
+{
+ NodeTag cmdtype;
+
+ /*
+ * Name of the template database given by users on CREATE DATABASE
+ * command. Elsewhere (including the case of default) NULL.
+ */
+ const char *createdb_dtemplate;
+} sepgsql_context_info_t;
+
+static sepgsql_context_info_t sepgsql_context_info;
/*
* GUC: sepgsql.permissive = (on|off)
switch (classId)
{
case DatabaseRelationId:
- sepgsql_database_post_create(objectId);
+ sepgsql_database_post_create(objectId,
+ sepgsql_context_info.createdb_dtemplate);
break;
case NamespaceRelationId:
case RelationRelationId:
if (subId == 0)
- sepgsql_relation_post_create(objectId);
+ {
+ /*
+ * All cases we want to apply permission checks on
+ * creation of a new relation are invocation of the
+ * heap_create_with_catalog via DefineRelation or
+ * OpenIntoRel.
+ * Elsewhere, we need neither assignment of security
+ * label nor permission checks.
+ */
+ switch (sepgsql_context_info.cmdtype)
+ {
+ case T_CreateStmt:
+ case T_ViewStmt:
+ case T_CreateSeqStmt:
+ case T_CompositeTypeStmt:
+ case T_CreateForeignTableStmt:
+ case T_SelectStmt:
+ sepgsql_relation_post_create(objectId);
+ break;
+ default:
+ /* via make_new_heap() */
+ break;
+ }
+ }
else
sepgsql_attribute_post_create(objectId, subId);
break;
}
}
+/*
+ * sepgsql_executor_start
+ *
+ * It saves contextual information during ExecutorStart to distinguish
+ * a case with/without permission checks later.
+ */
+static void
+sepgsql_executor_start(QueryDesc *queryDesc, int eflags)
+{
+ sepgsql_context_info_t saved_context_info = sepgsql_context_info;
+
+ PG_TRY();
+ {
+ if (queryDesc->operation == CMD_SELECT)
+ sepgsql_context_info.cmdtype = T_SelectStmt;
+ else if (queryDesc->operation == CMD_INSERT)
+ sepgsql_context_info.cmdtype = T_InsertStmt;
+ else if (queryDesc->operation == CMD_DELETE)
+ sepgsql_context_info.cmdtype = T_DeleteStmt;
+ else if (queryDesc->operation == CMD_UPDATE)
+ sepgsql_context_info.cmdtype = T_UpdateStmt;
+
+ /*
+ * XXX - If queryDesc->operation is not above four cases, an error
+ * shall be raised on the following executor stage soon.
+ */
+ if (next_ExecutorStart_hook)
+ (*next_ExecutorStart_hook) (queryDesc, eflags);
+ else
+ standard_ExecutorStart(queryDesc, eflags);
+ }
+ PG_CATCH();
+ {
+ sepgsql_context_info = saved_context_info;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ sepgsql_context_info = saved_context_info;
+}
+
/*
* sepgsql_utility_command
*
DestReceiver *dest,
char *completionTag)
{
- if (next_ProcessUtility_hook)
- (*next_ProcessUtility_hook) (parsetree, queryString, params,
- isTopLevel, dest, completionTag);
+ sepgsql_context_info_t saved_context_info = sepgsql_context_info;
+ ListCell *cell;
- /*
- * Check command tag to avoid nefarious operations
- */
- switch (nodeTag(parsetree))
+ PG_TRY();
{
- case T_LoadStmt:
-
- /*
- * We reject LOAD command across the board on enforcing mode,
- * because a binary module can arbitrarily override hooks.
- */
- if (sepgsql_getenforce())
- {
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("SELinux: LOAD is not permitted")));
- }
- break;
- default:
-
- /*
- * Right now we don't check any other utility commands, because it
- * needs more detailed information to make access control decision
- * here, but we don't want to have two parse and analyze routines
- * individually.
- */
- break;
+ /*
+ * Check command tag to avoid nefarious operations, and save the
+ * current contextual information to determine whether we should
+ * apply permission checks here, or not.
+ */
+ sepgsql_context_info.cmdtype = nodeTag(parsetree);
+
+ switch (nodeTag(parsetree))
+ {
+ case T_CreatedbStmt:
+ /*
+ * We hope to reference name of the source database, but it
+ * does not appear in system catalog. So, we save it here.
+ */
+ foreach (cell, ((CreatedbStmt *) parsetree)->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(cell);
+
+ if (strcmp(defel->defname, "template") == 0)
+ {
+ sepgsql_context_info.createdb_dtemplate
+ = strVal(defel->arg);
+ break;
+ }
+ }
+ break;
+
+ case T_LoadStmt:
+ /*
+ * We reject LOAD command across the board on enforcing mode,
+ * because a binary module can arbitrarily override hooks.
+ */
+ if (sepgsql_getenforce())
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("SELinux: LOAD is not permitted")));
+ }
+ break;
+ default:
+ /*
+ * Right now we don't check any other utility commands,
+ * because it needs more detailed information to make access
+ * control decision here, but we don't want to have two parse
+ * and analyze routines individually.
+ */
+ break;
+ }
+
+ if (next_ProcessUtility_hook)
+ (*next_ProcessUtility_hook) (parsetree, queryString, params,
+ isTopLevel, dest, completionTag);
+ else
+ standard_ProcessUtility(parsetree, queryString, params,
+ isTopLevel, dest, completionTag);
}
-
- /*
- * Original implementation
- */
- standard_ProcessUtility(parsetree, queryString, params,
- isTopLevel, dest, completionTag);
+ PG_CATCH();
+ {
+ sepgsql_context_info = saved_context_info;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ sepgsql_context_info = saved_context_info;
}
/*
/* ProcessUtility hook */
next_ProcessUtility_hook = ProcessUtility_hook;
ProcessUtility_hook = sepgsql_utility_command;
+
+ /* ExecutorStart hook */
+ next_ExecutorStart_hook = ExecutorStart_hook;
+ ExecutorStart_hook = sepgsql_executor_start;
+
+ /* init contextual info */
+ memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info));
}
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "commands/seclabel.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/tqual.h"
ScanKeyData skey;
SysScanDesc sscan;
HeapTuple tuple;
- Oid namespaceId;
- ObjectAddress object;
char *scontext;
char *tcontext;
char *ncontext;
+ int i;
+ StringInfoData audit_name;
+ ObjectAddress object;
+ Form_pg_proc proForm;
/*
* Fetch namespace of the new procedure. Because pg_proc entry is not
if (!HeapTupleIsValid(tuple))
elog(ERROR, "catalog lookup failed for proc %u", functionId);
- namespaceId = ((Form_pg_proc) GETSTRUCT(tuple))->pronamespace;
+ proForm = (Form_pg_proc) GETSTRUCT(tuple);
+
+ /*
+ * check db_schema:{add_name} permission of the namespace
+ */
+ object.classId = NamespaceRelationId;
+ object.objectId = proForm->pronamespace;
+ object.objectSubId = 0;
+ sepgsql_avc_check_perms(&object,
+ SEPG_CLASS_DB_SCHEMA,
+ SEPG_DB_SCHEMA__ADD_NAME,
+ getObjectDescription(&object),
+ true);
+ /*
+ * XXX - db_language:{implement} also should be checked here
+ */
- systable_endscan(sscan);
- heap_close(rel, AccessShareLock);
/*
* Compute a default security label when we create a new procedure object
* under the specified namespace.
*/
scontext = sepgsql_get_client_label();
- tcontext = sepgsql_get_label(NamespaceRelationId, namespaceId, 0);
+ tcontext = sepgsql_get_label(NamespaceRelationId,
+ proForm->pronamespace, 0);
ncontext = sepgsql_compute_create(scontext, tcontext,
SEPG_CLASS_DB_PROCEDURE);
+ /*
+ * check db_procedure:{create} permission
+ */
+ initStringInfo(&audit_name);
+ appendStringInfo(&audit_name, "function %s(", NameStr(proForm->proname));
+ for (i=0; i < proForm->pronargs; i++)
+ {
+ Oid typeoid = proForm->proargtypes.values[i];
+ if (i > 0)
+ appendStringInfoChar(&audit_name, ',');
+ appendStringInfoString(&audit_name, format_type_be(typeoid));
+ }
+ appendStringInfoChar(&audit_name, ')');
+
+ sepgsql_avc_check_perms_label(ncontext,
+ SEPG_CLASS_DB_PROCEDURE,
+ SEPG_DB_PROCEDURE__CREATE,
+ audit_name.data,
+ true);
/*
* Assign the default security label on a new procedure
*/
object.objectSubId = 0;
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
+ /*
+ * Cleanup
+ */
+ systable_endscan(sscan);
+ heap_close(rel, AccessShareLock);
+
+ pfree(audit_name.data);
pfree(tcontext);
pfree(ncontext);
}
void
sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
{
- char *scontext = sepgsql_get_client_label();
+ Relation rel;
+ ScanKeyData skey[2];
+ SysScanDesc sscan;
+ HeapTuple tuple;
+ char *scontext;
char *tcontext;
char *ncontext;
+ char audit_name[2*NAMEDATALEN + 20];
ObjectAddress object;
+ Form_pg_attribute attForm;
/*
* Only attributes within regular relation have individual security
return;
/*
- * Compute a default security label when we create a new procedure object
- * under the specified namespace.
+ * Compute a default security label of the new column underlying the
+ * specified relation, and check permission to create it.
*/
+ rel = heap_open(AttributeRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_attribute_attrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relOid));
+ ScanKeyInit(&skey[1],
+ Anum_pg_attribute_attnum,
+ BTEqualStrategyNumber, F_INT2EQ,
+ Int16GetDatum(attnum));
+
+ sscan = systable_beginscan(rel, AttributeRelidNumIndexId, true,
+ SnapshotSelf, 2, &skey[0]);
+
+ tuple = systable_getnext(sscan);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "catalog lookup failed for column %d of relation %u",
+ attnum, relOid);
+
+ attForm = (Form_pg_attribute) GETSTRUCT(tuple);
+
scontext = sepgsql_get_client_label();
tcontext = sepgsql_get_label(RelationRelationId, relOid, 0);
ncontext = sepgsql_compute_create(scontext, tcontext,
SEPG_CLASS_DB_COLUMN);
+ /*
+ * check db_column:{create} permission
+ */
+ snprintf(audit_name, sizeof(audit_name), "table %s column %s",
+ get_rel_name(relOid), NameStr(attForm->attname));
+ sepgsql_avc_check_perms_label(ncontext,
+ SEPG_CLASS_DB_COLUMN,
+ SEPG_DB_COLUMN__CREATE,
+ audit_name,
+ true);
/*
* Assign the default security label on a new procedure
object.objectSubId = attnum;
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
+ systable_endscan(sscan);
+ heap_close(rel, AccessShareLock);
+
pfree(tcontext);
pfree(ncontext);
}
Form_pg_class classForm;
ObjectAddress object;
uint16 tclass;
+ const char *tclass_text;
char *scontext; /* subject */
char *tcontext; /* schema */
char *rcontext; /* relation */
char *ccontext; /* column */
+ char audit_name[2*NAMEDATALEN + 20];
/*
* Fetch catalog record of the new relation. Because pg_class entry is not
classForm = (Form_pg_class) GETSTRUCT(tuple);
- if (classForm->relkind == RELKIND_RELATION)
- tclass = SEPG_CLASS_DB_TABLE;
- else if (classForm->relkind == RELKIND_SEQUENCE)
- tclass = SEPG_CLASS_DB_SEQUENCE;
- else if (classForm->relkind == RELKIND_VIEW)
- tclass = SEPG_CLASS_DB_VIEW;
- else
- goto out; /* No need to assign individual labels */
+ switch (classForm->relkind)
+ {
+ case RELKIND_RELATION:
+ tclass = SEPG_CLASS_DB_TABLE;
+ tclass_text = "table";
+ break;
+ case RELKIND_SEQUENCE:
+ tclass = SEPG_CLASS_DB_SEQUENCE;
+ tclass_text = "sequence";
+ break;
+ case RELKIND_VIEW:
+ tclass = SEPG_CLASS_DB_VIEW;
+ tclass_text = "view";
+ break;
+ default:
+ goto out;
+ }
+ /*
+ * check db_schema:{add_name} permission of the namespace
+ */
+ object.classId = NamespaceRelationId;
+ object.objectId = classForm->relnamespace;
+ object.objectSubId = 0;
+ sepgsql_avc_check_perms(&object,
+ SEPG_CLASS_DB_SCHEMA,
+ SEPG_DB_SCHEMA__ADD_NAME,
+ getObjectDescription(&object),
+ true);
/*
* Compute a default security label when we create a new relation object
* under the specified namespace.
classForm->relnamespace, 0);
rcontext = sepgsql_compute_create(scontext, tcontext, tclass);
+ /*
+ * check db_xxx:{create} permission
+ */
+ snprintf(audit_name, sizeof(audit_name), "%s %s",
+ tclass_text, NameStr(classForm->relname));
+ sepgsql_avc_check_perms_label(rcontext,
+ tclass,
+ SEPG_DB_DATABASE__CREATE,
+ audit_name,
+ true);
/*
* Assign the default security label on the new relation
*/
*/
if (classForm->relkind == RELKIND_RELATION)
{
- AttrNumber index;
+ Relation arel;
+ ScanKeyData akey;
+ SysScanDesc ascan;
+ HeapTuple atup;
+ Form_pg_attribute attForm;
- ccontext = sepgsql_compute_create(scontext, rcontext,
- SEPG_CLASS_DB_COLUMN);
- for (index = FirstLowInvalidHeapAttributeNumber + 1;
- index <= classForm->relnatts;
- index++)
+ arel = heap_open(AttributeRelationId, AccessShareLock);
+
+ ScanKeyInit(&akey,
+ Anum_pg_attribute_attrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relOid));
+
+ ascan = systable_beginscan(arel, AttributeRelidNumIndexId, true,
+ SnapshotSelf, 1, &akey);
+
+ while (HeapTupleIsValid(atup = systable_getnext(ascan)))
{
- if (index == InvalidAttrNumber)
- continue;
+ attForm = (Form_pg_attribute) GETSTRUCT(atup);
- if (index == ObjectIdAttributeNumber && !classForm->relhasoids)
- continue;
+ snprintf(audit_name, sizeof(audit_name), "%s %s column %s",
+ tclass_text,
+ NameStr(classForm->relname),
+ NameStr(attForm->attname));
+
+ ccontext = sepgsql_compute_create(scontext,
+ rcontext,
+ SEPG_CLASS_DB_COLUMN);
+ /*
+ * check db_column:{create} permission
+ */
+ sepgsql_avc_check_perms_label(ccontext,
+ SEPG_CLASS_DB_COLUMN,
+ SEPG_DB_COLUMN__CREATE,
+ audit_name,
+ true);
object.classId = RelationRelationId;
object.objectId = relOid;
- object.objectSubId = index;
+ object.objectSubId = attForm->attnum;
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext);
+
+ pfree(ccontext);
}
- pfree(ccontext);
+ systable_endscan(ascan);
+ heap_close(arel, AccessShareLock);
}
pfree(rcontext);
out:
*/
#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/sysattr.h"
#include "catalog/dependency.h"
+#include "catalog/indexing.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "commands/seclabel.h"
#include "miscadmin.h"
+#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
+#include "utils/tqual.h"
#include "sepgsql.h"
void
sepgsql_schema_post_create(Oid namespaceId)
{
- char *scontext;
+ Relation rel;
+ ScanKeyData skey;
+ SysScanDesc sscan;
+ HeapTuple tuple;
char *tcontext;
char *ncontext;
- ObjectAddress object;
+ char audit_name[NAMEDATALEN + 20];
+ ObjectAddress object;
+ Form_pg_namespace nspForm;
/*
* Compute a default security label when we create a new schema object
* under the working database.
+ *
+ * XXX - uncoming version of libselinux supports to take object
+ * name to handle special treatment on default security label;
+ * such as special label on "pg_temp" schema.
*/
- scontext = sepgsql_get_client_label();
+ rel = heap_open(NamespaceRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(namespaceId));
+
+ sscan = systable_beginscan(rel, NamespaceOidIndexId, true,
+ SnapshotSelf, 1, &skey);
+ tuple = systable_getnext(sscan);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "catalog lookup failed for namespace %u", namespaceId);
+
+ nspForm = (Form_pg_namespace) GETSTRUCT(tuple);
+
tcontext = sepgsql_get_label(DatabaseRelationId, MyDatabaseId, 0);
- ncontext = sepgsql_compute_create(scontext, tcontext,
+ ncontext = sepgsql_compute_create(sepgsql_get_client_label(),
+ tcontext,
SEPG_CLASS_DB_SCHEMA);
+ /*
+ * check db_schema:{create}
+ */
+ snprintf(audit_name, sizeof(audit_name),
+ "schema %s", NameStr(nspForm->nspname));
+ sepgsql_avc_check_perms_label(ncontext,
+ SEPG_CLASS_DB_SCHEMA,
+ SEPG_DB_SCHEMA__CREATE,
+ audit_name,
+ true);
+ systable_endscan(sscan);
+ heap_close(rel, AccessShareLock);
/*
* Assign the default security label on a new procedure
/*
* database.c
*/
-extern void sepgsql_database_post_create(Oid databaseId);
+extern void sepgsql_database_post_create(Oid databaseId,
+ const char *dtemplate);
extern void sepgsql_database_relabel(Oid databaseId, const char *seclabel);
/*
--- /dev/null
+--
+-- 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;
+
+CREATE SCHEMA regtest_schema;
+
+SET search_path = regtest_schema, public;
+
+CREATE TABLE regtest_table (x serial primary key, y text);
+
+ALTER TABLE regtest_table ADD COLUMN z int;
+
+CREATE TABLE regtest_table_2 (a int) WITH OIDS;
+
+-- corresponding toast table should not have label and permission checks
+ALTER TABLE regtest_table_2 ADD COLUMN b text;
+
+-- VACUUM FULL internally create a new table and swap them later.
+VACUUM FULL regtest_table;
+
+CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100;
+
+CREATE SEQUENCE regtest_seq;
+
+CREATE TYPE regtest_comptype AS (a int, b text);
+
+CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql
+ AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END';
+
+CREATE AGGREGATE regtest_agg (
+ sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond1 = '0'
+);
+
+--
+-- clean-up
+--
+DROP DATABASE IF EXISTS regtest_sepgsql_test_database;
+
+DROP SCHEMA IF EXISTS regtest_schema CASCADE;
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"
<sect3>
<title>DDL Permissions</title>
+ <para>
+ <productname>SELinux</> defines several permissions to control common
+ operations for each object types; such as creation, alter, drop and
+ relabel of security label. In addition, several object types has its
+ special permissions to control its characteristic operations; such as
+ addition or deletion of name entries underlying a particular schema.
+ </para>
+ <para>
+ When <literal>CREATE</> command is executed, <literal>create</> will
+ be checked on the object being constructed for each object types.
+ A default security label shall be assigned on the new database object,
+ and the <literal>create</> permission needs to be allowed on the pair
+ of security label of the client and the new object itself.
+ We consider <xref linkend="sql-createtable"> construct a table and
+ underlying columns at the same time, so it requires users permission
+ to create both of table and columns.
+ </para>
+ <para>
+ A few additional checks are applied depending on object types.
+ On <xref linkend="sql-createdatabase">, <literal>getattr</> permission
+ shall be checked on the source or template database of the new database,
+ not only <literal>create</> on the new database.
+ On creation of objects underlying a particula schema (tables, views,
+ sequences and procedures), <literal>add_name</> shall be also chechked
+ on the schema, not only <literal>create</> on the new object itself.
+ </para>
+
<para>
When <xref linkend="sql-security-label"> is executed, <literal>setattr</>
and <literal>relabelfrom</> will be checked on the object being relabeled
<term>Data Definition Language (DDL) Permissions</term>
<listitem>
<para>
- Due to implementation restrictions, DDL permissions are not checked.
+ Due to implementation restrictions, some of DDL permissions are not
+ checked.
</para>
</listitem>
</varlistentry>