sepgsql: Check CREATE permissions for some object types.
KaiGai Kohei, reviewed by Dimitri Fontaine and me.
This commit is contained in:
parent
7f0e4bb82e
commit
e1042a3484
@ -10,33 +10,100 @@
|
||||
*/
|
||||
#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);
|
||||
|
||||
ncontext = sepgsql_compute_create(scontext, tcontext,
|
||||
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(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
|
||||
|
80
contrib/sepgsql/expected/create.out
Normal file
80
contrib/sepgsql/expected/create.out
Normal file
@ -0,0 +1,80 @@
|
||||
--
|
||||
-- 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)
|
@ -41,6 +41,23 @@ static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
|
||||
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)
|
||||
@ -127,7 +144,8 @@ sepgsql_object_access(ObjectAccessType access,
|
||||
switch (classId)
|
||||
{
|
||||
case DatabaseRelationId:
|
||||
sepgsql_database_post_create(objectId);
|
||||
sepgsql_database_post_create(objectId,
|
||||
sepgsql_context_info.createdb_dtemplate);
|
||||
break;
|
||||
|
||||
case NamespaceRelationId:
|
||||
@ -136,7 +154,30 @@ sepgsql_object_access(ObjectAccessType access,
|
||||
|
||||
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;
|
||||
@ -294,6 +335,46 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
@ -308,44 +389,74 @@ sepgsql_utility_command(Node *parsetree,
|
||||
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:
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* 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:
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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 (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;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -456,4 +567,11 @@ _PG_init(void)
|
||||
/* 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));
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#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"
|
||||
@ -37,11 +38,13 @@ sepgsql_proc_post_create(Oid functionId)
|
||||
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
|
||||
@ -61,20 +64,53 @@ sepgsql_proc_post_create(Oid functionId)
|
||||
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
|
||||
*/
|
||||
@ -83,6 +119,13 @@ sepgsql_proc_post_create(Oid functionId)
|
||||
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);
|
||||
}
|
||||
|
@ -36,10 +36,16 @@
|
||||
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
|
||||
@ -49,13 +55,44 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
|
||||
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
|
||||
@ -65,6 +102,9 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
|
||||
object.objectSubId = attnum;
|
||||
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
|
||||
|
||||
systable_endscan(sscan);
|
||||
heap_close(rel, AccessShareLock);
|
||||
|
||||
pfree(tcontext);
|
||||
pfree(ncontext);
|
||||
}
|
||||
@ -127,10 +167,12 @@ sepgsql_relation_post_create(Oid relOid)
|
||||
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
|
||||
@ -152,15 +194,35 @@ sepgsql_relation_post_create(Oid relOid)
|
||||
|
||||
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.
|
||||
@ -170,6 +232,16 @@ sepgsql_relation_post_create(Oid relOid)
|
||||
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
|
||||
*/
|
||||
@ -184,26 +256,52 @@ sepgsql_relation_post_create(Oid relOid)
|
||||
*/
|
||||
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:
|
||||
|
@ -10,12 +10,18 @@
|
||||
*/
|
||||
#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"
|
||||
|
||||
@ -28,19 +34,55 @@
|
||||
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
|
||||
|
@ -286,7 +286,8 @@ extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort);
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
|
46
contrib/sepgsql/sql/create.sql
Normal file
46
contrib/sepgsql/sql/create.sql
Normal file
@ -0,0 +1,46 @@
|
||||
--
|
||||
-- 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;
|
@ -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"
|
||||
|
@ -420,6 +420,33 @@ UPDATE t1 SET x = 2, y = md5sum(y) WHERE z = 100;
|
||||
|
||||
<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
|
||||
@ -509,7 +536,8 @@ postgres=# SELECT cid, cname, show_credit(cid) FROM customer;
|
||||
<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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user