mirror of https://github.com/postgres/postgres
This is still pretty rough - among other things, the documentation needs work, and the messages need a visit from the style police - but this gets the basic framework in place. KaiGai Koheipull/1/head
parent
e5487f65fd
commit
968bc6fac9
@ -0,0 +1 @@ |
||||
/sepgsql.sql |
@ -0,0 +1,25 @@ |
||||
# contrib/sepgsql/Makefile
|
||||
|
||||
MODULE_big = sepgsql
|
||||
OBJS = hooks.o selinux.o label.o dml.o \
|
||||
schema.o relation.o proc.o
|
||||
DATA_built = sepgsql.sql sepgsql-regtest.pp
|
||||
REGRESS = label dml misc
|
||||
EXTRA_CLEAN = -r tmp *.pp sepgsql-regtest.if sepgsql-regtest.fc
|
||||
|
||||
ifdef USE_PGXS |
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS) |
||||
else |
||||
subdir = contrib/sepgsql
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
include $(top_srcdir)/contrib/contrib-global.mk |
||||
endif |
||||
|
||||
SHLIB_LINK += $(filter -lselinux, $(LIBS))
|
||||
REGRESS_OPTS += --launcher $(top_builddir)/contrib/sepgsql/launcher
|
||||
|
||||
sepgsql-regtest.pp: sepgsql-regtest.te |
||||
$(MAKE) -f $(DESTDIR)/usr/share/selinux/devel/Makefile $@
|
@ -0,0 +1,353 @@ |
||||
/* -------------------------------------------------------------------------
|
||||
* |
||||
* contrib/sepgsql/dml.c |
||||
* |
||||
* Routines to handle DML permission checks |
||||
* |
||||
* Copyright (c) 2010-2011, PostgreSQL Global Development Group |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/sysattr.h" |
||||
#include "access/tupdesc.h" |
||||
#include "catalog/catalog.h" |
||||
#include "catalog/heap.h" |
||||
#include "catalog/pg_attribute.h" |
||||
#include "catalog/pg_class.h" |
||||
#include "catalog/pg_inherits_fn.h" |
||||
#include "commands/seclabel.h" |
||||
#include "commands/tablecmds.h" |
||||
#include "executor/executor.h" |
||||
#include "nodes/bitmapset.h" |
||||
#include "utils/lsyscache.h" |
||||
#include "utils/syscache.h" |
||||
|
||||
#include "sepgsql.h" |
||||
|
||||
/*
|
||||
* fixup_whole_row_references |
||||
* |
||||
* When user reference a whole of row, it is equivalent to reference to |
||||
* all the user columns (not system columns). So, we need to fix up the |
||||
* given bitmapset, if it contains a whole of the row reference. |
||||
*/ |
||||
static Bitmapset * |
||||
fixup_whole_row_references(Oid relOid, Bitmapset *columns) |
||||
{ |
||||
Bitmapset *result; |
||||
HeapTuple tuple; |
||||
AttrNumber natts; |
||||
AttrNumber attno; |
||||
int index; |
||||
|
||||
/* if no whole of row references, do not anything */ |
||||
index = InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber; |
||||
if (!bms_is_member(index, columns)) |
||||
return columns; |
||||
|
||||
/* obtain number of attributes */ |
||||
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); |
||||
if (!HeapTupleIsValid(tuple)) |
||||
elog(ERROR, "cache lookup failed for relation %u", relOid); |
||||
natts = ((Form_pg_class) GETSTRUCT(tuple))->relnatts; |
||||
ReleaseSysCache(tuple); |
||||
|
||||
/* fix up the given columns */ |
||||
result = bms_copy(columns); |
||||
result = bms_del_member(result, index); |
||||
|
||||
for (attno=1; attno <= natts; attno++) |
||||
{ |
||||
tuple = SearchSysCache2(ATTNUM, |
||||
ObjectIdGetDatum(relOid), |
||||
Int16GetDatum(attno)); |
||||
if (!HeapTupleIsValid(tuple)) |
||||
continue; |
||||
|
||||
if (((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped) |
||||
continue; |
||||
|
||||
index = attno - FirstLowInvalidHeapAttributeNumber; |
||||
|
||||
result = bms_add_member(result, index); |
||||
|
||||
ReleaseSysCache(tuple); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/*
|
||||
* fixup_inherited_columns |
||||
* |
||||
* When user is querying on a table with children, it implicitly accesses |
||||
* child tables also. So, we also need to check security label of child |
||||
* tables and columns, but here is no guarantee attribute numbers are |
||||
* same between the parent ans children. |
||||
* It returns a bitmapset which contains attribute number of the child |
||||
* table based on the given bitmapset of the parent. |
||||
*/ |
||||
static Bitmapset * |
||||
fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns) |
||||
{ |
||||
AttrNumber attno; |
||||
Bitmapset *tmpset; |
||||
Bitmapset *result = NULL; |
||||
char *attname; |
||||
int index; |
||||
|
||||
/*
|
||||
* obviously, no need to do anything here |
||||
*/ |
||||
if (parentId == childId) |
||||
return columns; |
||||
|
||||
tmpset = bms_copy(columns); |
||||
while ((index = bms_first_member(tmpset)) > 0) |
||||
{ |
||||
attno = index + FirstLowInvalidHeapAttributeNumber; |
||||
/*
|
||||
* whole-row-reference shall be fixed-up later |
||||
*/ |
||||
if (attno == InvalidAttrNumber) |
||||
{ |
||||
result = bms_add_member(result, index); |
||||
continue; |
||||
} |
||||
|
||||
attname = get_attname(parentId, attno); |
||||
if (!attname) |
||||
elog(ERROR, "cache lookup failed for attribute %d of relation %u", |
||||
attno, parentId); |
||||
attno = get_attnum(childId, attname); |
||||
if (attno == InvalidAttrNumber) |
||||
elog(ERROR, "cache lookup failed for attribute %s of relation %u", |
||||
attname, childId); |
||||
|
||||
index = attno - FirstLowInvalidHeapAttributeNumber; |
||||
result = bms_add_member(result, index); |
||||
|
||||
pfree(attname); |
||||
} |
||||
bms_free(tmpset); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/*
|
||||
* check_relation_privileges |
||||
* |
||||
* It actually checks required permissions on a certain relation |
||||
* and its columns. |
||||
*/ |
||||
static bool |
||||
check_relation_privileges(Oid relOid, |
||||
Bitmapset *selected, |
||||
Bitmapset *modified, |
||||
uint32 required, |
||||
bool abort) |
||||
{ |
||||
char relkind = get_rel_relkind(relOid); |
||||
char *scontext = sepgsql_get_client_label(); |
||||
char *tcontext; |
||||
Bitmapset *columns; |
||||
int index; |
||||
bool result = true; |
||||
|
||||
/*
|
||||
* Hardwired Policies: |
||||
* SE-PostgreSQL enforces |
||||
* - clients cannot modify system catalogs using DMLs |
||||
* - clients cannot reference/modify toast relations using DMLs |
||||
*/ |
||||
if (sepgsql_getenforce() > 0) |
||||
{ |
||||
Oid relnamespace = get_rel_namespace(relOid); |
||||
|
||||
if (IsSystemNamespace(relnamespace) && |
||||
(required & (SEPG_DB_TABLE__UPDATE | |
||||
SEPG_DB_TABLE__INSERT | |
||||
SEPG_DB_TABLE__DELETE)) != 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
||||
errmsg("selinux: hardwired security policy violation"))); |
||||
|
||||
if (relkind == RELKIND_TOASTVALUE) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
||||
errmsg("selinux: hardwired security policy violation"))); |
||||
} |
||||
|
||||
/*
|
||||
* Check permissions on the relation |
||||
*/ |
||||
tcontext = sepgsql_get_label(RelationRelationId, relOid, 0); |
||||
switch (relkind) |
||||
{ |
||||
case RELKIND_RELATION: |
||||
result = sepgsql_check_perms(scontext, |
||||
tcontext, |
||||
SEPG_CLASS_DB_TABLE, |
||||
required, |
||||
get_rel_name(relOid), |
||||
abort); |
||||
if (!result) |
||||
return false; |
||||
break; |
||||
|
||||
case RELKIND_SEQUENCE: |
||||
Assert((required & ~SEPG_DB_TABLE__SELECT) == 0); |
||||
|
||||
if (required & SEPG_DB_TABLE__SELECT) |
||||
result = sepgsql_check_perms(scontext, |
||||
tcontext, |
||||
SEPG_CLASS_DB_SEQUENCE, |
||||
SEPG_DB_SEQUENCE__GET_VALUE, |
||||
get_rel_name(relOid), |
||||
abort); |
||||
return result; |
||||
|
||||
case RELKIND_VIEW: |
||||
result = sepgsql_check_perms(scontext, |
||||
tcontext, |
||||
SEPG_CLASS_DB_VIEW, |
||||
SEPG_DB_VIEW__EXPAND, |
||||
get_rel_name(relOid), |
||||
abort); |
||||
return result; |
||||
|
||||
default: |
||||
/* nothing to be checked */ |
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* Check permissions on the columns |
||||
*/ |
||||
selected = fixup_whole_row_references(relOid, selected); |
||||
modified = fixup_whole_row_references(relOid, modified); |
||||
columns = bms_union(selected, modified); |
||||
|
||||
while ((index = bms_first_member(columns)) >= 0) |
||||
{ |
||||
AttrNumber attnum; |
||||
uint32 column_perms = 0; |
||||
char audit_name[NAMEDATALEN * 2 + 10]; |
||||
|
||||
if (bms_is_member(index, selected)) |
||||
column_perms |= SEPG_DB_COLUMN__SELECT; |
||||
if (bms_is_member(index, modified)) |
||||
{ |
||||
if (required & SEPG_DB_TABLE__UPDATE) |
||||
column_perms |= SEPG_DB_COLUMN__UPDATE; |
||||
if (required & SEPG_DB_TABLE__INSERT) |
||||
column_perms |= SEPG_DB_COLUMN__INSERT; |
||||
} |
||||
if (column_perms == 0) |
||||
continue; |
||||
|
||||
/* obtain column's permission */ |
||||
attnum = index + FirstLowInvalidHeapAttributeNumber; |
||||
tcontext = sepgsql_get_label(RelationRelationId, relOid, attnum); |
||||
snprintf(audit_name, sizeof(audit_name), "%s.%s", |
||||
get_rel_name(relOid), get_attname(relOid, attnum)); |
||||
|
||||
result = sepgsql_check_perms(scontext, |
||||
tcontext, |
||||
SEPG_CLASS_DB_COLUMN, |
||||
column_perms, |
||||
audit_name, |
||||
abort); |
||||
if (!result) |
||||
return result; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_dml_privileges |
||||
* |
||||
* Entrypoint of the DML permission checks |
||||
*/ |
||||
bool |
||||
sepgsql_dml_privileges(List *rangeTabls, bool abort) |
||||
{ |
||||
ListCell *lr; |
||||
|
||||
foreach (lr, rangeTabls) |
||||
{ |
||||
RangeTblEntry *rte = lfirst(lr); |
||||
uint32 required = 0; |
||||
List *tableIds; |
||||
ListCell *li; |
||||
|
||||
/*
|
||||
* Only regular relations shall be checked |
||||
*/ |
||||
if (rte->rtekind != RTE_RELATION) |
||||
continue; |
||||
|
||||
/*
|
||||
* Find out required permissions |
||||
*/ |
||||
if (rte->requiredPerms & ACL_SELECT) |
||||
required |= SEPG_DB_TABLE__SELECT; |
||||
if (rte->requiredPerms & ACL_INSERT) |
||||
required |= SEPG_DB_TABLE__INSERT; |
||||
if (rte->requiredPerms & ACL_UPDATE) |
||||
{ |
||||
if (!bms_is_empty(rte->modifiedCols)) |
||||
required |= SEPG_DB_TABLE__UPDATE; |
||||
else |
||||
required |= SEPG_DB_TABLE__LOCK; |
||||
} |
||||
if (rte->requiredPerms & ACL_DELETE) |
||||
required |= SEPG_DB_TABLE__DELETE; |
||||
|
||||
/*
|
||||
* Skip, if nothing to be checked |
||||
*/ |
||||
if (required == 0) |
||||
continue; |
||||
|
||||
/*
|
||||
* If this RangeTblEntry is also supposed to reference inherited |
||||
* tables, we need to check security label of the child tables. |
||||
* So, we expand rte->relid into list of OIDs of inheritance |
||||
* hierarchy, then checker routine will be invoked for each |
||||
* relations. |
||||
*/ |
||||
if (!rte->inh) |
||||
tableIds = list_make1_oid(rte->relid); |
||||
else |
||||
tableIds = find_all_inheritors(rte->relid, NoLock, NULL); |
||||
|
||||
foreach (li, tableIds) |
||||
{ |
||||
Oid tableOid = lfirst_oid(li); |
||||
Bitmapset *selectedCols; |
||||
Bitmapset *modifiedCols; |
||||
|
||||
/*
|
||||
* child table has different attribute numbers, so we need |
||||
* to fix up them. |
||||
*/ |
||||
selectedCols = fixup_inherited_columns(rte->relid, tableOid, |
||||
rte->selectedCols); |
||||
modifiedCols = fixup_inherited_columns(rte->relid, tableOid, |
||||
rte->modifiedCols); |
||||
|
||||
/*
|
||||
* check permissions on individual tables |
||||
*/ |
||||
if (!check_relation_privileges(tableOid, |
||||
selectedCols, |
||||
modifiedCols, |
||||
required, abort)) |
||||
return false; |
||||
} |
||||
list_free(tableIds); |
||||
} |
||||
return true; |
||||
} |
@ -0,0 +1,182 @@ |
||||
-- |
||||
-- Regression Test for DML Permissions |
||||
-- |
||||
-- |
||||
-- Setup |
||||
-- |
||||
CREATE TABLE t1 (a int, b text); |
||||
SECURITY LABEL ON TABLE t1 IS 'system_u:object_r:sepgsql_table_t:s0'; |
||||
INSERT INTO t1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'); |
||||
CREATE TABLE t2 (x int, y text); |
||||
SECURITY LABEL ON TABLE t2 IS 'system_u:object_r:sepgsql_ro_table_t:s0'; |
||||
INSERT INTO t2 VALUES (1, 'xxx'), (2, 'yyy'), (3, 'zzz'); |
||||
CREATE TABLE t3 (s int, t text); |
||||
SECURITY LABEL ON TABLE t3 IS 'system_u:object_r:sepgsql_fixed_table_t:s0'; |
||||
INSERT INTO t3 VALUES (1, 'sss'), (2, 'ttt'), (3, 'uuu'); |
||||
CREATE TABLE t4 (m int, n text); |
||||
SECURITY LABEL ON TABLE t4 IS 'system_u:object_r:sepgsql_secret_table_t:s0'; |
||||
INSERT INTO t4 VALUES (1, 'mmm'), (2, 'nnn'), (3, 'ooo'); |
||||
CREATE TABLE t5 (e text, f text, g text); |
||||
SECURITY LABEL ON TABLE t5 IS 'system_u:object_r:sepgsql_table_t:s0'; |
||||
SECURITY LABEL ON COLUMN t5.e IS 'system_u:object_r:sepgsql_table_t:s0'; |
||||
SECURITY LABEL ON COLUMN t5.f IS 'system_u:object_r:sepgsql_ro_table_t:s0'; |
||||
SECURITY LABEL ON COLUMN t5.g IS 'system_u:object_r:sepgsql_secret_table_t:s0'; |
||||
CREATE TABLE customer (cid int primary key, cname text, ccredit text); |
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "customer_pkey" for table "customer" |
||||
SECURITY LABEL ON COLUMN customer.ccredit IS 'system_u:object_r:sepgsql_secret_table_t:s0'; |
||||
INSERT INTO customer VALUES (1, 'Taro', '1111-2222-3333-4444'), |
||||
(2, 'Hanako', '5555-6666-7777-8888'); |
||||
CREATE FUNCTION customer_credit(int) RETURNS text |
||||
AS 'SELECT regexp_replace(ccredit, ''-[0-9]+$'', ''-????'') FROM customer WHERE cid = $1' |
||||
LANGUAGE sql; |
||||
SECURITY LABEL ON FUNCTION customer_credit(int) |
||||
IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; |
||||
SELECT objtype, objname, label FROM pg_seclabels |
||||
WHERE provider = 'selinux' |
||||
AND objtype in ('table', 'column') |
||||
AND objname in ('t1', 't2', 't3', 't4', 't5', 't5.e', 't5.f', 't5.g'); |
||||
objtype | objname | label |
||||
---------+---------+--------------------------------------------- |
||||
table | t1 | system_u:object_r:sepgsql_table_t:s0 |
||||
table | t2 | system_u:object_r:sepgsql_ro_table_t:s0 |
||||
table | t3 | system_u:object_r:sepgsql_fixed_table_t:s0 |
||||
table | t4 | system_u:object_r:sepgsql_secret_table_t:s0 |
||||
table | t5 | system_u:object_r:sepgsql_table_t:s0 |
||||
column | t5.g | system_u:object_r:sepgsql_secret_table_t:s0 |
||||
column | t5.f | system_u:object_r:sepgsql_ro_table_t:s0 |
||||
column | t5.e | system_u:object_r:sepgsql_table_t:s0 |
||||
(8 rows) |
||||
|
||||
-- Hardwired Rules |
||||
UPDATE pg_attribute SET attisdropped = true |
||||
WHERE attrelid = 't5'::regclass AND attname = 'f'; -- failed |
||||
ERROR: selinux: hardwired security policy violation |
||||
-- |
||||
-- Simple DML statements |
||||
-- |
||||
SELECT sepgsql_getcon(); -- confirm client privilege |
||||
sepgsql_getcon |
||||
----------------------------------------------------- |
||||
unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 |
||||
(1 row) |
||||
|
||||
SELECT * FROM t1; -- ok |
||||
a | b |
||||
---+----- |
||||
1 | aaa |
||||
2 | bbb |
||||
3 | ccc |
||||
(3 rows) |
||||
|
||||
SELECT * FROM t2; -- ok |
||||
x | y |
||||
---+----- |
||||
1 | xxx |
||||
2 | yyy |
||||
3 | zzz |
||||
(3 rows) |
||||
|
||||
SELECT * FROM t3; -- ok |
||||
s | t |
||||
---+----- |
||||
1 | sss |
||||
2 | ttt |
||||
3 | uuu |
||||
(3 rows) |
||||
|
||||
SELECT * FROM t4; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
SELECT * FROM t5; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
SELECT e,f FROM t5; -- ok |
||||
e | f |
||||
---+--- |
||||
(0 rows) |
||||
|
||||
SELECT * FROM customer; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
SELECT cid, cname, customer_credit(cid) FROM customer; -- ok |
||||
cid | cname | customer_credit |
||||
-----+--------+--------------------- |
||||
1 | Taro | 1111-2222-3333-???? |
||||
2 | Hanako | 5555-6666-7777-???? |
||||
(2 rows) |
||||
|
||||
SELECT count(*) FROM t5; -- ok |
||||
count |
||||
------- |
||||
0 |
||||
(1 row) |
||||
|
||||
SELECT count(*) FROM t5 WHERE g IS NULL; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
INSERT INTO t1 VALUES (4, 'abc'); -- ok |
||||
INSERT INTO t2 VALUES (4, 'xyz'); -- failed |
||||
ERROR: SELinux: security policy violation |
||||
INSERT INTO t3 VALUES (4, 'stu'); -- ok |
||||
INSERT INTO t4 VALUES (4, 'mno'); -- failed |
||||
ERROR: SELinux: security policy violation |
||||
INSERT INTO t5 VALUES (1,2,3); -- failed |
||||
ERROR: SELinux: security policy violation |
||||
INSERT INTO t5 (e,f) VALUES ('abc', 'def'); -- failed |
||||
ERROR: SELinux: security policy violation |
||||
INSERT INTO t5 (e) VALUES ('abc'); -- ok |
||||
UPDATE t1 SET b = b || '_upd'; -- ok |
||||
UPDATE t2 SET y = y || '_upd'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
UPDATE t3 SET t = t || '_upd'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
UPDATE t4 SET n = n || '_upd'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
UPDATE t5 SET e = 'xyz'; -- ok |
||||
UPDATE t5 SET e = f || '_upd'; -- ok |
||||
UPDATE t5 SET e = g || '_upd'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
DELETE FROM t1; -- ok |
||||
DELETE FROM t2; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
DELETE FROM t3; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
DELETE FROM t4; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
DELETE FROM t5; -- ok |
||||
DELETE FROM t5 WHERE f IS NULL; -- ok |
||||
DELETE FROM t5 WHERE g IS NULL; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
-- |
||||
-- COPY TO/FROM statements |
||||
-- |
||||
COPY t1 TO '/dev/null'; -- ok |
||||
COPY t2 TO '/dev/null'; -- ok |
||||
COPY t3 TO '/dev/null'; -- ok |
||||
COPY t4 TO '/dev/null'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
COPY t5 TO '/dev/null'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
COPY t5(e,f) TO '/dev/null'; -- ok |
||||
COPY t1 FROM '/dev/null'; -- ok |
||||
COPY t2 FROM '/dev/null'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
COPY t3 FROM '/dev/null'; -- ok |
||||
COPY t4 FROM '/dev/null'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
COPY t5 FROM '/dev/null'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
COPY t5 (e,f) FROM '/dev/null'; -- failed |
||||
ERROR: SELinux: security policy violation |
||||
COPY t5 (e) FROM '/dev/null'; -- ok |
||||
-- |
||||
-- Clean up |
||||
-- |
||||
SELECT sepgsql_getcon(); -- confirm client privilege |
||||
sepgsql_getcon |
||||
------------------------------------------------------ |
||||
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255 |
||||
(1 row) |
||||
|
||||
DROP TABLE IF EXISTS t1 CASCADE; |
||||
DROP TABLE IF EXISTS t2 CASCADE; |
||||
DROP TABLE IF EXISTS t3 CASCADE; |
||||
DROP TABLE IF EXISTS t4 CASCADE; |
||||
DROP TABLE IF EXISTS t5 CASCADE; |
||||
DROP TABLE IF EXISTS customer CASCADE; |
@ -0,0 +1,109 @@ |
||||
-- |
||||
-- Regression Tests for Label Management |
||||
-- |
||||
-- |
||||
-- Setup |
||||
-- |
||||
CREATE TABLE t1 (a int, b text); |
||||
INSERT INTO t1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'); |
||||
SELECT * INTO t2 FROM t1 WHERE a % 2 = 0; |
||||
CREATE FUNCTION f1 () RETURNS text |
||||
AS 'SELECT sepgsql_getcon()' |
||||
LANGUAGE sql; |
||||
CREATE FUNCTION f2 () RETURNS text |
||||
AS 'SELECT sepgsql_getcon()' |
||||
LANGUAGE sql; |
||||
SECURITY LABEL ON FUNCTION f2() |
||||
IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; |
||||
CREATE FUNCTION f3 () RETURNS text |
||||
AS 'BEGIN |
||||
RAISE EXCEPTION ''an exception from f3()''; |
||||
RETURN NULL; |
||||
END;' LANGUAGE plpgsql; |
||||
SECURITY LABEL ON FUNCTION f3() |
||||
IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; |
||||
-- |
||||
-- Tests for default labeling behavior |
||||
-- |
||||
SELECT sepgsql_getcon(); -- confirm client privilege |
||||
sepgsql_getcon |
||||
----------------------------------------------------- |
||||
unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 |
||||
(1 row) |
||||
|
||||
CREATE TABLE t3 (s int, t text); |
||||
INSERT INTO t3 VALUES (1, 'sss'), (2, 'ttt'), (3, 'uuu'); |
||||
SELECT objtype, objname, label FROM pg_seclabels |
||||
WHERE provider = 'selinux' |
||||
AND objtype in ('table', 'column') |
||||
AND objname in ('t1', 't2', 't3'); |
||||
objtype | objname | label |
||||
---------+---------+----------------------------------------------- |
||||
table | t1 | unconfined_u:object_r:sepgsql_table_t:s0 |
||||
table | t2 | unconfined_u:object_r:sepgsql_table_t:s0 |
||||
table | t3 | unconfined_u:object_r:user_sepgsql_table_t:s0 |
||||
(3 rows) |
||||
|
||||
-- |
||||
-- Tests for SECURITY LABEL |
||||
-- |
||||
SELECT sepgsql_getcon(); -- confirm client privilege |
||||
sepgsql_getcon |
||||
---------------------------------------------------- |
||||
unconfined_u:unconfined_r:sepgsql_regtest_dba_t:s0 |
||||
(1 row) |
||||
|
||||
SECURITY LABEL ON TABLE t1 |
||||
IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok |
||||
SECURITY LABEL ON TABLE t2 |
||||
IS 'invalid seuciryt context'; -- be failed |
||||
ERROR: invalid security label: "invalid seuciryt context" |
||||
SECURITY LABEL ON COLUMN t2 |
||||
IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- be failed |
||||
ERROR: improper relation name (too many dotted names): |
||||
SECURITY LABEL ON COLUMN t2.b |
||||
IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok |
||||
-- |
||||
-- Tests for Trusted Procedures |
||||
-- |
||||
SELECT sepgsql_getcon(); -- confirm client privilege |
||||
sepgsql_getcon |
||||
----------------------------------------------------- |
||||
unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 |
||||
(1 row) |
||||
|
||||
SELECT f1(); -- normal procedure |
||||
f1 |
||||
----------------------------------------------------- |
||||
unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 |
||||
(1 row) |
||||
|
||||
SELECT f2(); -- trusted procedure |
||||
f2 |
||||
----------------------------------------------------- |
||||
unconfined_u:unconfined_r:sepgsql_trusted_proc_t:s0 |
||||
(1 row) |
||||
|
||||
SELECT f3(); -- trusted procedure that raises an error |
||||
ERROR: an exception from f3() |
||||
SELECT sepgsql_getcon(); -- client's label must be restored |
||||
sepgsql_getcon |
||||
----------------------------------------------------- |
||||
unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 |
||||
(1 row) |
||||
|
||||
-- |
||||
-- Clean up |
||||
-- |
||||
SELECT sepgsql_getcon(); -- confirm client privilege |
||||
sepgsql_getcon |
||||
------------------------------------------------------ |
||||
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255 |
||||
(1 row) |
||||
|
||||
DROP TABLE IF EXISTS t1 CASCADE; |
||||
DROP TABLE IF EXISTS t2 CASCADE; |
||||
DROP TABLE IF EXISTS t3 CASCADE; |
||||
DROP FUNCTION IF EXISTS f1() CASCADE; |
||||
DROP FUNCTION IF EXISTS f2() CASCADE; |
||||
DROP FUNCTION IF EXISTS f3() CASCADE; |
@ -0,0 +1,5 @@ |
||||
-- |
||||
-- Regression Test for Misc Permission Checks |
||||
-- |
||||
LOAD '$libdir/sepgsql'; -- failed |
||||
ERROR: SELinux: LOAD is not allowed anyway. |
@ -0,0 +1,446 @@ |
||||
/* -------------------------------------------------------------------------
|
||||
* |
||||
* contrib/sepgsql/hooks.c |
||||
* |
||||
* Entrypoints of the hooks in PostgreSQL, and dispatches the callbacks. |
||||
* |
||||
* Copyright (c) 2010-2011, PostgreSQL Global Development Group |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "catalog/objectaccess.h" |
||||
#include "catalog/pg_class.h" |
||||
#include "catalog/pg_namespace.h" |
||||
#include "catalog/pg_proc.h" |
||||
#include "commands/seclabel.h" |
||||
#include "executor/executor.h" |
||||
#include "fmgr.h" |
||||
#include "libpq/auth.h" |
||||
#include "miscadmin.h" |
||||
#include "tcop/utility.h" |
||||
#include "utils/guc.h" |
||||
|
||||
#include "sepgsql.h" |
||||
|
||||
PG_MODULE_MAGIC; |
||||
|
||||
/*
|
||||
* Declarations |
||||
*/ |
||||
void _PG_init(void); |
||||
|
||||
/*
|
||||
* Saved hook entries (if stacked) |
||||
*/ |
||||
static object_access_hook_type next_object_access_hook = NULL; |
||||
static ClientAuthentication_hook_type next_client_auth_hook = NULL; |
||||
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; |
||||
|
||||
/*
|
||||
* GUC: sepgsql.permissive = (on|off) |
||||
*/ |
||||
static bool sepgsql_permissive; |
||||
|
||||
bool |
||||
sepgsql_get_permissive(void) |
||||
{ |
||||
return sepgsql_permissive; |
||||
} |
||||
|
||||
/*
|
||||
* GUC: sepgsql.debug_audit = (on|off) |
||||
*/ |
||||
static bool sepgsql_debug_audit; |
||||
|
||||
bool |
||||
sepgsql_get_debug_audit(void) |
||||
{ |
||||
return sepgsql_debug_audit; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_client_auth |
||||
* |
||||
* Entrypoint of the client authentication hook. |
||||
* It switches the client label according to getpeercon(), and the current |
||||
* performing mode according to the GUC setting. |
||||
*/ |
||||
static void |
||||
sepgsql_client_auth(Port *port, int status) |
||||
{ |
||||
char *context; |
||||
|
||||
if (next_client_auth_hook) |
||||
(*next_client_auth_hook)(port, status); |
||||
|
||||
/*
|
||||
* In the case when authentication failed, the supplied socket |
||||
* shall be closed soon, so we don't need to do anything here. |
||||
*/ |
||||
if (status != STATUS_OK) |
||||
return; |
||||
|
||||
/*
|
||||
* Getting security label of the peer process using API of libselinux. |
||||
*/ |
||||
if (getpeercon_raw(port->sock, &context) < 0) |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_INTERNAL_ERROR), |
||||
errmsg("selinux: failed to get the peer label"))); |
||||
|
||||
sepgsql_set_client_label(context); |
||||
|
||||
/*
|
||||
* Switch the current performing mode from INTERNAL to either |
||||
* DEFAULT or PERMISSIVE. |
||||
*/ |
||||
if (sepgsql_permissive) |
||||
sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE); |
||||
else |
||||
sepgsql_set_mode(SEPGSQL_MODE_DEFAULT); |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_object_access |
||||
* |
||||
* Entrypoint of the object_access_hook. This routine performs as |
||||
* a dispatcher of invocation based on access type and object classes. |
||||
*/ |
||||
static void |
||||
sepgsql_object_access(ObjectAccessType access, |
||||
Oid classId, |
||||
Oid objectId, |
||||
int subId) |
||||
{ |
||||
if (next_object_access_hook) |
||||
(*next_object_access_hook)(access, classId, objectId, subId); |
||||
|
||||
switch (access) |
||||
{ |
||||
case OAT_POST_CREATE: |
||||
switch (classId) |
||||
{ |
||||
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; |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
elog(ERROR, "unexpected object access type: %d", (int)access); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_exec_check_perms |
||||
* |
||||
* Entrypoint of DML permissions |
||||
*/ |
||||
static bool |
||||
sepgsql_exec_check_perms(List *rangeTabls, bool abort) |
||||
{ |
||||
/*
|
||||
* If security provider is stacking and one of them replied 'false' |
||||
* at least, we don't need to check any more. |
||||
*/ |
||||
if (next_exec_check_perms_hook && |
||||
!(*next_exec_check_perms_hook)(rangeTabls, abort)) |
||||
return false; |
||||
|
||||
if (!sepgsql_dml_privileges(rangeTabls, abort)) |
||||
return false; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_needs_fmgr_hook |
||||
* |
||||
* It informs the core whether the supplied function is trusted procedure, |
||||
* or not. If true, sepgsql_fmgr_hook shall be invoked at start, end, and |
||||
* abort time of function invocation. |
||||
*/ |
||||
static bool |
||||
sepgsql_needs_fmgr_hook(Oid functionId) |
||||
{ |
||||
char *old_label; |
||||
char *new_label; |
||||
char *function_label; |
||||
|
||||
if (next_needs_fmgr_hook && |
||||
(*next_needs_fmgr_hook)(functionId)) |
||||
return true; |
||||
|
||||
/*
|
||||
* SELinux needs the function to be called via security_definer |
||||
* wrapper, if this invocation will take a domain-transition. |
||||
* We call these functions as trusted-procedure, if the security |
||||
* policy has a rule that switches security label of the client |
||||
* on execution. |
||||
*/ |
||||
old_label = sepgsql_get_client_label(); |
||||
new_label = sepgsql_proc_get_domtrans(functionId); |
||||
if (strcmp(old_label, new_label) != 0) |
||||
{ |
||||
pfree(new_label); |
||||
return true; |
||||
} |
||||
pfree(new_label); |
||||
|
||||
/*
|
||||
* Even if not a trusted-procedure, this function should not be inlined |
||||
* unless the client has db_procedure:{execute} permission. |
||||
* Please note that it shall be actually failed later because of same |
||||
* reason with ACL_EXECUTE. |
||||
*/ |
||||
function_label = sepgsql_get_label(ProcedureRelationId, functionId, 0); |
||||
if (sepgsql_check_perms(sepgsql_get_client_label(), |
||||
function_label, |
||||
SEPG_CLASS_DB_PROCEDURE, |
||||
SEPG_DB_PROCEDURE__EXECUTE, |
||||
NULL, false) != true) |
||||
{ |
||||
pfree(function_label); |
||||
return true; |
||||
} |
||||
pfree(function_label); |
||||
return false; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_fmgr_hook |
||||
* |
||||
* It switches security label of the client on execution of trusted |
||||
* procedures. |
||||
*/ |
||||
static void |
||||
sepgsql_fmgr_hook(FmgrHookEventType event, |
||||
FmgrInfo *flinfo, Datum *private) |
||||
{ |
||||
struct { |
||||
char *old_label; |
||||
char *new_label; |
||||
Datum next_private; |
||||
} *stack; |
||||
|
||||
switch (event) |
||||
{ |
||||
case FHET_START: |
||||
stack = (void *)DatumGetPointer(*private); |
||||
if (!stack) |
||||
{ |
||||
MemoryContext oldcxt; |
||||
|
||||
oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt); |
||||
stack = palloc(sizeof(*stack)); |
||||
stack->old_label = NULL; |
||||
stack->new_label = sepgsql_proc_get_domtrans(flinfo->fn_oid); |
||||
stack->next_private = 0; |
||||
|
||||
MemoryContextSwitchTo(oldcxt); |
||||
|
||||
*private = PointerGetDatum(stack); |
||||
} |
||||
Assert(!stack->old_label); |
||||
stack->old_label = sepgsql_set_client_label(stack->new_label); |
||||
|
||||
if (next_fmgr_hook) |
||||
(*next_fmgr_hook)(event, flinfo, &stack->next_private); |
||||
break; |
||||
|
||||
case FHET_END: |
||||
case FHET_ABORT: |
||||
stack = (void *)DatumGetPointer(*private); |
||||
|
||||
if (next_fmgr_hook) |
||||
(*next_fmgr_hook)(event, flinfo, &stack->next_private); |
||||
|
||||
sepgsql_set_client_label(stack->old_label); |
||||
stack->old_label = NULL; |
||||
break; |
||||
|
||||
default: |
||||
elog(ERROR, "unexpected event type: %d", (int)event); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_utility_command |
||||
* |
||||
* It tries to rough-grained control on utility commands; some of them can |
||||
* break whole of the things if nefarious user would use. |
||||
*/ |
||||
static void |
||||
sepgsql_utility_command(Node *parsetree, |
||||
const char *queryString, |
||||
ParamListInfo params, |
||||
bool isTopLevel, |
||||
DestReceiver *dest, |
||||
char *completionTag) |
||||
{ |
||||
if (next_ProcessUtility_hook) |
||||
(*next_ProcessUtility_hook)(parsetree, queryString, params, |
||||
isTopLevel, dest, completionTag); |
||||
|
||||
/*
|
||||
* Check command tag to avoid nefarious operations |
||||
*/ |
||||
switch (nodeTag(parsetree)) |
||||
{ |
||||
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 allowed anyway."))); |
||||
} |
||||
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; |
||||
} |
||||
|
||||
/*
|
||||
* Original implementation |
||||
*/ |
||||
standard_ProcessUtility(parsetree, queryString, params, |
||||
isTopLevel, dest, completionTag); |
||||
} |
||||
|
||||
/*
|
||||
* Module load/unload callback |
||||
*/ |
||||
void |
||||
_PG_init(void) |
||||
{ |
||||
char *context; |
||||
|
||||
/*
|
||||
* We allow to load the SE-PostgreSQL module on single-user-mode or |
||||
* shared_preload_libraries settings only. |
||||
*/ |
||||
if (IsUnderPostmaster) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("Not allowed to load SE-PostgreSQL now"))); |
||||
|
||||
/*
|
||||
* Check availability of SELinux on the platform. |
||||
* If disabled, we cannot activate any SE-PostgreSQL features, |
||||
* and we have to skip rest of initialization. |
||||
*/ |
||||
if (is_selinux_enabled() < 1) |
||||
{ |
||||
sepgsql_set_mode(SEPGSQL_MODE_DISABLED); |
||||
return; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql.permissive = (on|off) |
||||
* |
||||
* This variable controls performing mode of SE-PostgreSQL |
||||
* on user's session. |
||||
*/ |
||||
DefineCustomBoolVariable("sepgsql.permissive", |
||||
"Turn on/off permissive mode in SE-PostgreSQL", |
||||
NULL, |
||||
&sepgsql_permissive, |
||||
false, |
||||
PGC_SIGHUP, |
||||
GUC_NOT_IN_SAMPLE, |
||||
NULL, |
||||
NULL); |
||||
|
||||
/*
|
||||
* sepgsql.debug_audit = (on|off) |
||||
* |
||||
* This variable allows users to turn on/off audit logs on access |
||||
* control decisions, independent from auditallow/auditdeny setting |
||||
* in the security policy. |
||||
* We intend to use this option for debugging purpose. |
||||
*/ |
||||
DefineCustomBoolVariable("sepgsql.debug_audit", |
||||
"Turn on/off debug audit messages", |
||||
NULL, |
||||
&sepgsql_debug_audit, |
||||
false, |
||||
PGC_USERSET, |
||||
GUC_NOT_IN_SAMPLE, |
||||
NULL, |
||||
NULL); |
||||
|
||||
/*
|
||||
* Set up dummy client label. |
||||
* |
||||
* XXX - note that PostgreSQL launches background worker process |
||||
* like autovacuum without authentication steps. So, we initialize |
||||
* sepgsql_mode with SEPGSQL_MODE_INTERNAL, and client_label with |
||||
* the security context of server process. |
||||
* Later, it also launches background of user session. In this case, |
||||
* the process is always hooked on post-authentication, and we can |
||||
* initialize the sepgsql_mode and client_label correctly. |
||||
*/ |
||||
if (getcon_raw(&context) < 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INTERNAL_ERROR), |
||||
errmsg("selinux: unable to get security label of server"))); |
||||
sepgsql_set_client_label(context); |
||||
|
||||
/* Security label provider hook */ |
||||
register_label_provider(SEPGSQL_LABEL_TAG, |
||||
sepgsql_object_relabel); |
||||
|
||||
/* Client authentication hook */ |
||||
next_client_auth_hook = ClientAuthentication_hook; |
||||
ClientAuthentication_hook = sepgsql_client_auth; |
||||
|
||||
/* Object access hook */ |
||||
next_object_access_hook = object_access_hook; |
||||
object_access_hook = sepgsql_object_access; |
||||
|
||||
/* DML permission check */ |
||||
next_exec_check_perms_hook = ExecutorCheckPerms_hook; |
||||
ExecutorCheckPerms_hook = sepgsql_exec_check_perms; |
||||
|
||||
/* Trusted procedure hooks */ |
||||
next_needs_fmgr_hook = needs_fmgr_hook; |
||||
needs_fmgr_hook = sepgsql_needs_fmgr_hook; |
||||
|
||||
next_fmgr_hook = fmgr_hook; |
||||
fmgr_hook = sepgsql_fmgr_hook; |
||||
|
||||
/* ProcessUtility hook */ |
||||
next_ProcessUtility_hook = ProcessUtility_hook; |
||||
ProcessUtility_hook = sepgsql_utility_command; |
||||
} |
@ -0,0 +1,477 @@ |
||||
/* -------------------------------------------------------------------------
|
||||
* |
||||
* contrib/sepgsql/label.c |
||||
* |
||||
* Routines to support SELinux labels (security context) |
||||
* |
||||
* Copyright (c) 2010-2011, PostgreSQL Global Development Group |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/heapam.h" |
||||
#include "access/genam.h" |
||||
#include "catalog/catalog.h" |
||||
#include "catalog/dependency.h" |
||||
#include "catalog/indexing.h" |
||||
#include "catalog/pg_attribute.h" |
||||
#include "catalog/pg_class.h" |
||||
#include "catalog/pg_namespace.h" |
||||
#include "catalog/pg_proc.h" |
||||
#include "commands/dbcommands.h" |
||||
#include "commands/seclabel.h" |
||||
#include "libpq/libpq-be.h" |
||||
#include "miscadmin.h" |
||||
#include "utils/builtins.h" |
||||
#include "utils/fmgroids.h" |
||||
#include "utils/lsyscache.h" |
||||
#include "utils/rel.h" |
||||
#include "utils/tqual.h" |
||||
|
||||
#include "sepgsql.h" |
||||
|
||||
#include <selinux/label.h> |
||||
|
||||
/*
|
||||
* client_label |
||||
* |
||||
* security label of the client process |
||||
*/ |
||||
static char *client_label = NULL; |
||||
|
||||
char * |
||||
sepgsql_get_client_label(void) |
||||
{ |
||||
return client_label; |
||||
} |
||||
|
||||
char * |
||||
sepgsql_set_client_label(char *new_label) |
||||
{ |
||||
char *old_label = client_label; |
||||
|
||||
client_label = new_label; |
||||
|
||||
return old_label; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_get_label |
||||
* |
||||
* It returns a security context of the specified database object. |
||||
* If unlabeled or incorrectly labeled, the system "unlabeled" label |
||||
* shall be returned. |
||||
*/ |
||||
char * |
||||
sepgsql_get_label(Oid classId, Oid objectId, int32 subId) |
||||
{ |
||||
ObjectAddress object; |
||||
char *label; |
||||
|
||||
object.classId = classId; |
||||
object.objectId = objectId; |
||||
object.objectSubId = subId; |
||||
|
||||
label = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG); |
||||
if (!label || security_check_context_raw((security_context_t)label)) |
||||
{ |
||||
security_context_t unlabeled; |
||||
|
||||
if (security_get_initial_context_raw("unlabeled", &unlabeled) < 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INTERNAL_ERROR), |
||||
errmsg("selinux: unable to get initial security label"))); |
||||
PG_TRY(); |
||||
{ |
||||
label = pstrdup(unlabeled); |
||||
} |
||||
PG_CATCH(); |
||||
{ |
||||
freecon(unlabeled); |
||||
PG_RE_THROW(); |
||||
} |
||||
PG_END_TRY(); |
||||
|
||||
freecon(unlabeled); |
||||
} |
||||
return label; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_object_relabel |
||||
* |
||||
* An entrypoint of SECURITY LABEL statement |
||||
*/ |
||||
void |
||||
sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel) |
||||
{ |
||||
/*
|
||||
* validate format of the supplied security label, |
||||
* if it is security context of selinux. |
||||
*/ |
||||
if (seclabel && |
||||
security_check_context_raw((security_context_t) seclabel) < 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INVALID_NAME), |
||||
errmsg("invalid security label: \"%s\"", seclabel))); |
||||
/*
|
||||
* Do actual permission checks for each object classes |
||||
*/ |
||||
switch (object->classId) |
||||
{ |
||||
case NamespaceRelationId: |
||||
sepgsql_schema_relabel(object->objectId, seclabel); |
||||
break; |
||||
case RelationRelationId: |
||||
if (object->objectSubId == 0) |
||||
sepgsql_relation_relabel(object->objectId, |
||||
seclabel); |
||||
else |
||||
sepgsql_attribute_relabel(object->objectId, |
||||
object->objectSubId, |
||||
seclabel); |
||||
break; |
||||
case ProcedureRelationId: |
||||
sepgsql_proc_relabel(object->objectId, seclabel); |
||||
break; |
||||
|
||||
default: |
||||
elog(ERROR, "unsupported object type: %u", object->classId); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* TEXT sepgsql_getcon(VOID) |
||||
* |
||||
* It returns the security label of the client. |
||||
*/ |
||||
PG_FUNCTION_INFO_V1(sepgsql_getcon); |
||||
Datum |
||||
sepgsql_getcon(PG_FUNCTION_ARGS) |
||||
{ |
||||
char *client_label; |
||||
|
||||
if (!sepgsql_is_enabled()) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("SELinux: now disabled"))); |
||||
|
||||
client_label = sepgsql_get_client_label(); |
||||
|
||||
PG_RETURN_POINTER(cstring_to_text(client_label)); |
||||
} |
||||
|
||||
/*
|
||||
* TEXT sepgsql_mcstrans_in(TEXT) |
||||
* |
||||
* It translate the given qualified MLS/MCS range into raw format |
||||
* when mcstrans daemon is working. |
||||
*/ |
||||
PG_FUNCTION_INFO_V1(sepgsql_mcstrans_in); |
||||
Datum |
||||
sepgsql_mcstrans_in(PG_FUNCTION_ARGS) |
||||
{ |
||||
text *label = PG_GETARG_TEXT_P(0); |
||||
char *raw_label; |
||||
char *result; |
||||
|
||||
if (!sepgsql_is_enabled()) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("SELinux: now disabled"))); |
||||
|
||||
if (selinux_trans_to_raw_context(text_to_cstring(label), |
||||
&raw_label) < 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INTERNAL_ERROR), |
||||
errmsg("SELinux: internal error on mcstrans"))); |
||||
|
||||
PG_TRY(); |
||||
{ |
||||
result = pstrdup(raw_label); |
||||
} |
||||
PG_CATCH(); |
||||
{ |
||||
freecon(raw_label); |
||||
PG_RE_THROW(); |
||||
} |
||||
PG_END_TRY(); |
||||
freecon(raw_label); |
||||
|
||||
PG_RETURN_POINTER(cstring_to_text(result)); |
||||
} |
||||
|
||||
/*
|
||||
* TEXT sepgsql_mcstrans_out(TEXT) |
||||
* |
||||
* It translate the given raw MLS/MCS range into qualified format |
||||
* when mcstrans daemon is working. |
||||
*/ |
||||
PG_FUNCTION_INFO_V1(sepgsql_mcstrans_out); |
||||
Datum |
||||
sepgsql_mcstrans_out(PG_FUNCTION_ARGS) |
||||
{ |
||||
text *label = PG_GETARG_TEXT_P(0); |
||||
char *qual_label; |
||||
char *result; |
||||
|
||||
if (!sepgsql_is_enabled()) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("SELinux: now disabled"))); |
||||
|
||||
if (selinux_raw_to_trans_context(text_to_cstring(label), |
||||
&qual_label) < 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INTERNAL_ERROR), |
||||
errmsg("SELinux: internal error on mcstrans"))); |
||||
|
||||
PG_TRY(); |
||||
{ |
||||
result = pstrdup(qual_label); |
||||
} |
||||
PG_CATCH(); |
||||
{ |
||||
freecon(qual_label); |
||||
PG_RE_THROW(); |
||||
} |
||||
PG_END_TRY(); |
||||
freecon(qual_label); |
||||
|
||||
PG_RETURN_POINTER(cstring_to_text(result)); |
||||
} |
||||
|
||||
/*
|
||||
* exec_object_restorecon |
||||
* |
||||
* This routine is a helper called by sepgsql_restorecon; it set up |
||||
* initial security labels of database objects within the supplied |
||||
* catalog OID. |
||||
*/ |
||||
static void |
||||
exec_object_restorecon(struct selabel_handle *sehnd, Oid catalogId) |
||||
{ |
||||
Relation rel; |
||||
SysScanDesc sscan; |
||||
HeapTuple tuple; |
||||
char *database_name = get_database_name(MyDatabaseId); |
||||
char *namespace_name; |
||||
Oid namespace_id; |
||||
char *relation_name; |
||||
|
||||
/*
|
||||
* Open the target catalog. We don't want to allow writable |
||||
* accesses by other session during initial labeling. |
||||
*/ |
||||
rel = heap_open(catalogId, AccessShareLock); |
||||
|
||||
sscan = systable_beginscan(rel, InvalidOid, false, |
||||
SnapshotNow, 0, NULL); |
||||
while (HeapTupleIsValid(tuple = systable_getnext(sscan))) |
||||
{ |
||||
Form_pg_namespace nspForm; |
||||
Form_pg_class relForm; |
||||
Form_pg_attribute attForm; |
||||
Form_pg_proc proForm; |
||||
char objname[NAMEDATALEN * 4 + 10]; |
||||
int objtype = 1234; |
||||
ObjectAddress object; |
||||
security_context_t context; |
||||
|
||||
/*
|
||||
* The way to determine object name depends on object classes. |
||||
* So, any branches set up `objtype', `objname' and `object' here. |
||||
*/ |
||||
switch (catalogId) |
||||
{ |
||||
case NamespaceRelationId: |
||||
nspForm = (Form_pg_namespace) GETSTRUCT(tuple); |
||||
|
||||
objtype = SELABEL_DB_SCHEMA; |
||||
snprintf(objname, sizeof(objname), "%s.%s", |
||||
database_name, NameStr(nspForm->nspname)); |
||||
|
||||
object.classId = NamespaceRelationId; |
||||
object.objectId = HeapTupleGetOid(tuple); |
||||
object.objectSubId = 0; |
||||
break; |
||||
|
||||
case RelationRelationId: |
||||
relForm = (Form_pg_class) GETSTRUCT(tuple); |
||||
|
||||
if (relForm->relkind == RELKIND_RELATION) |
||||
objtype = SELABEL_DB_TABLE; |
||||
else if (relForm->relkind == RELKIND_SEQUENCE) |
||||
objtype = SELABEL_DB_SEQUENCE; |
||||
else if (relForm->relkind == RELKIND_VIEW) |
||||
objtype = SELABEL_DB_VIEW; |
||||
else |
||||
continue; /* no need to assign security label */ |
||||
|
||||
namespace_name = get_namespace_name(relForm->relnamespace); |
||||
snprintf(objname, sizeof(objname), "%s.%s.%s", |
||||
database_name, namespace_name, |
||||
NameStr(relForm->relname)); |
||||
pfree(namespace_name); |
||||
|
||||
object.classId = RelationRelationId; |
||||
object.objectId = HeapTupleGetOid(tuple); |
||||
object.objectSubId = 0; |
||||
break; |
||||
|
||||
case AttributeRelationId: |
||||
attForm = (Form_pg_attribute) GETSTRUCT(tuple); |
||||
|
||||
if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION) |
||||
continue; /* no need to assign security label */ |
||||
|
||||
objtype = SELABEL_DB_COLUMN; |
||||
|
||||
namespace_id = get_rel_namespace(attForm->attrelid); |
||||
namespace_name = get_namespace_name(namespace_id); |
||||
relation_name = get_rel_name(attForm->attrelid); |
||||
snprintf(objname, sizeof(objname), "%s.%s.%s.%s", |
||||
database_name, namespace_name, |
||||
relation_name, NameStr(attForm->attname)); |
||||
pfree(relation_name); |
||||
pfree(namespace_name); |
||||
|
||||
object.classId = RelationRelationId; |
||||
object.objectId = attForm->attrelid; |
||||
object.objectSubId = attForm->attnum; |
||||
break; |
||||
|
||||
case ProcedureRelationId: |
||||
proForm = (Form_pg_proc) GETSTRUCT(tuple); |
||||
|
||||
objtype = SELABEL_DB_PROCEDURE; |
||||
|
||||
namespace_name = get_namespace_name(proForm->pronamespace); |
||||
snprintf(objname, sizeof(objname), "%s.%s.%s", |
||||
database_name, namespace_name, |
||||
NameStr(proForm->proname)); |
||||
pfree(namespace_name); |
||||
|
||||
object.classId = ProcedureRelationId; |
||||
object.objectId = HeapTupleGetOid(tuple); |
||||
object.objectSubId = 0; |
||||
break; |
||||
|
||||
default: |
||||
elog(ERROR, "Bug? %u is not supported to set initial labels", |
||||
catalogId); |
||||
break; |
||||
} |
||||
|
||||
if (selabel_lookup_raw(sehnd, &context, objname, objtype) == 0) |
||||
{ |
||||
PG_TRY(); |
||||
{ |
||||
/*
|
||||
* Check SELinux permission to relabel the fetched object, |
||||
* then do the actual relabeling. |
||||
*/ |
||||
sepgsql_object_relabel(&object, context); |
||||
|
||||
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, context); |
||||
} |
||||
PG_CATCH(); |
||||
{ |
||||
freecon(context); |
||||
PG_RE_THROW(); |
||||
} |
||||
PG_END_TRY(); |
||||
freecon(context); |
||||
} |
||||
else if (errno == ENOENT) |
||||
ereport(WARNING, |
||||
(errmsg("no valid initial label on %s (type=%d), skipped", |
||||
objname, objtype))); |
||||
else |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INTERNAL_ERROR), |
||||
errmsg("libselinux: internal error"))); |
||||
} |
||||
systable_endscan(sscan); |
||||
|
||||
heap_close(rel, NoLock); |
||||
} |
||||
|
||||
/*
|
||||
* BOOL sepgsql_restorecon(TEXT specfile) |
||||
* |
||||
* This function tries to assign initial security labels on all the object |
||||
* within the current database, according to the system setting. |
||||
* It is typically invoked by sepgsql-install script just after initdb, to |
||||
* assign initial security labels. |
||||
* |
||||
* If @specfile is not NULL, it uses explicitly specified specfile, instead |
||||
* of the system default. |
||||
*/ |
||||
PG_FUNCTION_INFO_V1(sepgsql_restorecon); |
||||
Datum |
||||
sepgsql_restorecon(PG_FUNCTION_ARGS) |
||||
{ |
||||
struct selabel_handle *sehnd; |
||||
struct selinux_opt seopts; |
||||
|
||||
/*
|
||||
* SELinux has to be enabled on the running platform. |
||||
*/ |
||||
if (!sepgsql_is_enabled()) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("SELinux: now disabled"))); |
||||
/*
|
||||
* Check DAC permission. Only superuser can set up initial |
||||
* security labels, like root-user in filesystems |
||||
*/ |
||||
if (!superuser()) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
||||
errmsg("must be superuser to restore initial contexts"))); |
||||
|
||||
/*
|
||||
* Open selabel_lookup(3) stuff. It provides a set of mapping |
||||
* between an initial security label and object class/name due |
||||
* to the system setting. |
||||
*/ |
||||
if (PG_ARGISNULL(0)) |
||||
{ |
||||
seopts.type = SELABEL_OPT_UNUSED; |
||||
seopts.value = NULL; |
||||
} |
||||
else |
||||
{ |
||||
seopts.type = SELABEL_OPT_PATH; |
||||
seopts.value = TextDatumGetCString(PG_GETARG_DATUM(0)); |
||||
} |
||||
sehnd = selabel_open(SELABEL_CTX_DB, &seopts, 1); |
||||
if (!sehnd) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INTERNAL_ERROR), |
||||
errmsg("SELinux internal error"))); |
||||
PG_TRY(); |
||||
{ |
||||
/*
|
||||
* Right now, we have no support labeling on the shared |
||||
* database objects, such as database, role, or tablespace. |
||||
*/ |
||||
exec_object_restorecon(sehnd, NamespaceRelationId); |
||||
exec_object_restorecon(sehnd, RelationRelationId); |
||||
exec_object_restorecon(sehnd, AttributeRelationId); |
||||
exec_object_restorecon(sehnd, ProcedureRelationId); |
||||
} |
||||
PG_CATCH(); |
||||
{ |
||||
selabel_close(sehnd); |
||||
PG_RE_THROW(); |
||||
} |
||||
PG_END_TRY();
|
||||
|
||||
selabel_close(sehnd); |
||||
|
||||
PG_RETURN_BOOL(true); |
||||
} |
@ -0,0 +1,52 @@ |
||||
#!/bin/sh |
||||
# |
||||
# A wrapper script to launch psql command in regression test |
||||
# |
||||
# Copyright (c) 2010-2011, PostgreSQL Global Development Group |
||||
# |
||||
# ------------------------------------------------------------------------- |
||||
|
||||
if [ $# -lt 1 ]; then |
||||
echo "usage: `basename $0` <command> [options...]" |
||||
exit 1 |
||||
fi |
||||
|
||||
RUNCON=`which runcon` |
||||
if [ ! -e "$RUNCON" ]; then |
||||
echo "runcon command is not found" |
||||
exit 1 |
||||
fi |
||||
|
||||
# |
||||
# Read SQL from stdin |
||||
# |
||||
TEMP=`mktemp` |
||||
CONTEXT="" |
||||
|
||||
while IFS='\\n' read LINE |
||||
do |
||||
if echo "$LINE" | grep -q "^-- @SECURITY-CONTEXT="; then |
||||
if [ -s "$TEMP" ]; then |
||||
if [ -n "$CONTEXT" ]; then |
||||
"$RUNCON" "$CONTEXT" $* < "$TEMP" |
||||
else |
||||
$* < $TEMP |
||||
fi |
||||
truncate -s0 $TEMP |
||||
fi |
||||
CONTEXT=`echo "$LINE" | sed 's/^-- @SECURITY-CONTEXT=//g'` |
||||
LINE="SELECT sepgsql_getcon(); -- confirm client privilege" |
||||
fi |
||||
echo "$LINE" >> $TEMP |
||||
done |
||||
|
||||
if [ -s "$TEMP" ]; then |
||||
if [ -n "$CONTEXT" ]; then |
||||
"$RUNCON" "$CONTEXT" $* < "$TEMP" |
||||
else |
||||
$* < $TEMP |
||||
fi |
||||
fi |
||||
|
||||
# cleanup temp file |
||||
rm -f $TEMP |
@ -0,0 +1,158 @@ |
||||
/* -------------------------------------------------------------------------
|
||||
* |
||||
* contrib/sepgsql/proc.c |
||||
* |
||||
* Routines corresponding to procedure objects |
||||
* |
||||
* Copyright (c) 2010-2011, PostgreSQL Global Development Group |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/genam.h" |
||||
#include "access/heapam.h" |
||||
#include "access/sysattr.h" |
||||
#include "catalog/indexing.h" |
||||
#include "catalog/pg_namespace.h" |
||||
#include "catalog/pg_proc.h" |
||||
#include "commands/seclabel.h" |
||||
#include "utils/fmgroids.h" |
||||
#include "utils/lsyscache.h" |
||||
#include "utils/tqual.h" |
||||
|
||||
#include "sepgsql.h" |
||||
|
||||
/*
|
||||
* sepgsql_proc_post_create |
||||
* |
||||
* This routine assigns a default security label on a newly defined |
||||
* procedure. |
||||
*/ |
||||
void |
||||
sepgsql_proc_post_create(Oid functionId) |
||||
{ |
||||
Relation rel; |
||||
ScanKeyData skey; |
||||
SysScanDesc sscan; |
||||
HeapTuple tuple; |
||||
Oid namespaceId; |
||||
ObjectAddress object; |
||||
char *scontext; |
||||
char *tcontext; |
||||
char *ncontext; |
||||
|
||||
/*
|
||||
* Fetch namespace of the new procedure. Because pg_proc entry is not |
||||
* visible right now, we need to scan the catalog using SnapshotSelf. |
||||
*/ |
||||
rel = heap_open(ProcedureRelationId, AccessShareLock); |
||||
|
||||
ScanKeyInit(&skey, |
||||
ObjectIdAttributeNumber, |
||||
BTEqualStrategyNumber, F_OIDEQ, |
||||
ObjectIdGetDatum(functionId)); |
||||
|
||||
sscan = systable_beginscan(rel, ProcedureOidIndexId, true, |
||||
SnapshotSelf, 1, &skey); |
||||
|
||||
tuple = systable_getnext(sscan); |
||||
if (!HeapTupleIsValid(tuple)) |
||||
elog(ERROR, "catalog lookup failed for proc %u", functionId); |
||||
|
||||
namespaceId = ((Form_pg_proc) GETSTRUCT(tuple))->pronamespace; |
||||
|
||||
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); |
||||
ncontext = sepgsql_compute_create(scontext, tcontext, |
||||
SEPG_CLASS_DB_PROCEDURE); |
||||
|
||||
/*
|
||||
* Assign the default security label on a new procedure |
||||
*/ |
||||
object.classId = ProcedureRelationId; |
||||
object.objectId = functionId; |
||||
object.objectSubId = 0; |
||||
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); |
||||
|
||||
pfree(tcontext); |
||||
pfree(ncontext); |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_proc_relabel |
||||
* |
||||
* It checks privileges to relabel the supplied function |
||||
* by the `seclabel'. |
||||
*/ |
||||
void |
||||
sepgsql_proc_relabel(Oid functionId, const char *seclabel) |
||||
{ |
||||
char *scontext = sepgsql_get_client_label(); |
||||
char *tcontext; |
||||
char *audit_name; |
||||
|
||||
audit_name = get_func_name(functionId); |
||||
|
||||
/*
|
||||
* check db_procedure:{setattr relabelfrom} permission |
||||
*/ |
||||
tcontext = sepgsql_get_label(ProcedureRelationId, functionId, 0); |
||||
sepgsql_check_perms(scontext, |
||||
tcontext, |
||||
SEPG_CLASS_DB_PROCEDURE, |
||||
SEPG_DB_PROCEDURE__SETATTR | |
||||
SEPG_DB_PROCEDURE__RELABELFROM, |
||||
audit_name, |
||||
true); |
||||
pfree(tcontext); |
||||
|
||||
/*
|
||||
* check db_procedure:{relabelto} permission |
||||
*/ |
||||
sepgsql_check_perms(scontext, |
||||
seclabel, |
||||
SEPG_CLASS_DB_PROCEDURE, |
||||
SEPG_DB_PROCEDURE__RELABELTO, |
||||
audit_name, |
||||
true); |
||||
pfree(audit_name); |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_proc_get_domtrans |
||||
* |
||||
* It computes security label of the client that shall be applied when |
||||
* the current client invokes the supplied function. |
||||
* This computed label is either same or different from the current one. |
||||
* If security policy informed the function is a trusted-procedure, |
||||
* we need to switch security label of the client during execution of |
||||
* the function. |
||||
* |
||||
* Also note that the translated label shall be allocated using palloc(). |
||||
* So, need to switch memory context, if you want to hold the string in |
||||
* someone except for CurrentMemoryContext. |
||||
*/ |
||||
char * |
||||
sepgsql_proc_get_domtrans(Oid functionId) |
||||
{ |
||||
char *scontext = sepgsql_get_client_label(); |
||||
char *tcontext; |
||||
char *ncontext; |
||||
|
||||
tcontext = sepgsql_get_label(ProcedureRelationId, functionId, 0); |
||||
|
||||
ncontext = sepgsql_compute_create(scontext, |
||||
tcontext, |
||||
SEPG_CLASS_PROCESS); |
||||
pfree(tcontext); |
||||
|
||||
return ncontext; |
||||
} |
@ -0,0 +1,267 @@ |
||||
/* -------------------------------------------------------------------------
|
||||
* |
||||
* contrib/sepgsql/label.c |
||||
* |
||||
* Routines corresponding to relation/attribute objects |
||||
* |
||||
* Copyright (c) 2010-2011, PostgreSQL Global Development Group |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/genam.h" |
||||
#include "access/heapam.h" |
||||
#include "access/sysattr.h" |
||||
#include "catalog/indexing.h" |
||||
#include "catalog/pg_attribute.h" |
||||
#include "catalog/pg_class.h" |
||||
#include "catalog/pg_namespace.h" |
||||
#include "commands/seclabel.h" |
||||
#include "utils/fmgroids.h" |
||||
#include "utils/lsyscache.h" |
||||
#include "utils/tqual.h" |
||||
|
||||
#include "sepgsql.h" |
||||
|
||||
/*
|
||||
* sepgsql_attribute_post_create |
||||
* |
||||
* This routine assigns a default security label on a newly defined |
||||
* column, using ALTER TABLE ... ADD COLUMN. |
||||
* Note that this routine is not invoked in the case of CREATE TABLE, |
||||
* although it also defines columns in addition to table. |
||||
*/ |
||||
void |
||||
sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum) |
||||
{ |
||||
char *scontext = sepgsql_get_client_label(); |
||||
char *tcontext; |
||||
char *ncontext; |
||||
ObjectAddress object; |
||||
|
||||
/*
|
||||
* Only attributes within regular relation have individual |
||||
* security labels. |
||||
*/ |
||||
if (get_rel_relkind(relOid) != RELKIND_RELATION) |
||||
return; |
||||
|
||||
/*
|
||||
* 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(RelationRelationId, relOid, 0); |
||||
ncontext = sepgsql_compute_create(scontext, tcontext, |
||||
SEPG_CLASS_DB_COLUMN); |
||||
/*
|
||||
* Assign the default security label on a new procedure |
||||
*/ |
||||
object.classId = RelationRelationId; |
||||
object.objectId = relOid; |
||||
object.objectSubId = attnum; |
||||
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); |
||||
|
||||
pfree(tcontext); |
||||
pfree(ncontext); |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_attribute_relabel |
||||
* |
||||
* It checks privileges to relabel the supplied column |
||||
* by the `seclabel'. |
||||
*/ |
||||
void |
||||
sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum, |
||||
const char *seclabel) |
||||
{ |
||||
char *scontext = sepgsql_get_client_label(); |
||||
char *tcontext; |
||||
char audit_name[NAMEDATALEN * 2 + 10]; |
||||
|
||||
if (get_rel_relkind(relOid) != RELKIND_RELATION) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE), |
||||
errmsg("cannot set security label on non-regular columns"))); |
||||
|
||||
snprintf(audit_name, sizeof(audit_name), "%s.%s", |
||||
get_rel_name(relOid), get_attname(relOid, attnum)); |
||||
|
||||
/*
|
||||
* check db_column:{setattr relabelfrom} permission |
||||
*/ |
||||
tcontext = sepgsql_get_label(RelationRelationId, relOid, attnum); |
||||
sepgsql_check_perms(scontext, |
||||
tcontext, |
||||
SEPG_CLASS_DB_COLUMN, |
||||
SEPG_DB_COLUMN__SETATTR | |
||||
SEPG_DB_COLUMN__RELABELFROM, |
||||
audit_name, |
||||
true); |
||||
pfree(tcontext); |
||||
|
||||
/*
|
||||
* check db_column:{relabelto} permission |
||||
*/ |
||||
sepgsql_check_perms(scontext, |
||||
seclabel, |
||||
SEPG_CLASS_DB_COLUMN, |
||||
SEPG_DB_PROCEDURE__RELABELTO, |
||||
audit_name, |
||||
true); |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_relation_post_create |
||||
* |
||||
* The post creation hook of relation/attribute |
||||
*/ |
||||
void |
||||
sepgsql_relation_post_create(Oid relOid) |
||||
{ |
||||
Relation rel; |
||||
ScanKeyData skey; |
||||
SysScanDesc sscan; |
||||
HeapTuple tuple; |
||||
Form_pg_class classForm; |
||||
ObjectAddress object; |
||||
uint16 tclass; |
||||
char *scontext; /* subject */ |
||||
char *tcontext; /* schema */ |
||||
char *rcontext; /* relation */ |
||||
char *ccontext; /* column */ |
||||
|
||||
/*
|
||||
* Fetch catalog record of the new relation. Because pg_class entry is |
||||
* not visible right now, we need to scan the catalog using SnapshotSelf. |
||||
*/ |
||||
rel = heap_open(RelationRelationId, AccessShareLock); |
||||
|
||||
ScanKeyInit(&skey, |
||||
ObjectIdAttributeNumber, |
||||
BTEqualStrategyNumber, F_OIDEQ, |
||||
ObjectIdGetDatum(relOid)); |
||||
|
||||
sscan = systable_beginscan(rel, ClassOidIndexId, true, |
||||
SnapshotSelf, 1, &skey); |
||||
|
||||
tuple = systable_getnext(sscan); |
||||
if (!HeapTupleIsValid(tuple)) |
||||
elog(ERROR, "catalog lookup failed for relation %u", 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 */ |
||||
|
||||
/*
|
||||
* Compute a default security label when we create a new relation |
||||
* object under the specified namespace. |
||||
*/ |
||||
scontext = sepgsql_get_client_label(); |
||||
tcontext = sepgsql_get_label(NamespaceRelationId, |
||||
classForm->relnamespace, 0); |
||||
rcontext = sepgsql_compute_create(scontext, tcontext, tclass); |
||||
|
||||
/*
|
||||
* Assign the default security label on the new relation |
||||
*/ |
||||
object.classId = RelationRelationId; |
||||
object.objectId = relOid; |
||||
object.objectSubId = 0; |
||||
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext); |
||||
|
||||
/*
|
||||
* We also assigns a default security label on columns of the new |
||||
* regular tables. |
||||
*/ |
||||
if (classForm->relkind == RELKIND_RELATION) |
||||
{ |
||||
AttrNumber index; |
||||
|
||||
ccontext = sepgsql_compute_create(scontext, rcontext, |
||||
SEPG_CLASS_DB_COLUMN); |
||||
for (index = FirstLowInvalidHeapAttributeNumber + 1; |
||||
index <= classForm->relnatts; |
||||
index++) |
||||
{ |
||||
if (index == InvalidAttrNumber) |
||||
continue; |
||||
|
||||
if (index == ObjectIdAttributeNumber && !classForm->relhasoids) |
||||
continue; |
||||
|
||||
object.classId = RelationRelationId; |
||||
object.objectId = relOid; |
||||
object.objectSubId = index; |
||||
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext); |
||||
} |
||||
pfree(ccontext); |
||||
} |
||||
pfree(rcontext); |
||||
out: |
||||
systable_endscan(sscan); |
||||
heap_close(rel, AccessShareLock); |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_relation_relabel |
||||
* |
||||
* It checks privileges to relabel the supplied relation by the `seclabel'. |
||||
*/ |
||||
void |
||||
sepgsql_relation_relabel(Oid relOid, const char *seclabel) |
||||
{ |
||||
char *scontext = sepgsql_get_client_label(); |
||||
char *tcontext; |
||||
char *audit_name; |
||||
char relkind; |
||||
uint16_t tclass = 0; |
||||
|
||||
relkind = get_rel_relkind(relOid); |
||||
if (relkind == RELKIND_RELATION) |
||||
tclass = SEPG_CLASS_DB_TABLE; |
||||
else if (relkind == RELKIND_SEQUENCE) |
||||
tclass = SEPG_CLASS_DB_SEQUENCE; |
||||
else if (relkind == RELKIND_VIEW) |
||||
tclass = SEPG_CLASS_DB_VIEW; |
||||
else |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE), |
||||
errmsg("cannot set security labels on relations except " |
||||
"for tables, sequences or views"))); |
||||
|
||||
audit_name = get_rel_name(relOid); |
||||
|
||||
/*
|
||||
* check db_xxx:{setattr relabelfrom} permission |
||||
*/ |
||||
tcontext = sepgsql_get_label(RelationRelationId, relOid, 0); |
||||
|
||||
sepgsql_check_perms(scontext, |
||||
tcontext, |
||||
tclass, |
||||
SEPG_DB_TABLE__SETATTR | |
||||
SEPG_DB_TABLE__RELABELFROM, |
||||
audit_name, |
||||
true); |
||||
pfree(tcontext); |
||||
|
||||
/*
|
||||
* check db_xxx:{relabelto} permission |
||||
*/ |
||||
sepgsql_check_perms(scontext, |
||||
seclabel, |
||||
tclass, |
||||
SEPG_DB_TABLE__RELABELTO, |
||||
audit_name, |
||||
true); |
||||
} |
@ -0,0 +1,98 @@ |
||||
/* -------------------------------------------------------------------------
|
||||
* |
||||
* contrib/sepgsql/schema.c |
||||
* |
||||
* Routines corresponding to schema objects |
||||
* |
||||
* Copyright (c) 2010-2011, PostgreSQL Global Development Group |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "catalog/pg_namespace.h" |
||||
#include "commands/seclabel.h" |
||||
#include "utils/lsyscache.h" |
||||
|
||||
#include "sepgsql.h" |
||||
|
||||
/*
|
||||
* sepgsql_schema_post_create |
||||
* |
||||
* This routine assigns a default security label on a newly defined |
||||
* schema. |
||||
*/ |
||||
void |
||||
sepgsql_schema_post_create(Oid namespaceId) |
||||
{ |
||||
char *scontext = sepgsql_get_client_label(); |
||||
char *tcontext; |
||||
char *ncontext; |
||||
ObjectAddress object; |
||||
|
||||
/*
|
||||
* FIXME: Right now, we assume pg_database object has a fixed |
||||
* security label, because pg_seclabel does not support to store |
||||
* label of shared database objects. |
||||
*/ |
||||
tcontext = "system_u:object_r:sepgsql_db_t:s0"; |
||||
|
||||
/*
|
||||
* Compute a default security label when we create a new schema |
||||
* object under the working database. |
||||
*/ |
||||
ncontext = sepgsql_compute_create(scontext, tcontext, |
||||
SEPG_CLASS_DB_SCHEMA); |
||||
|
||||
/*
|
||||
* Assign the default security label on a new procedure |
||||
*/ |
||||
object.classId = NamespaceRelationId; |
||||
object.objectId = namespaceId; |
||||
object.objectSubId = 0; |
||||
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); |
||||
|
||||
pfree(ncontext); |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_schema_relabel |
||||
* |
||||
* It checks privileges to relabel the supplied schema |
||||
* by the `seclabel'. |
||||
*/ |
||||
void |
||||
sepgsql_schema_relabel(Oid namespaceId, const char *seclabel) |
||||
{ |
||||
char *scontext = sepgsql_get_client_label(); |
||||
char *tcontext; |
||||
char *audit_name; |
||||
|
||||
audit_name = get_namespace_name(namespaceId); |
||||
|
||||
/*
|
||||
* check db_schema:{setattr relabelfrom} permission |
||||
*/ |
||||
tcontext = sepgsql_get_label(NamespaceRelationId, namespaceId, 0); |
||||
|
||||
sepgsql_check_perms(scontext, |
||||
tcontext, |
||||
SEPG_CLASS_DB_SCHEMA, |
||||
SEPG_DB_SCHEMA__SETATTR | |
||||
SEPG_DB_SCHEMA__RELABELFROM, |
||||
audit_name, |
||||
true); |
||||
|
||||
/*
|
||||
* check db_schema:{relabelto} permission |
||||
*/ |
||||
sepgsql_check_perms(scontext, |
||||
seclabel, |
||||
SEPG_CLASS_DB_SCHEMA, |
||||
SEPG_DB_SCHEMA__RELABELTO, |
||||
audit_name, |
||||
true); |
||||
|
||||
pfree(tcontext); |
||||
pfree(audit_name); |
||||
} |
@ -0,0 +1,631 @@ |
||||
/* -------------------------------------------------------------------------
|
||||
* |
||||
* contrib/sepgsql/selinux.c |
||||
* |
||||
* Interactions between userspace and selinux in kernelspace, |
||||
* using libselinux api. |
||||
* |
||||
* Copyright (c) 2010-2011, PostgreSQL Global Development Group |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "lib/stringinfo.h" |
||||
|
||||
#include "sepgsql.h" |
||||
|
||||
/*
|
||||
* selinux_catalog |
||||
* |
||||
* This mapping table enables to translate the name of object classes and |
||||
* access vectors to/from their own codes. |
||||
* When we ask SELinux whether the required privileges are allowed or not, |
||||
* we use security_compute_av(3). It needs us to represent object classes |
||||
* and access vectors using 'external' codes defined in the security policy. |
||||
* It is determinded in the runtime, not build time. So, it needs an internal |
||||
* service to translate object class/access vectors which we want to check |
||||
* into the code which kernel want to be given. |
||||
*/ |
||||
static struct |
||||
{ |
||||
const char *class_name; |
||||
uint16 class_code; |
||||
struct |
||||
{ |
||||
const char *av_name; |
||||
uint32 av_code; |
||||
} av[32]; |
||||
} selinux_catalog[] = { |
||||
{ |
||||
"process", SEPG_CLASS_PROCESS, |
||||
{ |
||||
{ "transition", SEPG_PROCESS__TRANSITION }, |
||||
{ NULL, 0UL } |
||||
} |
||||
}, |
||||
{ |
||||
"file", SEPG_CLASS_FILE, |
||||
{ |
||||
{ "read", SEPG_FILE__READ }, |
||||
{ "write", SEPG_FILE__WRITE }, |
||||
{ "create", SEPG_FILE__CREATE }, |
||||
{ "getattr", SEPG_FILE__GETATTR }, |
||||
{ "unlink", SEPG_FILE__UNLINK }, |
||||
{ "rename", SEPG_FILE__RENAME }, |
||||
{ "append", SEPG_FILE__APPEND }, |
||||
{ NULL, 0UL } |
||||
} |
||||
}, |
||||
{ |
||||
"dir", SEPG_CLASS_DIR, |
||||
{ |
||||
{ "read", SEPG_DIR__READ }, |
||||
{ "write", SEPG_DIR__WRITE }, |
||||
{ "create", SEPG_DIR__CREATE }, |
||||
{ "getattr", SEPG_DIR__GETATTR }, |
||||
{ "unlink", SEPG_DIR__UNLINK }, |
||||
{ "rename", SEPG_DIR__RENAME }, |
||||
{ "search", SEPG_DIR__SEARCH }, |
||||
{ "add_name", SEPG_DIR__ADD_NAME }, |
||||
{ "remove_name", SEPG_DIR__REMOVE_NAME }, |
||||
{ "rmdir", SEPG_DIR__RMDIR }, |
||||
{ "reparent", SEPG_DIR__REPARENT }, |
||||
{ NULL, 0UL } |
||||
} |
||||
}, |
||||
{ |
||||
"lnk_file", SEPG_CLASS_LNK_FILE, |
||||
{ |
||||
{ "read", SEPG_LNK_FILE__READ }, |
||||
{ "write", SEPG_LNK_FILE__WRITE }, |
||||
{ "create", SEPG_LNK_FILE__CREATE }, |
||||
{ "getattr", SEPG_LNK_FILE__GETATTR }, |
||||
{ "unlink", SEPG_LNK_FILE__UNLINK }, |
||||
{ "rename", SEPG_LNK_FILE__RENAME }, |
||||
{ NULL, 0UL } |
||||
} |
||||
}, |
||||
{ |
||||
"chr_file", SEPG_CLASS_CHR_FILE, |
||||
{ |
||||
{ "read", SEPG_CHR_FILE__READ }, |
||||
{ "write", SEPG_CHR_FILE__WRITE }, |
||||
{ "create", SEPG_CHR_FILE__CREATE }, |
||||
{ "getattr", SEPG_CHR_FILE__GETATTR }, |
||||
{ "unlink", SEPG_CHR_FILE__UNLINK }, |
||||
{ "rename", SEPG_CHR_FILE__RENAME }, |
||||
{ NULL, 0UL } |
||||
} |
||||
}, |
||||
{ |
||||
"blk_file", SEPG_CLASS_BLK_FILE, |
||||
{ |
||||
{ "read", SEPG_BLK_FILE__READ }, |
||||
{ "write", SEPG_BLK_FILE__WRITE }, |
||||
{ "create", SEPG_BLK_FILE__CREATE }, |
||||
{ "getattr", SEPG_BLK_FILE__GETATTR }, |
||||
{ "unlink", SEPG_BLK_FILE__UNLINK }, |
||||
{ "rename", SEPG_BLK_FILE__RENAME }, |
||||
{ NULL, 0UL } |
||||
} |
||||
}, |
||||
{ |
||||
"sock_file", SEPG_CLASS_SOCK_FILE, |
||||
{ |
||||
{ "read", SEPG_SOCK_FILE__READ }, |
||||
{ "write", SEPG_SOCK_FILE__WRITE }, |
||||
{ "create", SEPG_SOCK_FILE__CREATE }, |
||||
{ "getattr", SEPG_SOCK_FILE__GETATTR }, |
||||
{ "unlink", SEPG_SOCK_FILE__UNLINK }, |
||||
{ "rename", SEPG_SOCK_FILE__RENAME }, |
||||
{ NULL, 0UL } |
||||
} |
||||
}, |
||||
{ |
||||
"fifo_file", SEPG_CLASS_FIFO_FILE, |
||||
{ |
||||
{ "read", SEPG_FIFO_FILE__READ }, |
||||
{ "write", SEPG_FIFO_FILE__WRITE }, |
||||
{ "create", SEPG_FIFO_FILE__CREATE }, |
||||
{ "getattr", SEPG_FIFO_FILE__GETATTR }, |
||||
{ "unlink", SEPG_FIFO_FILE__UNLINK }, |
||||
{ "rename", SEPG_FIFO_FILE__RENAME }, |
||||
{ NULL, 0UL } |
||||
} |
||||
}, |
||||
{ |
||||
"db_database", SEPG_CLASS_DB_DATABASE, |
||||
{ |
||||
{ "create", SEPG_DB_DATABASE__CREATE }, |
||||
{ "drop", SEPG_DB_DATABASE__DROP }, |
||||
{ "getattr", SEPG_DB_DATABASE__GETATTR }, |
||||
{ "setattr", SEPG_DB_DATABASE__SETATTR }, |
||||
{ "relabelfrom", SEPG_DB_DATABASE__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_DATABASE__RELABELTO }, |
||||
{ "access", SEPG_DB_DATABASE__ACCESS }, |
||||
{ "load_module", SEPG_DB_DATABASE__LOAD_MODULE }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
{ |
||||
"db_schema", SEPG_CLASS_DB_SCHEMA, |
||||
{ |
||||
{ "create", SEPG_DB_SCHEMA__CREATE }, |
||||
{ "drop", SEPG_DB_SCHEMA__DROP }, |
||||
{ "getattr", SEPG_DB_SCHEMA__GETATTR }, |
||||
{ "setattr", SEPG_DB_SCHEMA__SETATTR }, |
||||
{ "relabelfrom", SEPG_DB_SCHEMA__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_SCHEMA__RELABELTO }, |
||||
{ "search", SEPG_DB_SCHEMA__SEARCH }, |
||||
{ "add_name", SEPG_DB_SCHEMA__ADD_NAME }, |
||||
{ "remove_name", SEPG_DB_SCHEMA__REMOVE_NAME }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
{ |
||||
"db_table", SEPG_CLASS_DB_TABLE, |
||||
{ |
||||
{ "create", SEPG_DB_TABLE__CREATE }, |
||||
{ "drop", SEPG_DB_TABLE__DROP }, |
||||
{ "getattr", SEPG_DB_TABLE__GETATTR }, |
||||
{ "setattr", SEPG_DB_TABLE__SETATTR }, |
||||
{ "relabelfrom", SEPG_DB_TABLE__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_TABLE__RELABELTO }, |
||||
{ "select", SEPG_DB_TABLE__SELECT }, |
||||
{ "update", SEPG_DB_TABLE__UPDATE }, |
||||
{ "insert", SEPG_DB_TABLE__INSERT }, |
||||
{ "delete", SEPG_DB_TABLE__DELETE }, |
||||
{ "lock", SEPG_DB_TABLE__LOCK }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
{ |
||||
"db_sequence", SEPG_CLASS_DB_SEQUENCE, |
||||
{ |
||||
{ "create", SEPG_DB_SEQUENCE__CREATE }, |
||||
{ "drop", SEPG_DB_SEQUENCE__DROP }, |
||||
{ "getattr", SEPG_DB_SEQUENCE__GETATTR }, |
||||
{ "setattr", SEPG_DB_SEQUENCE__SETATTR }, |
||||
{ "relabelfrom", SEPG_DB_SEQUENCE__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_SEQUENCE__RELABELTO }, |
||||
{ "get_value", SEPG_DB_SEQUENCE__GET_VALUE }, |
||||
{ "next_value", SEPG_DB_SEQUENCE__NEXT_VALUE }, |
||||
{ "set_value", SEPG_DB_SEQUENCE__SET_VALUE }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
{ |
||||
"db_procedure", SEPG_CLASS_DB_PROCEDURE, |
||||
{ |
||||
{ "create", SEPG_DB_PROCEDURE__CREATE }, |
||||
{ "drop", SEPG_DB_PROCEDURE__DROP }, |
||||
{ "getattr", SEPG_DB_PROCEDURE__GETATTR }, |
||||
{ "setattr", SEPG_DB_PROCEDURE__SETATTR }, |
||||
{ "relabelfrom", SEPG_DB_PROCEDURE__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_PROCEDURE__RELABELTO }, |
||||
{ "execute", SEPG_DB_PROCEDURE__EXECUTE }, |
||||
{ "entrypoint", SEPG_DB_PROCEDURE__ENTRYPOINT }, |
||||
{ "install", SEPG_DB_PROCEDURE__INSTALL }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
{ |
||||
"db_column", SEPG_CLASS_DB_COLUMN, |
||||
{ |
||||
{ "create", SEPG_DB_COLUMN__CREATE }, |
||||
{ "drop", SEPG_DB_COLUMN__DROP }, |
||||
{ "getattr", SEPG_DB_COLUMN__GETATTR }, |
||||
{ "setattr", SEPG_DB_COLUMN__SETATTR }, |
||||
{ "relabelfrom", SEPG_DB_COLUMN__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_COLUMN__RELABELTO }, |
||||
{ "select", SEPG_DB_COLUMN__SELECT }, |
||||
{ "update", SEPG_DB_COLUMN__UPDATE }, |
||||
{ "insert", SEPG_DB_COLUMN__INSERT }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
{ |
||||
"db_tuple", SEPG_CLASS_DB_TUPLE, |
||||
{ |
||||
{ "relabelfrom", SEPG_DB_TUPLE__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_TUPLE__RELABELTO }, |
||||
{ "select", SEPG_DB_TUPLE__SELECT }, |
||||
{ "update", SEPG_DB_TUPLE__UPDATE }, |
||||
{ "insert", SEPG_DB_TUPLE__INSERT }, |
||||
{ "delete", SEPG_DB_TUPLE__DELETE }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
{ |
||||
"db_blob", SEPG_CLASS_DB_BLOB, |
||||
{ |
||||
{ "create", SEPG_DB_BLOB__CREATE }, |
||||
{ "drop", SEPG_DB_BLOB__DROP }, |
||||
{ "getattr", SEPG_DB_BLOB__GETATTR }, |
||||
{ "setattr", SEPG_DB_BLOB__SETATTR }, |
||||
{ "relabelfrom", SEPG_DB_BLOB__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_BLOB__RELABELTO }, |
||||
{ "read", SEPG_DB_BLOB__READ }, |
||||
{ "write", SEPG_DB_BLOB__WRITE }, |
||||
{ "import", SEPG_DB_BLOB__IMPORT }, |
||||
{ "export", SEPG_DB_BLOB__EXPORT }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
{ |
||||
"db_language", SEPG_CLASS_DB_LANGUAGE, |
||||
{ |
||||
{ "create", SEPG_DB_LANGUAGE__CREATE }, |
||||
{ "drop", SEPG_DB_LANGUAGE__DROP }, |
||||
{ "getattr", SEPG_DB_LANGUAGE__GETATTR }, |
||||
{ "setattr", SEPG_DB_LANGUAGE__SETATTR }, |
||||
{ "relabelfrom", SEPG_DB_LANGUAGE__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_LANGUAGE__RELABELTO }, |
||||
{ "implement", SEPG_DB_LANGUAGE__IMPLEMENT }, |
||||
{ "execute", SEPG_DB_LANGUAGE__EXECUTE }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
{ |
||||
"db_view", SEPG_CLASS_DB_VIEW, |
||||
{ |
||||
{ "create", SEPG_DB_VIEW__CREATE }, |
||||
{ "drop", SEPG_DB_VIEW__DROP }, |
||||
{ "getattr", SEPG_DB_VIEW__GETATTR }, |
||||
{ "setattr", SEPG_DB_VIEW__SETATTR }, |
||||
{ "relabelfrom", SEPG_DB_VIEW__RELABELFROM }, |
||||
{ "relabelto", SEPG_DB_VIEW__RELABELTO }, |
||||
{ "expand", SEPG_DB_VIEW__EXPAND }, |
||||
{ NULL, 0UL }, |
||||
} |
||||
}, |
||||
}; |
||||
|
||||
/*
|
||||
* sepgsql_mode |
||||
* |
||||
* SEPGSQL_MODE_DISABLED: Disabled on runtime |
||||
* SEPGSQL_MODE_DEFAULT: Same as system settings |
||||
* SEPGSQL_MODE_PERMISSIVE: Always permissive mode |
||||
* SEPGSQL_MODE_INTERNAL: Same as permissive, except for no audit logs |
||||
*/ |
||||
static int sepgsql_mode = SEPGSQL_MODE_INTERNAL; |
||||
|
||||
/*
|
||||
* sepgsql_is_enabled |
||||
*/ |
||||
bool |
||||
sepgsql_is_enabled(void) |
||||
{ |
||||
return (sepgsql_mode != SEPGSQL_MODE_DISABLED ? true : false); |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_get_mode |
||||
*/ |
||||
int |
||||
sepgsql_get_mode(void) |
||||
{ |
||||
return sepgsql_mode; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_set_mode |
||||
*/ |
||||
int |
||||
sepgsql_set_mode(int new_mode) |
||||
{ |
||||
int old_mode = sepgsql_mode; |
||||
|
||||
sepgsql_mode = new_mode; |
||||
|
||||
return old_mode; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_getenforce |
||||
* |
||||
* It returns whether the current working mode tries to enforce access |
||||
* control decision, or not. It shall be enforced when sepgsql_mode is |
||||
* SEPGSQL_MODE_DEFAULT and system is running in enforcing mode. |
||||
*/ |
||||
bool |
||||
sepgsql_getenforce(void) |
||||
{ |
||||
if (sepgsql_mode == SEPGSQL_MODE_DEFAULT && |
||||
security_getenforce() > 0) |
||||
return true; |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_audit_log |
||||
* |
||||
* It generates a security audit record. In the default, it writes out |
||||
* audit records into standard PG's logfile. It also allows to set up |
||||
* external audit log receiver, such as auditd in Linux, using the |
||||
* sepgsql_audit_hook. |
||||
* |
||||
* SELinux can control what should be audited and should not using |
||||
* "auditdeny" and "auditallow" rules in the security policy. In the |
||||
* default, all the access violations are audited, and all the access |
||||
* allowed are not audited. But we can set up the security policy, so |
||||
* we can have exceptions. So, it is necessary to follow the suggestion |
||||
* come from the security policy. (av_decision.auditallow and auditdeny) |
||||
* |
||||
* Security audit is an important feature, because it enables us to check |
||||
* what was happen if we have a security incident. In fact, ISO/IEC15408 |
||||
* defines several security functionalities for audit features. |
||||
*/ |
||||
void |
||||
sepgsql_audit_log(bool denied, |
||||
const char *scontext, |
||||
const char *tcontext, |
||||
uint16 tclass, |
||||
uint32 audited, |
||||
const char *audit_name) |
||||
{ |
||||
StringInfoData buf; |
||||
const char *class_name; |
||||
const char *av_name; |
||||
int i; |
||||
|
||||
/* lookup name of the object class */ |
||||
Assert(tclass < SEPG_CLASS_MAX); |
||||
class_name = selinux_catalog[tclass].class_name; |
||||
|
||||
/* lookup name of the permissions */ |
||||
initStringInfo(&buf); |
||||
appendStringInfo(&buf, "%s {", |
||||
(denied ? "denied" : "allowed")); |
||||
for (i=0; selinux_catalog[tclass].av[i].av_name; i++) |
||||
{ |
||||
if (audited & (1UL << i)) |
||||
{ |
||||
av_name = selinux_catalog[tclass].av[i].av_name; |
||||
appendStringInfo(&buf, " %s", av_name); |
||||
} |
||||
} |
||||
appendStringInfo(&buf, " }"); |
||||
|
||||
/*
|
||||
* Call external audit module, if loaded |
||||
*/ |
||||
appendStringInfo(&buf, " scontext=%s tcontext=%s tclass=%s", |
||||
scontext, tcontext, class_name); |
||||
if (audit_name) |
||||
appendStringInfo(&buf, " name=%s", audit_name); |
||||
|
||||
ereport(LOG, (errmsg("SELinux: %s", buf.data))); |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_compute_avd |
||||
* |
||||
* It actually asks SELinux what permissions are allowed on a pair of |
||||
* the security contexts and object class. It also returns what permissions |
||||
* should be audited on access violation or allowed. |
||||
* In most cases, subject's security context (scontext) is a client, and |
||||
* target security context (tcontext) is a database object. |
||||
* |
||||
* The access control decision shall be set on the given av_decision. |
||||
* The av_decision.allowed has a bitmask of SEPG_<class>__<perms> |
||||
* to suggest a set of allowed actions in this object class. |
||||
*/ |
||||
void |
||||
sepgsql_compute_avd(const char *scontext, |
||||
const char *tcontext, |
||||
uint16 tclass, |
||||
struct av_decision *avd) |
||||
{ |
||||
const char *tclass_name; |
||||
security_class_t tclass_ex; |
||||
struct av_decision avd_ex; |
||||
int i, deny_unknown = security_deny_unknown(); |
||||
|
||||
/* Get external code of the object class*/ |
||||
Assert(tclass < SEPG_CLASS_MAX); |
||||
Assert(tclass == selinux_catalog[tclass].class_code); |
||||
|
||||
tclass_name = selinux_catalog[tclass].class_name; |
||||
tclass_ex = string_to_security_class(tclass_name); |
||||
|
||||
if (tclass_ex == 0) |
||||
{ |
||||
/*
|
||||
* If the current security policy does not support permissions |
||||
* corresponding to database objects, we fill up them with dummy |
||||
* data. |
||||
* If security_deny_unknown() returns positive value, undefined |
||||
* permissions should be denied. Otherwise, allowed |
||||
*/ |
||||
avd->allowed = (security_deny_unknown() > 0 ? 0 : ~0); |
||||
avd->auditallow = 0U; |
||||
avd->auditdeny = ~0U; |
||||
avd->flags = 0; |
||||
|
||||
return; |
||||
} |
||||
|
||||
/*
|
||||
* Ask SELinux what is allowed set of permissions on a pair of the |
||||
* security contexts and the given object class. |
||||
*/ |
||||
if (security_compute_av_flags_raw((security_context_t)scontext, |
||||
(security_context_t)tcontext, |
||||
tclass_ex, 0, &avd_ex) < 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INTERNAL_ERROR), |
||||
errmsg("SELinux could not compute av_decision: " |
||||
"scontext=%s tcontext=%s tclass=%s", |
||||
scontext, tcontext, tclass_name))); |
||||
|
||||
/*
|
||||
* SELinux returns its access control decision as a set of permissions |
||||
* represented in external code which depends on run-time environment. |
||||
* So, we need to translate it to the internal representation before |
||||
* returning results for the caller. |
||||
*/ |
||||
memset(avd, 0, sizeof(struct av_decision)); |
||||
|
||||
for (i=0; selinux_catalog[tclass].av[i].av_name; i++) |
||||
{ |
||||
access_vector_t av_code_ex; |
||||
const char *av_name = selinux_catalog[tclass].av[i].av_name; |
||||
uint32 av_code = selinux_catalog[tclass].av[i].av_code; |
||||
|
||||
av_code_ex = string_to_av_perm(tclass_ex, av_name); |
||||
if (av_code_ex == 0) |
||||
{ |
||||
/* fill up undefined permissions */ |
||||
if (!deny_unknown) |
||||
avd->allowed |= av_code; |
||||
avd->auditdeny |= av_code; |
||||
|
||||
continue; |
||||
} |
||||
|
||||
if (avd_ex.allowed & av_code_ex) |
||||
avd->allowed |= av_code; |
||||
if (avd_ex.auditallow & av_code_ex) |
||||
avd->auditallow |= av_code; |
||||
if (avd_ex.auditdeny & av_code_ex) |
||||
avd->auditdeny |= av_code; |
||||
} |
||||
|
||||
return; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_compute_create |
||||
* |
||||
* It returns a default security context to be assigned on a new database |
||||
* object. SELinux compute it based on a combination of client, upper object |
||||
* which owns the new object and object class. |
||||
* |
||||
* For example, when a client (staff_u:staff_r:staff_t:s0) tries to create |
||||
* a new table within a schema (system_u:object_r:sepgsql_schema_t:s0), |
||||
* SELinux looks-up its security policy. If it has a special rule on the |
||||
* combination of these security contexts and object class (db_table), |
||||
* it returns the security context suggested by the special rule. |
||||
* Otherwise, it returns the security context of schema, as is. |
||||
* |
||||
* We expect the caller already applies sanity/validation checks on the |
||||
* given security context. |
||||
* |
||||
* scontext: security context of the subject (mostly, peer process). |
||||
* tcontext: security context of the the upper database object. |
||||
* tclass: class code (SEPG_CLASS_*) of the new object in creation |
||||
*/ |
||||
char * |
||||
sepgsql_compute_create(const char *scontext, |
||||
const char *tcontext, |
||||
uint16 tclass) |
||||
{ |
||||
security_context_t ncontext; |
||||
security_class_t tclass_ex; |
||||
const char *tclass_name; |
||||
char *result; |
||||
|
||||
/* Get external code of the object class*/ |
||||
Assert(tclass < SEPG_CLASS_MAX); |
||||
|
||||
tclass_name = selinux_catalog[tclass].class_name; |
||||
tclass_ex = string_to_security_class(tclass_name); |
||||
|
||||
/*
|
||||
* Ask SELinux what is the default context for the given object class |
||||
* on a pair of security contexts |
||||
*/ |
||||
if (security_compute_create_raw((security_context_t)scontext, |
||||
(security_context_t)tcontext, |
||||
tclass_ex, &ncontext) < 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INTERNAL_ERROR), |
||||
errmsg("SELinux could not compute a new context: " |
||||
"scontext=%s tcontext=%s tclass=%s", |
||||
scontext, tcontext, tclass_name))); |
||||
|
||||
/*
|
||||
* libselinux returns malloc()'ed string, so we need to copy it |
||||
* on the palloc()'ed region. |
||||
*/ |
||||
PG_TRY(); |
||||
{ |
||||
result = pstrdup(ncontext); |
||||
} |
||||
PG_CATCH(); |
||||
{ |
||||
freecon(ncontext); |
||||
PG_RE_THROW(); |
||||
} |
||||
PG_END_TRY(); |
||||
freecon(ncontext); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/*
|
||||
* sepgsql_check_perms |
||||
* |
||||
* It makes access control decision without userspace caching mechanism. |
||||
* If SELinux denied the required accesses on the pair of security labels, |
||||
* it raises an error or returns false. |
||||
* |
||||
* scontext: security label of the subject (mostly, peer process) |
||||
* tcontext: security label of the object being referenced |
||||
* tclass: class code (SEPG_CLASS_*) of the object being referenced |
||||
* required: a mask of required permissions (SEPG_<class>__<perm>) |
||||
* audit_name: a human readable object name for audit logs, or NULL. |
||||
* abort: true, if caller wants to raise an error on access violation |
||||
*/ |
||||
bool |
||||
sepgsql_check_perms(const char *scontext, |
||||
const char *tcontext, |
||||
uint16 tclass, |
||||
uint32 required, |
||||
const char *audit_name, |
||||
bool abort) |
||||
{ |
||||
struct av_decision avd; |
||||
uint32 denied; |
||||
uint32 audited; |
||||
bool result = true; |
||||
|
||||
sepgsql_compute_avd(scontext, tcontext, tclass, &avd); |
||||
|
||||
denied = required & ~avd.allowed; |
||||
|
||||
if (sepgsql_get_debug_audit()) |
||||
audited = (denied ? denied : required); |
||||
else |
||||
audited = (denied ? (denied & avd.auditdeny) |
||||
: (required & avd.auditallow)); |
||||
|
||||
if (denied && |
||||
sepgsql_getenforce() > 0 && |
||||
(avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE) == 0) |
||||
result = false; |
||||
|
||||
/*
|
||||
* It records a security audit for the request, if needed. |
||||
* But, when SE-PgSQL performs 'internal' mode, it needs to keep silent. |
||||
*/ |
||||
if (audited && sepgsql_mode != SEPGSQL_MODE_INTERNAL) |
||||
{ |
||||
sepgsql_audit_log(denied, |
||||
scontext, |
||||
tcontext, |
||||
tclass, |
||||
audited, |
||||
audit_name); |
||||
} |
||||
|
||||
if (!result && abort) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
||||
errmsg("SELinux: security policy violation"))); |
||||
return result; |
||||
} |
@ -0,0 +1,59 @@ |
||||
policy_module(sepgsql-regtest, 1.01) |
||||
|
||||
## <desc> |
||||
## <p> |
||||
## Allow to launch regression test of SE-PostgreSQL |
||||
## Don't switch to TRUE in normal cases |
||||
## </p> |
||||
## </desc> |
||||
gen_tunable(sepgsql_regression_test_mode, false) |
||||
|
||||
# |
||||
# Test domains for database administrators |
||||
# |
||||
role sepgsql_regtest_dba_r; |
||||
userdom_base_user_template(sepgsql_regtest_dba) |
||||
userdom_manage_home_role(sepgsql_regtest_dba_r, sepgsql_regtest_dba_t) |
||||
userdom_write_user_tmp_sockets(sepgsql_regtest_user_t) |
||||
optional_policy(` |
||||
postgresql_admin(sepgsql_regtest_dba_t, sepgsql_regtest_dba_r) |
||||
postgresql_stream_connect(sepgsql_regtest_dba_t) |
||||
') |
||||
optional_policy(` |
||||
unconfined_stream_connect(sepgsql_regtest_dba_t) |
||||
unconfined_rw_pipes(sepgsql_regtest_dba_t) |
||||
') |
||||
|
||||
# |
||||
# Dummy domain for unpriv users |
||||
# |
||||
role sepgsql_regtest_user_r; |
||||
userdom_base_user_template(sepgsql_regtest_user) |
||||
userdom_manage_home_role(sepgsql_regtest_user_r, sepgsql_regtest_user_t) |
||||
userdom_write_user_tmp_sockets(sepgsql_regtest_user_t) |
||||
optional_policy(` |
||||
postgresql_role(sepgsql_regtest_user_r, sepgsql_regtest_user_t) |
||||
postgresql_stream_connect(sepgsql_regtest_user_t) |
||||
') |
||||
optional_policy(` |
||||
unconfined_stream_connect(sepgsql_regtest_user_t) |
||||
unconfined_rw_pipes(sepgsql_regtest_user_t) |
||||
') |
||||
|
||||
# |
||||
# Rules to launch psql in the dummy domains |
||||
# |
||||
optional_policy(` |
||||
gen_require(` |
||||
role unconfined_r; |
||||
type unconfined_t; |
||||
type sepgsql_trusted_proc_t; |
||||
') |
||||
tunable_policy(`sepgsql_regression_test_mode',` |
||||
allow unconfined_t sepgsql_regtest_dba_t : process { transition }; |
||||
allow unconfined_t sepgsql_regtest_user_t : process { transition }; |
||||
') |
||||
role unconfined_r types sepgsql_regtest_dba_t; |
||||
role unconfined_r types sepgsql_regtest_user_t; |
||||
role unconfined_r types sepgsql_trusted_proc_t; |
||||
') |
@ -0,0 +1,288 @@ |
||||
/* -------------------------------------------------------------------------
|
||||
* |
||||
* contrib/sepgsql/sepgsql.h |
||||
* |
||||
* Definitions corresponding to SE-PostgreSQL |
||||
* |
||||
* Copyright (c) 2010-2011, PostgreSQL Global Development Group |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef SEPGSQL_H |
||||
#define SEPGSQL_H |
||||
|
||||
#include "catalog/objectaddress.h" |
||||
#include <selinux/selinux.h> |
||||
|
||||
/*
|
||||
* SE-PostgreSQL Label Tag |
||||
*/ |
||||
#define SEPGSQL_LABEL_TAG "selinux" |
||||
|
||||
/*
|
||||
* SE-PostgreSQL performing mode |
||||
*/ |
||||
#define SEPGSQL_MODE_DEFAULT 1 |
||||
#define SEPGSQL_MODE_PERMISSIVE 2 |
||||
#define SEPGSQL_MODE_INTERNAL 3 |
||||
#define SEPGSQL_MODE_DISABLED 4 |
||||
|
||||
/*
|
||||
* Internally used code of object classes |
||||
*/ |
||||
#define SEPG_CLASS_PROCESS 0 |
||||
#define SEPG_CLASS_FILE 1 |
||||
#define SEPG_CLASS_DIR 2 |
||||
#define SEPG_CLASS_LNK_FILE 3 |
||||
#define SEPG_CLASS_CHR_FILE 4 |
||||
#define SEPG_CLASS_BLK_FILE 5 |
||||
#define SEPG_CLASS_SOCK_FILE 6 |
||||
#define SEPG_CLASS_FIFO_FILE 7 |
||||
#define SEPG_CLASS_DB_DATABASE 8 |
||||
#define SEPG_CLASS_DB_SCHEMA 9 |
||||
#define SEPG_CLASS_DB_TABLE 10 |
||||
#define SEPG_CLASS_DB_SEQUENCE 11 |
||||
#define SEPG_CLASS_DB_PROCEDURE 12 |
||||
#define SEPG_CLASS_DB_COLUMN 13 |
||||
#define SEPG_CLASS_DB_TUPLE 14 |
||||
#define SEPG_CLASS_DB_BLOB 15 |
||||
#define SEPG_CLASS_DB_LANGUAGE 16 |
||||
#define SEPG_CLASS_DB_VIEW 17 |
||||
#define SEPG_CLASS_MAX 18 |
||||
|
||||
/*
|
||||
* Internally used code of access vectors |
||||
*/ |
||||
#define SEPG_PROCESS__TRANSITION (1<<0) |
||||
|
||||
#define SEPG_FILE__READ (1<<0) |
||||
#define SEPG_FILE__WRITE (1<<1) |
||||
#define SEPG_FILE__CREATE (1<<2) |
||||
#define SEPG_FILE__GETATTR (1<<3) |
||||
#define SEPG_FILE__UNLINK (1<<4) |
||||
#define SEPG_FILE__RENAME (1<<5) |
||||
#define SEPG_FILE__APPEND (1<<6) |
||||
|
||||
#define SEPG_DIR__READ (SEPG_FILE__READ) |
||||
#define SEPG_DIR__WRITE (SEPG_FILE__WRITE) |
||||
#define SEPG_DIR__CREATE (SEPG_FILE__CREATE) |
||||
#define SEPG_DIR__GETATTR (SEPG_FILE__GETATTR) |
||||
#define SEPG_DIR__UNLINK (SEPG_FILE__UNLINK) |
||||
#define SEPG_DIR__RENAME (SEPG_FILE__RENAME) |
||||
#define SEPG_DIR__SEARCH (1<<6) |
||||
#define SEPG_DIR__ADD_NAME (1<<7) |
||||
#define SEPG_DIR__REMOVE_NAME (1<<8) |
||||
#define SEPG_DIR__RMDIR (1<<9) |
||||
#define SEPG_DIR__REPARENT (1<<10) |
||||
|
||||
#define SEPG_LNK_FILE__READ (SEPG_FILE__READ) |
||||
#define SEPG_LNK_FILE__WRITE (SEPG_FILE__WRITE) |
||||
#define SEPG_LNK_FILE__CREATE (SEPG_FILE__CREATE) |
||||
#define SEPG_LNK_FILE__GETATTR (SEPG_FILE__GETATTR) |
||||
#define SEPG_LNK_FILE__UNLINK (SEPG_FILE__UNLINK) |
||||
#define SEPG_LNK_FILE__RENAME (SEPG_FILE__RENAME) |
||||
|
||||
#define SEPG_CHR_FILE__READ (SEPG_FILE__READ) |
||||
#define SEPG_CHR_FILE__WRITE (SEPG_FILE__WRITE) |
||||
#define SEPG_CHR_FILE__CREATE (SEPG_FILE__CREATE) |
||||
#define SEPG_CHR_FILE__GETATTR (SEPG_FILE__GETATTR) |
||||
#define SEPG_CHR_FILE__UNLINK (SEPG_FILE__UNLINK) |
||||
#define SEPG_CHR_FILE__RENAME (SEPG_FILE__RENAME) |
||||
|
||||
#define SEPG_BLK_FILE__READ (SEPG_FILE__READ) |
||||
#define SEPG_BLK_FILE__WRITE (SEPG_FILE__WRITE) |
||||
#define SEPG_BLK_FILE__CREATE (SEPG_FILE__CREATE) |
||||
#define SEPG_BLK_FILE__GETATTR (SEPG_FILE__GETATTR) |
||||
#define SEPG_BLK_FILE__UNLINK (SEPG_FILE__UNLINK) |
||||
#define SEPG_BLK_FILE__RENAME (SEPG_FILE__RENAME) |
||||
|
||||
#define SEPG_SOCK_FILE__READ (SEPG_FILE__READ) |
||||
#define SEPG_SOCK_FILE__WRITE (SEPG_FILE__WRITE) |
||||
#define SEPG_SOCK_FILE__CREATE (SEPG_FILE__CREATE) |
||||
#define SEPG_SOCK_FILE__GETATTR (SEPG_FILE__GETATTR) |
||||
#define SEPG_SOCK_FILE__UNLINK (SEPG_FILE__UNLINK) |
||||
#define SEPG_SOCK_FILE__RENAME (SEPG_FILE__RENAME) |
||||
|
||||
#define SEPG_FIFO_FILE__READ (SEPG_FILE__READ) |
||||
#define SEPG_FIFO_FILE__WRITE (SEPG_FILE__WRITE) |
||||
#define SEPG_FIFO_FILE__CREATE (SEPG_FILE__CREATE) |
||||
#define SEPG_FIFO_FILE__GETATTR (SEPG_FILE__GETATTR) |
||||
#define SEPG_FIFO_FILE__UNLINK (SEPG_FILE__UNLINK) |
||||
#define SEPG_FIFO_FILE__RENAME (SEPG_FILE__RENAME) |
||||
|
||||
#define SEPG_DB_DATABASE__CREATE (1<<0) |
||||
#define SEPG_DB_DATABASE__DROP (1<<1) |
||||
#define SEPG_DB_DATABASE__GETATTR (1<<2) |
||||
#define SEPG_DB_DATABASE__SETATTR (1<<3) |
||||
#define SEPG_DB_DATABASE__RELABELFROM (1<<4) |
||||
#define SEPG_DB_DATABASE__RELABELTO (1<<5) |
||||
#define SEPG_DB_DATABASE__ACCESS (1<<6) |
||||
#define SEPG_DB_DATABASE__LOAD_MODULE (1<<7) |
||||
|
||||
#define SEPG_DB_SCHEMA__CREATE (SEPG_DB_DATABASE__CREATE) |
||||
#define SEPG_DB_SCHEMA__DROP (SEPG_DB_DATABASE__DROP) |
||||
#define SEPG_DB_SCHEMA__GETATTR (SEPG_DB_DATABASE__GETATTR) |
||||
#define SEPG_DB_SCHEMA__SETATTR (SEPG_DB_DATABASE__SETATTR) |
||||
#define SEPG_DB_SCHEMA__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) |
||||
#define SEPG_DB_SCHEMA__RELABELTO (SEPG_DB_DATABASE__RELABELTO) |
||||
#define SEPG_DB_SCHEMA__SEARCH (1<<6) |
||||
#define SEPG_DB_SCHEMA__ADD_NAME (1<<7) |
||||
#define SEPG_DB_SCHEMA__REMOVE_NAME (1<<8) |
||||
|
||||
#define SEPG_DB_TABLE__CREATE (SEPG_DB_DATABASE__CREATE) |
||||
#define SEPG_DB_TABLE__DROP (SEPG_DB_DATABASE__DROP) |
||||
#define SEPG_DB_TABLE__GETATTR (SEPG_DB_DATABASE__GETATTR) |
||||
#define SEPG_DB_TABLE__SETATTR (SEPG_DB_DATABASE__SETATTR) |
||||
#define SEPG_DB_TABLE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) |
||||
#define SEPG_DB_TABLE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) |
||||
#define SEPG_DB_TABLE__SELECT (1<<6) |
||||
#define SEPG_DB_TABLE__UPDATE (1<<7) |
||||
#define SEPG_DB_TABLE__INSERT (1<<8) |
||||
#define SEPG_DB_TABLE__DELETE (1<<9) |
||||
#define SEPG_DB_TABLE__LOCK (1<<10) |
||||
#define SEPG_DB_TABLE__INDEXON (1<<11) |
||||
|
||||
#define SEPG_DB_SEQUENCE__CREATE (SEPG_DB_DATABASE__CREATE) |
||||
#define SEPG_DB_SEQUENCE__DROP (SEPG_DB_DATABASE__DROP) |
||||
#define SEPG_DB_SEQUENCE__GETATTR (SEPG_DB_DATABASE__GETATTR) |
||||
#define SEPG_DB_SEQUENCE__SETATTR (SEPG_DB_DATABASE__SETATTR) |
||||
#define SEPG_DB_SEQUENCE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) |
||||
#define SEPG_DB_SEQUENCE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) |
||||
#define SEPG_DB_SEQUENCE__GET_VALUE (1<<6) |
||||
#define SEPG_DB_SEQUENCE__NEXT_VALUE (1<<7) |
||||
#define SEPG_DB_SEQUENCE__SET_VALUE (1<<8) |
||||
|
||||
#define SEPG_DB_PROCEDURE__CREATE (SEPG_DB_DATABASE__CREATE) |
||||
#define SEPG_DB_PROCEDURE__DROP (SEPG_DB_DATABASE__DROP) |
||||
#define SEPG_DB_PROCEDURE__GETATTR (SEPG_DB_DATABASE__GETATTR) |
||||
#define SEPG_DB_PROCEDURE__SETATTR (SEPG_DB_DATABASE__SETATTR) |
||||
#define SEPG_DB_PROCEDURE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) |
||||
#define SEPG_DB_PROCEDURE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) |
||||
#define SEPG_DB_PROCEDURE__EXECUTE (1<<6) |
||||
#define SEPG_DB_PROCEDURE__ENTRYPOINT (1<<7) |
||||
#define SEPG_DB_PROCEDURE__INSTALL (1<<8) |
||||
|
||||
#define SEPG_DB_COLUMN__CREATE (SEPG_DB_DATABASE__CREATE) |
||||
#define SEPG_DB_COLUMN__DROP (SEPG_DB_DATABASE__DROP) |
||||
#define SEPG_DB_COLUMN__GETATTR (SEPG_DB_DATABASE__GETATTR) |
||||
#define SEPG_DB_COLUMN__SETATTR (SEPG_DB_DATABASE__SETATTR) |
||||
#define SEPG_DB_COLUMN__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) |
||||
#define SEPG_DB_COLUMN__RELABELTO (SEPG_DB_DATABASE__RELABELTO) |
||||
#define SEPG_DB_COLUMN__SELECT (1<<6) |
||||
#define SEPG_DB_COLUMN__UPDATE (1<<7) |
||||
#define SEPG_DB_COLUMN__INSERT (1<<8) |
||||
|
||||
#define SEPG_DB_TUPLE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) |
||||
#define SEPG_DB_TUPLE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) |
||||
#define SEPG_DB_TUPLE__SELECT (SEPG_DB_DATABASE__GETATTR) |
||||
#define SEPG_DB_TUPLE__UPDATE (SEPG_DB_DATABASE__SETATTR) |
||||
#define SEPG_DB_TUPLE__INSERT (SEPG_DB_DATABASE__CREATE) |
||||
#define SEPG_DB_TUPLE__DELETE (SEPG_DB_DATABASE__DROP) |
||||
|
||||
#define SEPG_DB_BLOB__CREATE (SEPG_DB_DATABASE__CREATE) |
||||
#define SEPG_DB_BLOB__DROP (SEPG_DB_DATABASE__DROP) |
||||
#define SEPG_DB_BLOB__GETATTR (SEPG_DB_DATABASE__GETATTR) |
||||
#define SEPG_DB_BLOB__SETATTR (SEPG_DB_DATABASE__SETATTR) |
||||
#define SEPG_DB_BLOB__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) |
||||
#define SEPG_DB_BLOB__RELABELTO (SEPG_DB_DATABASE__RELABELTO) |
||||
#define SEPG_DB_BLOB__READ (1<<6) |
||||
#define SEPG_DB_BLOB__WRITE (1<<7) |
||||
#define SEPG_DB_BLOB__IMPORT (1<<8) |
||||
#define SEPG_DB_BLOB__EXPORT (1<<9) |
||||
|
||||
#define SEPG_DB_LANGUAGE__CREATE (SEPG_DB_DATABASE__CREATE) |
||||
#define SEPG_DB_LANGUAGE__DROP (SEPG_DB_DATABASE__DROP) |
||||
#define SEPG_DB_LANGUAGE__GETATTR (SEPG_DB_DATABASE__GETATTR) |
||||
#define SEPG_DB_LANGUAGE__SETATTR (SEPG_DB_DATABASE__SETATTR) |
||||
#define SEPG_DB_LANGUAGE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) |
||||
#define SEPG_DB_LANGUAGE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) |
||||
#define SEPG_DB_LANGUAGE__IMPLEMENT (1<<6) |
||||
#define SEPG_DB_LANGUAGE__EXECUTE (1<<7) |
||||
|
||||
#define SEPG_DB_VIEW__CREATE (SEPG_DB_DATABASE__CREATE) |
||||
#define SEPG_DB_VIEW__DROP (SEPG_DB_DATABASE__DROP) |
||||
#define SEPG_DB_VIEW__GETATTR (SEPG_DB_DATABASE__GETATTR) |
||||
#define SEPG_DB_VIEW__SETATTR (SEPG_DB_DATABASE__SETATTR) |
||||
#define SEPG_DB_VIEW__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) |
||||
#define SEPG_DB_VIEW__RELABELTO (SEPG_DB_DATABASE__RELABELTO) |
||||
#define SEPG_DB_VIEW__EXPAND (1<<6) |
||||
|
||||
/*
|
||||
* hooks.c |
||||
*/ |
||||
extern bool sepgsql_get_permissive(void); |
||||
extern bool sepgsql_get_debug_audit(void); |
||||
|
||||
/*
|
||||
* selinux.c |
||||
*/ |
||||
extern bool sepgsql_is_enabled(void); |
||||
extern int sepgsql_get_mode(void); |
||||
extern int sepgsql_set_mode(int new_mode); |
||||
extern bool sepgsql_getenforce(void); |
||||
|
||||
extern void sepgsql_audit_log(bool denied, |
||||
const char *scontext, |
||||
const char *tcontext, |
||||
uint16 tclass, |
||||
uint32 audited, |
||||
const char *audit_name); |
||||
|
||||
extern void sepgsql_compute_avd(const char *scontext, |
||||
const char *tcontext, |
||||
uint16 tclass, |
||||
struct av_decision *avd); |
||||
|
||||
extern char *sepgsql_compute_create(const char *scontext, |
||||
const char *tcontext, |
||||
uint16 tclass); |
||||
|
||||
extern bool sepgsql_check_perms(const char *scontext, |
||||
const char *tcontext, |
||||
uint16 tclass, |
||||
uint32 required, |
||||
const char *audit_name, |
||||
bool abort); |
||||
/*
|
||||
* label.c |
||||
*/ |
||||
extern char *sepgsql_get_client_label(void); |
||||
extern char *sepgsql_set_client_label(char *new_label); |
||||
extern char *sepgsql_get_label(Oid relOid, Oid objOid, int32 subId); |
||||
|
||||
extern void sepgsql_object_relabel(const ObjectAddress *object, |
||||
const char *seclabel); |
||||
|
||||
extern Datum sepgsql_getcon(PG_FUNCTION_ARGS); |
||||
extern Datum sepgsql_mcstrans_in(PG_FUNCTION_ARGS); |
||||
extern Datum sepgsql_mcstrans_out(PG_FUNCTION_ARGS); |
||||
extern Datum sepgsql_restorecon(PG_FUNCTION_ARGS); |
||||
|
||||
/*
|
||||
* dml.c |
||||
*/ |
||||
extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort); |
||||
|
||||
/*
|
||||
* schema.c |
||||
*/ |
||||
extern void sepgsql_schema_post_create(Oid namespaceId); |
||||
extern void sepgsql_schema_relabel(Oid namespaceId, const char *seclabel); |
||||
|
||||
/*
|
||||
* relation.c |
||||
*/ |
||||
extern void sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum); |
||||
extern void sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum, |
||||
const char *seclabel); |
||||
extern void sepgsql_relation_post_create(Oid relOid); |
||||
extern void sepgsql_relation_relabel(Oid relOid, const char *seclabel); |
||||
|
||||
/*
|
||||
* proc.c |
||||
*/ |
||||
extern void sepgsql_proc_post_create(Oid functionId); |
||||
extern void sepgsql_proc_relabel(Oid functionId, const char *seclabel); |
||||
extern char *sepgsql_proc_get_domtrans(Oid functionId); |
||||
|
||||
#endif /* SEPGSQL_H */ |
@ -0,0 +1,36 @@ |
||||
-- |
||||
-- contrib/sepgsql/sepgsql.sql |
||||
-- |
||||
-- [Step to install] |
||||
-- |
||||
-- 1. Run initdb |
||||
-- to set up a new database cluster. |
||||
-- |
||||
-- 2. Edit $PGDATA/postgresql.conf |
||||
-- to add 'MODULE_PATHNAME' to shared_preload_libraries. |
||||
-- |
||||
-- Example) |
||||
-- shared_preload_libraries = 'MODULE_PATHNAME' |
||||
-- |
||||
-- 3. Run this script for each databases |
||||
-- This script installs corresponding functions, and assigns initial |
||||
-- security labels on target database objects. |
||||
-- It can be run both single-user mode and multi-user mode, according |
||||
-- to your preference. |
||||
-- |
||||
-- Example) |
||||
-- $ for DBNAME in template0 template1 postgres; \ |
||||
-- do \ |
||||
-- postgres --single -F -c exit_on_error=true -D $PGDATA $DBNAME \ |
||||
-- < /path/to/script/sepgsql.sql > /dev/null \ |
||||
-- done |
||||
-- |
||||
-- 4. Start postmaster, |
||||
-- if you initialized the database in single-user mode. |
||||
-- |
||||
LOAD 'MODULE_PATHNAME'; |
||||
CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_getcon() RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_getcon' LANGUAGE C; |
||||
CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_in(text) RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_mcstrans_in' LANGUAGE C STRICT; |
||||
CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_out(text) RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_mcstrans_out' LANGUAGE C STRICT; |
||||
CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_restorecon(text) RETURNS bool AS 'MODULE_PATHNAME', 'sepgsql_restorecon' LANGUAGE C; |
||||
SELECT sepgsql_restorecon(NULL); |
@ -0,0 +1,118 @@ |
||||
-- |
||||
-- Regression Test for DML Permissions |
||||
-- |
||||
|
||||
-- |
||||
-- Setup |
||||
-- |
||||
CREATE TABLE t1 (a int, b text); |
||||
SECURITY LABEL ON TABLE t1 IS 'system_u:object_r:sepgsql_table_t:s0'; |
||||
INSERT INTO t1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'); |
||||
|
||||
CREATE TABLE t2 (x int, y text); |
||||
SECURITY LABEL ON TABLE t2 IS 'system_u:object_r:sepgsql_ro_table_t:s0'; |
||||
INSERT INTO t2 VALUES (1, 'xxx'), (2, 'yyy'), (3, 'zzz'); |
||||
|
||||
CREATE TABLE t3 (s int, t text); |
||||
SECURITY LABEL ON TABLE t3 IS 'system_u:object_r:sepgsql_fixed_table_t:s0'; |
||||
INSERT INTO t3 VALUES (1, 'sss'), (2, 'ttt'), (3, 'uuu'); |
||||
|
||||
CREATE TABLE t4 (m int, n text); |
||||
SECURITY LABEL ON TABLE t4 IS 'system_u:object_r:sepgsql_secret_table_t:s0'; |
||||
INSERT INTO t4 VALUES (1, 'mmm'), (2, 'nnn'), (3, 'ooo'); |
||||
|
||||
CREATE TABLE t5 (e text, f text, g text); |
||||
SECURITY LABEL ON TABLE t5 IS 'system_u:object_r:sepgsql_table_t:s0'; |
||||
SECURITY LABEL ON COLUMN t5.e IS 'system_u:object_r:sepgsql_table_t:s0'; |
||||
SECURITY LABEL ON COLUMN t5.f IS 'system_u:object_r:sepgsql_ro_table_t:s0'; |
||||
SECURITY LABEL ON COLUMN t5.g IS 'system_u:object_r:sepgsql_secret_table_t:s0'; |
||||
|
||||
CREATE TABLE customer (cid int primary key, cname text, ccredit text); |
||||
SECURITY LABEL ON COLUMN customer.ccredit IS 'system_u:object_r:sepgsql_secret_table_t:s0'; |
||||
INSERT INTO customer VALUES (1, 'Taro', '1111-2222-3333-4444'), |
||||
(2, 'Hanako', '5555-6666-7777-8888'); |
||||
CREATE FUNCTION customer_credit(int) RETURNS text |
||||
AS 'SELECT regexp_replace(ccredit, ''-[0-9]+$'', ''-????'') FROM customer WHERE cid = $1' |
||||
LANGUAGE sql; |
||||
SECURITY LABEL ON FUNCTION customer_credit(int) |
||||
IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; |
||||
|
||||
SELECT objtype, objname, label FROM pg_seclabels |
||||
WHERE provider = 'selinux' |
||||
AND objtype in ('table', 'column') |
||||
AND objname in ('t1', 't2', 't3', 't4', 't5', 't5.e', 't5.f', 't5.g'); |
||||
|
||||
-- Hardwired Rules |
||||
UPDATE pg_attribute SET attisdropped = true |
||||
WHERE attrelid = 't5'::regclass AND attname = 'f'; -- failed |
||||
|
||||
-- |
||||
-- Simple DML statements |
||||
-- |
||||
-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 |
||||
|
||||
SELECT * FROM t1; -- ok |
||||
SELECT * FROM t2; -- ok |
||||
SELECT * FROM t3; -- ok |
||||
SELECT * FROM t4; -- failed |
||||
SELECT * FROM t5; -- failed |
||||
SELECT e,f FROM t5; -- ok |
||||
|
||||
SELECT * FROM customer; -- failed |
||||
SELECT cid, cname, customer_credit(cid) FROM customer; -- ok |
||||
|
||||
SELECT count(*) FROM t5; -- ok |
||||
SELECT count(*) FROM t5 WHERE g IS NULL; -- failed |
||||
|
||||
INSERT INTO t1 VALUES (4, 'abc'); -- ok |
||||
INSERT INTO t2 VALUES (4, 'xyz'); -- failed |
||||
INSERT INTO t3 VALUES (4, 'stu'); -- ok |
||||
INSERT INTO t4 VALUES (4, 'mno'); -- failed |
||||
INSERT INTO t5 VALUES (1,2,3); -- failed |
||||
INSERT INTO t5 (e,f) VALUES ('abc', 'def'); -- failed |
||||
INSERT INTO t5 (e) VALUES ('abc'); -- ok |
||||
|
||||
UPDATE t1 SET b = b || '_upd'; -- ok |
||||
UPDATE t2 SET y = y || '_upd'; -- failed |
||||
UPDATE t3 SET t = t || '_upd'; -- failed |
||||
UPDATE t4 SET n = n || '_upd'; -- failed |
||||
UPDATE t5 SET e = 'xyz'; -- ok |
||||
UPDATE t5 SET e = f || '_upd'; -- ok |
||||
UPDATE t5 SET e = g || '_upd'; -- failed |
||||
|
||||
DELETE FROM t1; -- ok |
||||
DELETE FROM t2; -- failed |
||||
DELETE FROM t3; -- failed |
||||
DELETE FROM t4; -- failed |
||||
DELETE FROM t5; -- ok |
||||
DELETE FROM t5 WHERE f IS NULL; -- ok |
||||
DELETE FROM t5 WHERE g IS NULL; -- failed |
||||
|
||||
-- |
||||
-- COPY TO/FROM statements |
||||
-- |
||||
COPY t1 TO '/dev/null'; -- ok |
||||
COPY t2 TO '/dev/null'; -- ok |
||||
COPY t3 TO '/dev/null'; -- ok |
||||
COPY t4 TO '/dev/null'; -- failed |
||||
COPY t5 TO '/dev/null'; -- failed |
||||
COPY t5(e,f) TO '/dev/null'; -- ok |
||||
|
||||
COPY t1 FROM '/dev/null'; -- ok |
||||
COPY t2 FROM '/dev/null'; -- failed |
||||
COPY t3 FROM '/dev/null'; -- ok |
||||
COPY t4 FROM '/dev/null'; -- failed |
||||
COPY t5 FROM '/dev/null'; -- failed |
||||
COPY t5 (e,f) FROM '/dev/null'; -- failed |
||||
COPY t5 (e) FROM '/dev/null'; -- ok |
||||
|
||||
-- |
||||
-- Clean up |
||||
-- |
||||
-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255 |
||||
DROP TABLE IF EXISTS t1 CASCADE; |
||||
DROP TABLE IF EXISTS t2 CASCADE; |
||||
DROP TABLE IF EXISTS t3 CASCADE; |
||||
DROP TABLE IF EXISTS t4 CASCADE; |
||||
DROP TABLE IF EXISTS t5 CASCADE; |
||||
DROP TABLE IF EXISTS customer CASCADE; |
@ -0,0 +1,73 @@ |
||||
-- |
||||
-- Regression Tests for Label Management |
||||
-- |
||||
|
||||
-- |
||||
-- Setup |
||||
-- |
||||
CREATE TABLE t1 (a int, b text); |
||||
INSERT INTO t1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'); |
||||
SELECT * INTO t2 FROM t1 WHERE a % 2 = 0; |
||||
|
||||
CREATE FUNCTION f1 () RETURNS text |
||||
AS 'SELECT sepgsql_getcon()' |
||||
LANGUAGE sql; |
||||
|
||||
CREATE FUNCTION f2 () RETURNS text |
||||
AS 'SELECT sepgsql_getcon()' |
||||
LANGUAGE sql; |
||||
SECURITY LABEL ON FUNCTION f2() |
||||
IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; |
||||
|
||||
CREATE FUNCTION f3 () RETURNS text |
||||
AS 'BEGIN |
||||
RAISE EXCEPTION ''an exception from f3()''; |
||||
RETURN NULL; |
||||
END;' LANGUAGE plpgsql; |
||||
SECURITY LABEL ON FUNCTION f3() |
||||
IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; |
||||
|
||||
-- |
||||
-- Tests for default labeling behavior |
||||
-- |
||||
-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 |
||||
CREATE TABLE t3 (s int, t text); |
||||
INSERT INTO t3 VALUES (1, 'sss'), (2, 'ttt'), (3, 'uuu'); |
||||
|
||||
SELECT objtype, objname, label FROM pg_seclabels |
||||
WHERE provider = 'selinux' |
||||
AND objtype in ('table', 'column') |
||||
AND objname in ('t1', 't2', 't3'); |
||||
|
||||
-- |
||||
-- Tests for SECURITY LABEL |
||||
-- |
||||
-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_dba_t:s0 |
||||
SECURITY LABEL ON TABLE t1 |
||||
IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok |
||||
SECURITY LABEL ON TABLE t2 |
||||
IS 'invalid seuciryt context'; -- be failed |
||||
SECURITY LABEL ON COLUMN t2 |
||||
IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- be failed |
||||
SECURITY LABEL ON COLUMN t2.b |
||||
IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok |
||||
|
||||
-- |
||||
-- Tests for Trusted Procedures |
||||
-- |
||||
-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 |
||||
SELECT f1(); -- normal procedure |
||||
SELECT f2(); -- trusted procedure |
||||
SELECT f3(); -- trusted procedure that raises an error |
||||
SELECT sepgsql_getcon(); -- client's label must be restored |
||||
|
||||
-- |
||||
-- Clean up |
||||
-- |
||||
-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255 |
||||
DROP TABLE IF EXISTS t1 CASCADE; |
||||
DROP TABLE IF EXISTS t2 CASCADE; |
||||
DROP TABLE IF EXISTS t3 CASCADE; |
||||
DROP FUNCTION IF EXISTS f1() CASCADE; |
||||
DROP FUNCTION IF EXISTS f2() CASCADE; |
||||
DROP FUNCTION IF EXISTS f3() CASCADE; |
@ -0,0 +1,5 @@ |
||||
-- |
||||
-- Regression Test for Misc Permission Checks |
||||
-- |
||||
|
||||
LOAD '$libdir/sepgsql'; -- failed |
@ -0,0 +1,704 @@ |
||||
<!-- doc/src/sgml/sepgsql.sgml --> |
||||
|
||||
<sect1 id="sepgsql"> |
||||
<title>sepgsql</title> |
||||
|
||||
<indexterm zone="sepgsql"> |
||||
<primary>sepgsql</primary> |
||||
</indexterm> |
||||
|
||||
<para> |
||||
The <filename>sepgsql</> is a module which performs as an external |
||||
security provider; to support label based mandatory access control |
||||
(MAC) base on <productname>SELinux</> policy. |
||||
</para> |
||||
<para> |
||||
This extension won't build at all unless the installation was configured |
||||
with <literal>--with-selinux</>. |
||||
</para> |
||||
|
||||
<sect2 id="sepgsql-overview"> |
||||
<title>Overview</title> |
||||
|
||||
<para> |
||||
<productname>PostgreSQL</> provides various kind of hooks. Some of these |
||||
hooks can be utilized to make access control decision on the supplied |
||||
users' accesses on database objects. |
||||
We call plug-in modules making access control decision based on its own |
||||
security model as an external security provider. |
||||
</para> |
||||
<para> |
||||
This module acquires control on these strategic points, then it asks |
||||
<productname>SELinux</> to check whether the supplied access shall be |
||||
allowed, or not. Then, it returns its access control decision. |
||||
If violated, this module prevents this access with rising an error for |
||||
example. |
||||
</para> |
||||
<para> |
||||
A series of making decision is done independently from the default |
||||
database privilege mechanism. Users must be allowed with both of access |
||||
control models, whenever they try to access something. |
||||
</para> |
||||
<para> |
||||
We can see <productname>SELinux</> as a function which takes two arguments |
||||
then returns a bool value; allowed or denied. The first argument in this |
||||
analogy is label of subject which tries to reference a certain obejct. |
||||
The other one is label of the object being referenced in this operation. |
||||
</para> |
||||
<para> |
||||
Label is a formatted string, |
||||
like <literal>system_u:object_r:sepgsql_table_t:s0</>. |
||||
It is not a property depending on characteristics of a certain kind of |
||||
object, so we can apply common credentials on either database objects |
||||
or others. |
||||
</para> |
||||
<para> |
||||
<productname>PostgreSQL</> 9.1 or later supports |
||||
<xref linkend="sql-security-label"> statement that allows to assign |
||||
a security label on specified database objects, if user wants to change |
||||
label from the creation default. |
||||
Also <productname>SELinux</> provides an interface to obtain security |
||||
label of the peer process that connected to. |
||||
</para> |
||||
<para> |
||||
These facilities enable to integrate <productname>SELinux</> model within |
||||
access controls to database objects. Because it makes access control |
||||
decision according to a common centralized security policy (a set of rules), |
||||
its decision will be always consistent independent from the way to store |
||||
information assets. |
||||
</para> |
||||
</sect2> |
||||
<sect2 id="sepgsql-installation"> |
||||
<title>Installation</title> |
||||
<para> |
||||
The <filename>sepgsql</> module requires the following packages to install. |
||||
Please check it at first. |
||||
</para> |
||||
<variablelist> |
||||
<varlistentry> |
||||
<term><productname>Linux kernel</productname></term> |
||||
<listitem> |
||||
<para> |
||||
v2.6.28 or later with built with SELinux enabled |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term><productname>libselinux</productname></term> |
||||
<listitem> |
||||
<para> |
||||
v2.0.80 or later |
||||
</para> |
||||
<para> |
||||
This library provides a set of APIs to communicate with |
||||
<productname>SELinux</> in kernel. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term><productname>selinux-policy</productname></term> |
||||
<listitem> |
||||
<para> |
||||
v3.9.13 or later |
||||
</para> |
||||
<para> |
||||
The default security policy provides a set of access control rules. |
||||
Some of distribution may backports necessary rules, even if base |
||||
policy was older than above version. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
<para> |
||||
<productname>SE-PostgreSQL</> needs <productname>SELinux</> being |
||||
available on the platform. You can check the current setting using |
||||
<command>sestatus</>. |
||||
<screen> |
||||
$ sestatus |
||||
SELinux status: enabled |
||||
SELinuxfs mount: /selinux |
||||
Current mode: enforcing |
||||
Mode from config file: enforcing |
||||
Policy version: 24 |
||||
Policy from config file: targeted |
||||
</screen> |
||||
If disabled or not-installed, you need to set up <productname>SELinux</> |
||||
prior to all the installation step of <productname>SE-PostgreSQL</>. |
||||
</para> |
||||
<para> |
||||
On the compile time, add <literal>--with-selinux</> option to |
||||
the <command>configure</> script to check existence of |
||||
the <productname>libselinux</>, and to set a flag whether |
||||
we build this contrib module, or not. |
||||
<screen> |
||||
$ ./configure --enable-debug --enable-cassert --with-selinux |
||||
$ make |
||||
$ make install |
||||
</screen> |
||||
</para> |
||||
<para> |
||||
Next to the <command>initdb</>, add <literal>'$libdir/sepgsql'</> |
||||
to <xref linkend="guc-shared-preload-libraries"> in |
||||
the <filename>postgresql.conf</>. |
||||
|
||||
It enables to load <filename>sepgsql</> on the starting up of |
||||
postmaster process. |
||||
</para> |
||||
<para> |
||||
Then, load the <filename>sepgsql.sql</filename> script for each databases. |
||||
It installs functions corresponding to security label management, and |
||||
tries to assign initial labels on the target objects. |
||||
</para> |
||||
<para> |
||||
The following instruction assumes your installation is under the |
||||
<filename>/usr/local/pgsql</> directory, and the database cluster is in |
||||
<filename>/usr/local/pgsql/data</>. Substitute your paths appropriately. |
||||
</para> |
||||
<screen> |
||||
$ initdb -D $PGDATA |
||||
$ vi $PGDATA/postgresql.conf |
||||
$ for DBNAME in template0 template1 postgres; do |
||||
postgres --single -F -O -c exit_on_error=true -D $PGDATA $DBNAME \ |
||||
< /usr/local/pgsql/share/contrib/sepgsql.sql > /dev/null |
||||
done |
||||
</screen> |
||||
<para> |
||||
If all the installation process was done with no errors, start postmaster |
||||
process. <productname>SE-PostgreSQL</> shall prevent violated accesses |
||||
according to the security policy of <productname>SELinux</>. |
||||
</para> |
||||
</sect2> |
||||
|
||||
<sect2 id="sepgsql-regression"> |
||||
<title>Regression Tests</title> |
||||
<para> |
||||
The regression test of this module requires a few more configurations |
||||
on the platform system, in addition to the above installation process. |
||||
See the following steps. |
||||
</para> |
||||
<para> |
||||
First, install the policy package for regression test. |
||||
The <filename>sepgsql-regtest.pp</> is a special purpose policy package |
||||
that provides a set of rules to be allowed during the regression test |
||||
cases. It shall be installed at <filename>/usr/local/pgsql/share/contrib</> |
||||
directory in the default setup. |
||||
</para> |
||||
<para> |
||||
You need to install this policy package using <command>semodule</> |
||||
command which enables to link supplied policy packages and load them |
||||
into the kernel space. If you could install the pakage correctly, |
||||
<literal><command>semodule</> -l</> prints sepgsql-regtest as a part |
||||
of policy packages currently available. |
||||
</para> |
||||
<screen> |
||||
$ su |
||||
# semodule -u /usr/local/pgsql/share/contrib/sepgsql-regtest.pp |
||||
# semodule -l |
||||
: |
||||
sepgsql-regtest 1.03 |
||||
: |
||||
</screen> |
||||
<para> |
||||
Second, turn on the <literal>sepgsql_regression_test_mode</>. |
||||
We don't enable all the rules in the <filename>sepgsql-regtest.pp</> |
||||
in the default, for your system's safety. |
||||
The <literal>sepgsql_regression_test_mode</literal> parameter is associated |
||||
with rules to launch regression test. |
||||
It can be turned on using <command>setsebool</> command. |
||||
</para> |
||||
<screen> |
||||
$ su |
||||
# setsebool sepgsql_regression_test_mode on |
||||
# getsebool sepgsql_regression_test_mode |
||||
sepgsql_regression_test_mode --> on |
||||
</screen> |
||||
<para> |
||||
Last, kick the regression test from the <literal>unconfined_t</> domain. |
||||
</para> |
||||
<para> |
||||
This test policy is designed to kick each test cases from the |
||||
<literal>unconfined_t</> domain that is a default choice in most of |
||||
the known <literal>SELinux</> installation base. |
||||
So, you don't need to set up anything special, as long as you didn't |
||||
change default configuration of SELinux before. |
||||
</para> |
||||
<para> |
||||
The <command>id</> command tells us the current working domain. |
||||
Confirm your shell is now performing with <literal>unconfined_t</> |
||||
domain as follows. |
||||
</para> |
||||
<screen> |
||||
$ id -Z |
||||
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 |
||||
</screen> |
||||
<para> |
||||
If not an expected one, you should revert this configuration. |
||||
The <xref linkend="sepgsql-resources"> section will give you |
||||
some useful hints. |
||||
</para> |
||||
<para> |
||||
Then, you will see the all-green result of regression test, |
||||
if we have no problem here. |
||||
</para> |
||||
<screen> |
||||
$ make -C contrib/sepgsql/ installcheck |
||||
: |
||||
../../src/test/regress/pg_regress --inputdir=. --psqldir=/usr/local/pgsql/bin \ |
||||
--dbname=contrib_regression --launcher ../../contrib/sepgsql/launcher \ |
||||
label dml |
||||
(using postmaster on Unix socket, default port) |
||||
============== dropping database "contrib_regression" ============== |
||||
DROP DATABASE |
||||
============== creating database "contrib_regression" ============== |
||||
CREATE DATABASE |
||||
ALTER DATABASE |
||||
============== running regression test queries ============== |
||||
test label ... ok |
||||
test dml ... ok |
||||
test misc ... ok |
||||
|
||||
===================== |
||||
All 3 tests passed. |
||||
===================== |
||||
</screen> |
||||
<para> |
||||
If <command>pg_regress</> failed to launch <command>psql</> command, |
||||
here is a hint to fix up the matter. |
||||
|
||||
When we try to launch <command>psql</> command with restrictive |
||||
privileges, the <command>psql</> must eb labeled as <literal>bin_t</>. |
||||
If not, try to run <command>restorecon</> to fix up security label of |
||||
the commands as expected. |
||||
</para> |
||||
<screen> |
||||
$ restorecon -R /usr/local/pgsql/ |
||||
</screen> |
||||
</sect2> |
||||
|
||||
<sect2 id="sepgsql-parameters"> |
||||
<title>GUC Parameters</title> |
||||
|
||||
<variablelist> |
||||
<varlistentry id="guc-sepgsql-permissive" xreflabel="sepgsql.permissive"> |
||||
<term><varname>sepgsql.permissive</> (<type>boolean</type>)</term> |
||||
<indexterm> |
||||
<primary><varname>sepgsql.permissive</> configuration parameter</primary> |
||||
</indexterm> |
||||
<listitem> |
||||
<para> |
||||
This parameter enables to perform <productname>SE-PostgreSQL</> |
||||
in permissive mode independent from the system setting. |
||||
The default is off (according to the system setting). |
||||
This parameter can only be set in the <filename>postgresql.conf</> |
||||
file or on the server command line. |
||||
</para> |
||||
<para> |
||||
We have two performing mode except for disabled; The one is enforcing |
||||
mode that checks the security policy on references and actually prevents |
||||
violated accesses. The other is permissive mode that only checks |
||||
the security policy, but does not prevents anything except for log |
||||
generation. |
||||
This log shall be utilized for debugging of the security policy itself. |
||||
</para> |
||||
<para> |
||||
When this parameter is on, <productname>SE-PostgreSQL</> performs |
||||
in permissive mode, even if the platform system is working on enforcing |
||||
mode. |
||||
We recommend users to keep the default setting, except for the case |
||||
when we develop security policy by ourself. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry id="guc-sepgsql-debug-audit" xreflabel="sepgsql.debug_audit"> |
||||
<term><varname>sepgsql.debug_audit</> (<type>boolean</>)</> |
||||
<indexterm> |
||||
<primary><varname>sepgsql.debug_audit</> configuration parameter</> |
||||
</indexterm> |
||||
<listitem> |
||||
<para> |
||||
This parameter enables to print audit messages independent from |
||||
the policy setting. |
||||
The default is off (according to the security policy setting). |
||||
</para> |
||||
<para> |
||||
The security policy of <productname>SELinux</> also has rules to |
||||
control what accesses shall be logged, or not. |
||||
In the default, any access violations are logged, but any allowed |
||||
accesses are not logged. |
||||
</para> |
||||
<para> |
||||
When this parameter is on, all the possible logs shall be printed |
||||
independently from the policy settings. |
||||
We recommend to keep the variable turned off in normal cases to |
||||
avoid noisy messages. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
</sect2> |
||||
|
||||
<sect2 id="sepgsql-features"> |
||||
<title>Features</title> |
||||
<sect3> |
||||
<title>controlled object classes</title> |
||||
<para> |
||||
The security model of <productname>SELinux</> describes all the access |
||||
control rules as a relationship between a subject entity (typically, |
||||
it is a client of database) and an object entity. |
||||
And, these entities are identified by a security label. |
||||
</para> |
||||
<para> |
||||
We call a set of these rules as security policy. |
||||
All the access control decision shall be made according to the security |
||||
policy, when we ask SELinux whether the required action shall be allowed |
||||
or not. |
||||
Thus, we have no way to control accesses on any sort of objects without |
||||
security labels. |
||||
(<productname>SELinux</> assumes <literal>unlabeled_t</> is assigned, |
||||
if no valid security label is assigned on the target object.) |
||||
</para> |
||||
<para> |
||||
This version of <productname>SE-PostgreSQL</> supports to assign |
||||
a security label on these database object classes: schema, table, column, |
||||
sequence, view and procedure. |
||||
Other database object classes are not supported to assign security label |
||||
on, right now. |
||||
</para> |
||||
<para> |
||||
A security label shall be automatically assigned to the supported |
||||
database objects on their creation time. |
||||
This label is called as a default security label; being decided according |
||||
to the security policy, or a pair of security label of the client and |
||||
upper object for more correctly. |
||||
</para> |
||||
<para> |
||||
A new database object basically inherits security label of the upper |
||||
object. A new column inherits security label of its parent table for |
||||
instance. |
||||
If and when the security policy has special rules called as |
||||
type-transition on a pair of the client and upper object, we can assign |
||||
an individual label as a default. The upper object depends on sort of |
||||
object classes as follows. |
||||
</para> |
||||
<variablelist> |
||||
<varlistentry> |
||||
<term>schema</term> |
||||
<listitem> |
||||
<para> |
||||
Its upper object is the current database. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term>table</term> |
||||
<listitem> |
||||
<para> |
||||
Its upper object is the schema object which owns the new table. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term>column</term> |
||||
<listitem> |
||||
<para> |
||||
Its upper object is the table object which owns the new column. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term>sequence</term> |
||||
<listitem> |
||||
<para> |
||||
Its upper object is the schema object which owns the new sequence. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term>view</term> |
||||
<listitem> |
||||
<para> |
||||
Its upper object is the schema object which owns the new view. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term>procedure</term> |
||||
<listitem> |
||||
<para> |
||||
Its upper object is the schema object which owns the new procedure. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
</sect3> |
||||
<sect3> |
||||
<title>DML Permissions</title> |
||||
<para> |
||||
This section introduces what permissions shall be checked on DML; |
||||
<literal>SELECT</>, <literal>INSERT</>, <literal>UPDATE</> and |
||||
<literal>DELETE</>. |
||||
</para> |
||||
<para> |
||||
DML statements are used to reference or modify contents within |
||||
the specified database objects; such as tables or columns. |
||||
We basically checks access rights of the client on all the appeared |
||||
objects in the given statement, and kind of privileges depend on |
||||
class of object and sort of accesses. |
||||
</para> |
||||
<para> |
||||
For tables, <literal>db_table:select</>, <literal>db_table:insert</>, |
||||
<literal>db_table:update</> or <literal>db_table:delete</> shall be |
||||
checked for all the appeared target tables depending on the sort of |
||||
statement; |
||||
In addition, <literal>db_table:select</> shall be also checked for |
||||
all the tables that containin the columns to be referenced in |
||||
<literal>WHERE</> or <literal>RETURNING</> clause, as a data source |
||||
of <literal>UPDATE</>, and so on. |
||||
</para> |
||||
<para> |
||||
<synopsis> |
||||
UPDATE t1 SET x = 2, y = md5sum(y) WHERE z = 100; |
||||
</synopsis> |
||||
In this case, we must have <literal>db_table:select</>, not only |
||||
<literal>db_table:update</>, because <literal>t1.a</> is referenced |
||||
within <literal>WHERE</> clause. |
||||
Also note that column-level permission shall be checked individually. |
||||
</para> |
||||
<para> |
||||
The client must be allowed to reference all the appeared tables and |
||||
columns, even if they are originated from views then expanded, unlike |
||||
the default database privileges, because we intend to apply consistent |
||||
access control rules independent from the route to reference contents |
||||
of the tables. |
||||
</para> |
||||
<para> |
||||
For columns, <literal>db_column:select</> shall be also checked on |
||||
not only the columns being read using <literal>SELECT</>, but being |
||||
referenced in other DML statement. |
||||
</para> |
||||
<para> |
||||
Of course, it also checks <literal>db_column:update</> or |
||||
<literal>db_column:insert</> on the column being modified by |
||||
<literal>UPDATE</> or <literal>INSERT</>. |
||||
Note that we have no definition of column-level delete permission, |
||||
like as the default database privilege doing. |
||||
</para> |
||||
<para> |
||||
<synopsis> |
||||
UPDATE t1 SET x = 2, y = md5sum(y) WHERE z = 100; |
||||
</synopsis> |
||||
In this case, it checks <literal>db_column:update</> on |
||||
the <literal>t1.x</> being updated, <literal>db_column:{select update}</> |
||||
on the <literal>t1.y</> being updated and referenced, |
||||
and <literal>db_column:select</> on the <literal>t1.z</> being only |
||||
referenced in the <literal>WHERE</> clause. |
||||
Also note that <literal>db_table:{select update}</> shall be checked |
||||
in the table-level granularity. |
||||
</para> |
||||
<para> |
||||
For sequences, <literal>db_sequence:get_value</> when we reference |
||||
a sequence object using <literal>SELECT</>, however, note that we |
||||
cannot check permissions on execution of corresponding functions |
||||
such as <literal>lastval()</> right now, although they performs same |
||||
job, because here is no object access hook to acquire controls. |
||||
</para> |
||||
<para> |
||||
For views, <literal>db_view:expand</> shall be checked, then any other |
||||
corresponding permissions shall be also checked on the objects being |
||||
expanded from the view, individually. |
||||
Note that both of permissions have to be allowed. |
||||
</para> |
||||
<para> |
||||
For procedures, <literal>db_procedure:{execute}</> is defined, but not |
||||
checked in this version. |
||||
</para> |
||||
<para> |
||||
Here is a few more corner cases. |
||||
The default database privilege system allows database superusers to |
||||
modify system catalogs using DML commands, and reference or modify |
||||
toast tables, however, both of the cases shall be denied when |
||||
<productname>SE-PostgreSQL</> is enabled. |
||||
</para> |
||||
</sect3> |
||||
<sect3> |
||||
<title>DDL Permissions</title> |
||||
<para> |
||||
On <xref linkend="sql-security-label"> command, <literal>setattr</> and |
||||
<literal>relabelfrom</> shall be checked on the object being relabeled |
||||
with an old security label, then <literal>relabelto</> on the supplied |
||||
new security label. |
||||
</para> |
||||
<para> |
||||
In a case when multiple label providers are installed and user tries |
||||
to set a security label, but is not managed by <productname>SELinux</>, |
||||
only <literal>setattr</> should be checked here. |
||||
However, it is not unavailable because of limitation of the hook. |
||||
</para> |
||||
<para> |
||||
As we will describe in <xref linkend="sepgsql-limitations"> section, |
||||
<productname>SE-PostgreSQL</> does not control any other DDL operations. |
||||
</para> |
||||
</sect3> |
||||
<sect3> |
||||
<title>Trusted Procedure</title> |
||||
<para> |
||||
It is a similar idea to security definer functions or set-uid commands |
||||
on operating systems. <productname>SELinux</> provides a feature to |
||||
switch privilege of the client (that is a security label of the client |
||||
for more correctness) during execution of certain functions; being |
||||
called as trusted procedures. |
||||
</para> |
||||
<para> |
||||
A trusted function is a function with a special security label being |
||||
set up as a trusted procedure. |
||||
So, we need to assign the special security label on the function that |
||||
we hope it to perform as a trusted procedure, by administrative users. |
||||
The default security policy also provides this special security label. |
||||
See the following example. |
||||
</para> |
||||
<screen> |
||||
postgres=# CREATE TABLE customer ( |
||||
cid int primary key, |
||||
cname text, |
||||
credit text |
||||
); |
||||
CREATE TABLE |
||||
postgres=# SECURITY LABEL ON COLUMN customer.credit |
||||
IS 'system_u:object_r:sepgsql_secret_table_t:s0'; |
||||
SECURITY LABEL |
||||
postgres=# CREATE FUNCTION show_credit(int) RETURNS text |
||||
AS 'SELECT regexp_replace(credit, ''-[0-9]+$'', ''-xxxx'', ''g'') |
||||
FROM customer WHERE cid = $1' |
||||
LANGUAGE sql; |
||||
CREATE FUNCTION |
||||
postgres=# SECURITY LABEL ON FUNCTION show_credit(int) |
||||
IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; |
||||
SECURITY LABEL |
||||
</screen> |
||||
<para> |
||||
Above operations shall be done by administrative users. |
||||
</para> |
||||
<screen> |
||||
postgres=# SELECT * FROM customer; |
||||
ERROR: SELinux: security policy violation |
||||
postgres=# SELECT cid, cname, show_credit(cid) FROM customer; |
||||
cid | cname | show_credit |
||||
-----+--------+--------------------- |
||||
1 | taro | 1111-2222-3333-xxxx |
||||
2 | hanako | 5555-6666-7777-xxxx |
||||
(2 rows) |
||||
</screen> |
||||
<para> |
||||
In this case, a regular user cannot reference <literal>customer.credit</> |
||||
directly, but a trusted procedure <literal>show_credit</> enables us |
||||
to print credit number of customers with a bit modification. |
||||
</para> |
||||
</sect3> |
||||
<sect3> |
||||
<title>Miscellaneous</title> |
||||
<para> |
||||
In this version, we reject <xref linkend="sql-load"> command across |
||||
the board, because the binary module can override security hooks to |
||||
make access control decision. It means a risk to invalidate all the |
||||
control by security providers. |
||||
</para> |
||||
</sect3> |
||||
</sect2> |
||||
<sect2 id="sepgsql-limitations"> |
||||
<title>Limitations</title> |
||||
<para> |
||||
This section introduces limitations of <productname>SE-PostgreSQL</> |
||||
in this version. |
||||
</para> |
||||
<variablelist> |
||||
<varlistentry> |
||||
<term>Userspace access vector cache</term> |
||||
<listitem> |
||||
<para> |
||||
<productname>SE-PostgreSQL</> tells <productname>SELinux</> its access |
||||
control decision. It takes system call invocation being heavy, however, |
||||
we can reduce number of the invocations using caching mechanism; called |
||||
as access vector cache in <productname>SELinux</>. |
||||
Because of code size, <productname>SE-PostgreSQL</> does not support |
||||
this mechanism yet. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term>DDL Permissions</term> |
||||
<listitem> |
||||
<para> |
||||
Now <productname>PostgreSQL</> does not provide a set of hooks on |
||||
the DDL routines. |
||||
It means plugin modules cannot acquire control here, |
||||
so <productname>SE-PostgreSQL</> does not check DDL Permissions |
||||
right now. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term>Row-level access control</term> |
||||
<listitem> |
||||
<para> |
||||
Now <productname>SE-PostgreSQL</> does not support row-level access |
||||
control, because a few needed facilities are not supported yet. |
||||
The one is security labels on users' tables. The other is behavior of |
||||
optimizer. Also see <xref linkend="rules-privileges"> for more details. |
||||
We know similar issue on VIEW. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term>Covert channels</term> |
||||
<listitem> |
||||
<para> |
||||
<productname>SE-PostgreSQL</> never tries to hide existence of |
||||
a certain object, even if user is not allowed to reference. |
||||
For example, we can infer an existence of invisible object using |
||||
primary-key confliction, foreign-key violation, and so on, even if |
||||
we cannot reference contents of these objects. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
</sect2> |
||||
<sect2 id="sepgsql-resources"> |
||||
<title>External Resources</title> |
||||
<variablelist> |
||||
<varlistentry> |
||||
<term><ulink url="http://wiki.postgresql.org/wiki/SEPostgreSQL">SE-PostgreSQL Introduction</ulink></term> |
||||
<listitem> |
||||
<para> |
||||
This wikipage provides a brief-overview, security design, architecture, |
||||
administration and upcoming feature for more details. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term><ulink url="http://docs.fedoraproject.org/selinux-user-guide/">Fedora SELinux User Guide</ulink></term> |
||||
<listitem> |
||||
<para> |
||||
This document provides wide spectrum of knowledge to administrate |
||||
SELinux on your systems. |
||||
It primary focuses on Fedora, but not limited to Fedora. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
<varlistentry> |
||||
<term><ulink url="http://docs.fedoraproject.org/selinux-faq">Fedora SELinux FAQ</ulink></term> |
||||
<listitem> |
||||
<para> |
||||
This document provides FAQs about SELinux. |
||||
It primary focuses on Fedora, but not limited to Fedora. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
</sect2> |
||||
<sect2 id="sepgsql-author"> |
||||
<title>Author</title> |
||||
<para> |
||||
KaiGai Kohei (<email>kaigai@ak.jp.nec.com</email>) |
||||
</para> |
||||
</sect2> |
||||
</sect1> |
Loading…
Reference in new issue