diff options
author | Robert Haas | 2011-01-24 01:44:48 +0000 |
---|---|---|
committer | Robert Haas | 2011-01-24 01:48:27 +0000 |
commit | 968bc6fac91d6aaca594488ab85c179b686cbbdd (patch) | |
tree | 3cb8fa7ee4101723733e5ed5a06803f9c299c2d7 /contrib | |
parent | e5487f65fdbd05716ade642a3ae1c5c6e85b6f22 (diff) |
sepgsql, an SE-Linux integration for PostgreSQL
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 Kohei
Diffstat (limited to 'contrib')
-rw-r--r-- | contrib/Makefile | 4 | ||||
-rw-r--r-- | contrib/README | 4 | ||||
-rw-r--r-- | contrib/sepgsql/.gitignore | 1 | ||||
-rw-r--r-- | contrib/sepgsql/Makefile | 25 | ||||
-rw-r--r-- | contrib/sepgsql/dml.c | 353 | ||||
-rw-r--r-- | contrib/sepgsql/expected/dml.out | 182 | ||||
-rw-r--r-- | contrib/sepgsql/expected/label.out | 109 | ||||
-rw-r--r-- | contrib/sepgsql/expected/misc.out | 5 | ||||
-rw-r--r-- | contrib/sepgsql/hooks.c | 446 | ||||
-rw-r--r-- | contrib/sepgsql/label.c | 477 | ||||
-rw-r--r-- | contrib/sepgsql/launcher | 52 | ||||
-rw-r--r-- | contrib/sepgsql/proc.c | 158 | ||||
-rw-r--r-- | contrib/sepgsql/relation.c | 267 | ||||
-rw-r--r-- | contrib/sepgsql/schema.c | 98 | ||||
-rw-r--r-- | contrib/sepgsql/selinux.c | 631 | ||||
-rw-r--r-- | contrib/sepgsql/sepgsql-regtest.te | 59 | ||||
-rw-r--r-- | contrib/sepgsql/sepgsql.h | 288 | ||||
-rw-r--r-- | contrib/sepgsql/sepgsql.sql.in | 36 | ||||
-rw-r--r-- | contrib/sepgsql/sql/dml.sql | 118 | ||||
-rw-r--r-- | contrib/sepgsql/sql/label.sql | 73 | ||||
-rw-r--r-- | contrib/sepgsql/sql/misc.sql | 5 |
21 files changed, 3391 insertions, 0 deletions
diff --git a/contrib/Makefile b/contrib/Makefile index 76fa2a65769..2b314501f7c 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -61,6 +61,10 @@ ifeq ($(with_libxml),yes) SUBDIRS += xml2 endif +ifeq ($(with_selinux),yes) +SUBDIRS += sepgsql +endif + # Missing: # start-scripts \ (does not have a makefile) diff --git a/contrib/README b/contrib/README index fdc5dc11e64..3c4e3242714 100644 --- a/contrib/README +++ b/contrib/README @@ -163,6 +163,10 @@ seg - Confidence-interval datatype (GiST indexing example) by Gene Selkov, Jr. <[email protected]> +sepgsql - + External security provider using SELinux + by KaiGai Kohei <[email protected]> + spi - Various trigger functions, examples for using SPI. diff --git a/contrib/sepgsql/.gitignore b/contrib/sepgsql/.gitignore new file mode 100644 index 00000000000..1e4a297b09b --- /dev/null +++ b/contrib/sepgsql/.gitignore @@ -0,0 +1 @@ +/sepgsql.sql diff --git a/contrib/sepgsql/Makefile b/contrib/sepgsql/Makefile new file mode 100644 index 00000000000..37a6dce89d8 --- /dev/null +++ b/contrib/sepgsql/Makefile @@ -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 $@ diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c new file mode 100644 index 00000000000..cfa436d37d9 --- /dev/null +++ b/contrib/sepgsql/dml.c @@ -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; +} diff --git a/contrib/sepgsql/expected/dml.out b/contrib/sepgsql/expected/dml.out new file mode 100644 index 00000000000..5625ebcd9ee --- /dev/null +++ b/contrib/sepgsql/expected/dml.out @@ -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; diff --git a/contrib/sepgsql/expected/label.out b/contrib/sepgsql/expected/label.out new file mode 100644 index 00000000000..0f0615cb306 --- /dev/null +++ b/contrib/sepgsql/expected/label.out @@ -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; diff --git a/contrib/sepgsql/expected/misc.out b/contrib/sepgsql/expected/misc.out new file mode 100644 index 00000000000..5242333bf49 --- /dev/null +++ b/contrib/sepgsql/expected/misc.out @@ -0,0 +1,5 @@ +-- +-- Regression Test for Misc Permission Checks +-- +LOAD '$libdir/sepgsql'; -- failed +ERROR: SELinux: LOAD is not allowed anyway. diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c new file mode 100644 index 00000000000..6b55e484cfd --- /dev/null +++ b/contrib/sepgsql/hooks.c @@ -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; +} diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c new file mode 100644 index 00000000000..bc28adfea55 --- /dev/null +++ b/contrib/sepgsql/label.c @@ -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); +} diff --git a/contrib/sepgsql/launcher b/contrib/sepgsql/launcher new file mode 100644 index 00000000000..9e5ecdc400b --- /dev/null +++ b/contrib/sepgsql/launcher @@ -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 diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c new file mode 100644 index 00000000000..f1a7b9b7505 --- /dev/null +++ b/contrib/sepgsql/proc.c @@ -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; +} diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c new file mode 100644 index 00000000000..ceaa6b02357 --- /dev/null +++ b/contrib/sepgsql/relation.c @@ -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); +} diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c new file mode 100644 index 00000000000..df33a027353 --- /dev/null +++ b/contrib/sepgsql/schema.c @@ -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); +} diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c new file mode 100644 index 00000000000..a67bd567112 --- /dev/null +++ b/contrib/sepgsql/selinux.c @@ -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; +} diff --git a/contrib/sepgsql/sepgsql-regtest.te b/contrib/sepgsql/sepgsql-regtest.te new file mode 100644 index 00000000000..66666d0c38a --- /dev/null +++ b/contrib/sepgsql/sepgsql-regtest.te @@ -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; +') diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h new file mode 100644 index 00000000000..1a27818fbe8 --- /dev/null +++ b/contrib/sepgsql/sepgsql.h @@ -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 */ diff --git a/contrib/sepgsql/sepgsql.sql.in b/contrib/sepgsql/sepgsql.sql.in new file mode 100644 index 00000000000..45ffe31e6bd --- /dev/null +++ b/contrib/sepgsql/sepgsql.sql.in @@ -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); diff --git a/contrib/sepgsql/sql/dml.sql b/contrib/sepgsql/sql/dml.sql new file mode 100644 index 00000000000..6fa1eb802e4 --- /dev/null +++ b/contrib/sepgsql/sql/dml.sql @@ -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; diff --git a/contrib/sepgsql/sql/label.sql b/contrib/sepgsql/sql/label.sql new file mode 100644 index 00000000000..31624948782 --- /dev/null +++ b/contrib/sepgsql/sql/label.sql @@ -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; diff --git a/contrib/sepgsql/sql/misc.sql b/contrib/sepgsql/sql/misc.sql new file mode 100644 index 00000000000..a46d8a6b5c8 --- /dev/null +++ b/contrib/sepgsql/sql/misc.sql @@ -0,0 +1,5 @@ +-- +-- Regression Test for Misc Permission Checks +-- + +LOAD '$libdir/sepgsql'; -- failed |