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
This commit is contained in:
Robert Haas 2011-01-23 20:44:48 -05:00
parent e5487f65fd
commit 968bc6fac9
30 changed files with 4246 additions and 1 deletions

122
configure vendored
View File

@ -715,6 +715,7 @@ with_libxslt
with_libxml
XML2_CONFIG
with_ossp_uuid
with_selinux
with_openssl
with_bonjour
with_ldap
@ -837,6 +838,7 @@ with_pam
with_ldap
with_bonjour
with_openssl
with_selinux
with_readline
with_libedit_preferred
with_ossp_uuid
@ -848,6 +850,7 @@ with_gnu_ld
enable_largefile
enable_float4_byval
enable_float8_byval
enable_float8_byval
'
ac_precious_vars='build_alias
host_alias
@ -858,6 +861,7 @@ LDFLAGS
LIBS
CPPFLAGS
CPP
CPPFLAGS
LDFLAGS_EX
LDFLAGS_SL
DOCBOOKSTYLE'
@ -1533,6 +1537,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--without-readline do not use GNU Readline nor BSD Libedit for editing
--with-libedit-preferred
prefer BSD Libedit over GNU Readline
@ -5364,6 +5369,40 @@ fi
$as_echo "$with_openssl" >&6; }
#
# SELinux
#
{ $as_echo "$as_me:$LINENO: checking whether to build with SELinux support" >&5
$as_echo_n "checking whether to build with SELinux support... " >&6; }
# Check whether --with-selinux was given.
if test "${with_selinux+set}" = set; then
withval=$with_selinux;
case $withval in
yes)
:
;;
no)
:
;;
*)
{ { $as_echo "$as_me:$LINENO: error: no argument expected for --with-selinux option" >&5
$as_echo "$as_me: error: no argument expected for --with-selinux option" >&2;}
{ (exit 1); exit 1; }; }
;;
esac
else
with_selinux=no
fi
{ $as_echo "$as_me:$LINENO: result: $with_selinux" >&5
$as_echo "$with_selinux" >&6; }
#
# Readline
@ -9291,6 +9330,89 @@ fi
fi
# for contrib/sepgsql
if test "$with_selinux" = yes; then
{ $as_echo "$as_me:$LINENO: checking for getpeercon_raw in -lselinux" >&5
$as_echo_n "checking for getpeercon_raw in -lselinux... " >&6; }
if test "${ac_cv_lib_selinux_getpeercon_raw+set}" = set; then
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-lselinux $LIBS"
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char getpeercon_raw ();
int
main ()
{
return getpeercon_raw ();
;
return 0;
}
_ACEOF
rm -f conftest.$ac_objext conftest$ac_exeext
if { (ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\""
$as_echo "$ac_try_echo") >&5
(eval "$ac_link") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
$as_echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_c_werror_flag" ||
test ! -s conftest.err
} && test -s conftest$ac_exeext && {
test "$cross_compiling" = yes ||
$as_test_x conftest$ac_exeext
}; then
ac_cv_lib_selinux_getpeercon_raw=yes
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_cv_lib_selinux_getpeercon_raw=no
fi
rm -rf conftest.dSYM
rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_selinux_getpeercon_raw" >&5
$as_echo "$ac_cv_lib_selinux_getpeercon_raw" >&6; }
if test "x$ac_cv_lib_selinux_getpeercon_raw" = x""yes; then
cat >>confdefs.h <<_ACEOF
#define HAVE_LIBSELINUX 1
_ACEOF
LIBS="-lselinux $LIBS"
else
{ { $as_echo "$as_me:$LINENO: error: library 'libselinux' is required for SELinux support" >&5
$as_echo "$as_me: error: library 'libselinux' is required for SELinux support" >&2;}
{ (exit 1); exit 1; }; }
fi
fi
# for contrib/uuid-ossp
if test "$with_ossp_uuid" = yes ; then
{ $as_echo "$as_me:$LINENO: checking for uuid_export in -lossp-uuid" >&5

View File

@ -676,6 +676,13 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
#
# SELinux
#
AC_MSG_CHECKING([whether to build with SELinux support])
PGAC_ARG_BOOL(with, selinux, no, [build with SELinux support])
AC_SUBST(with_selinux)
AC_MSG_RESULT([$with_selinux])
#
# Readline
@ -948,6 +955,12 @@ if test "$with_libxslt" = yes ; then
AC_CHECK_LIB(xslt, xsltCleanupGlobals, [], [AC_MSG_ERROR([library 'xslt' is required for XSLT support])])
fi
# for contrib/sepgsql
if test "$with_selinux" = yes; then
AC_CHECK_LIB(selinux, getpeercon_raw, [],
[AC_MSG_ERROR([library 'libselinux' is required for SELinux support])])
fi
# for contrib/uuid-ossp
if test "$with_ossp_uuid" = yes ; then
AC_CHECK_LIB(ossp-uuid, uuid_export,

View File

@ -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)

View File

@ -163,6 +163,10 @@ seg -
Confidence-interval datatype (GiST indexing example)
by Gene Selkov, Jr. <selkovjr@mcs.anl.gov>
sepgsql -
External security provider using SELinux
by KaiGai Kohei <kaigai@ak.jp.nec.com>
spi -
Various trigger functions, examples for using SPI.

1
contrib/sepgsql/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/sepgsql.sql

25
contrib/sepgsql/Makefile Normal file
View File

@ -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 $@

353
contrib/sepgsql/dml.c Normal file
View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,5 @@
--
-- Regression Test for Misc Permission Checks
--
LOAD '$libdir/sepgsql'; -- failed
ERROR: SELinux: LOAD is not allowed anyway.

446
contrib/sepgsql/hooks.c Normal file
View File

@ -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;
}

477
contrib/sepgsql/label.c Normal file
View File

@ -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);
}

52
contrib/sepgsql/launcher Normal file
View File

@ -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

158
contrib/sepgsql/proc.c Normal file
View File

@ -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;
}

267
contrib/sepgsql/relation.c Normal file
View File

@ -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);
}

98
contrib/sepgsql/schema.c Normal file
View File

@ -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);
}

631
contrib/sepgsql/selinux.c Normal file
View File

@ -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;
}

View File

@ -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;
')

288
contrib/sepgsql/sepgsql.h Normal file
View File

@ -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 */

View File

@ -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);

118
contrib/sepgsql/sql/dml.sql Normal file
View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,5 @@
--
-- Regression Test for Misc Permission Checks
--
LOAD '$libdir/sepgsql'; -- failed

View File

@ -116,6 +116,7 @@ psql -d dbname -f <replaceable>SHAREDIR</>/contrib/<replaceable>module</>.sql
&pgtrgm;
&pgupgrade;
&seg;
&sepgsql;
&contrib-spi;
&sslinfo;
&tablefunc;

View File

@ -129,6 +129,7 @@
<!entity pgupgrade SYSTEM "pgupgrade.sgml">
<!entity seg SYSTEM "seg.sgml">
<!entity contrib-spi SYSTEM "contrib-spi.sgml">
<!entity sepgsql SYSTEM "sepgsql.sgml">
<!entity sslinfo SYSTEM "sslinfo.sgml">
<!entity tablefunc SYSTEM "tablefunc.sgml">
<!entity test-parser SYSTEM "test-parser.sgml">

704
doc/src/sgml/sepgsql.sgml Normal file
View File

@ -0,0 +1,704 @@
<!-- doc/src/sgml/sepgsql.sgml -->
<sect1 id="sepgsql">
<title>sepgsql</title>
<indexterm zone="sepgsql">
<primary>sepgsql</primary>
</indexterm>
<para>
The <filename>sepgsql</> is a module which performs as an external
security provider; to support label based mandatory access control
(MAC) base on <productname>SELinux</> policy.
</para>
<para>
This extension won't build at all unless the installation was configured
with <literal>--with-selinux</>.
</para>
<sect2 id="sepgsql-overview">
<title>Overview</title>
<para>
<productname>PostgreSQL</> provides various kind of hooks. Some of these
hooks can be utilized to make access control decision on the supplied
users' accesses on database objects.
We call plug-in modules making access control decision based on its own
security model as an external security provider.
</para>
<para>
This module acquires control on these strategic points, then it asks
<productname>SELinux</> to check whether the supplied access shall be
allowed, or not. Then, it returns its access control decision.
If violated, this module prevents this access with rising an error for
example.
</para>
<para>
A series of making decision is done independently from the default
database privilege mechanism. Users must be allowed with both of access
control models, whenever they try to access something.
</para>
<para>
We can see <productname>SELinux</> as a function which takes two arguments
then returns a bool value; allowed or denied. The first argument in this
analogy is label of subject which tries to reference a certain obejct.
The other one is label of the object being referenced in this operation.
</para>
<para>
Label is a formatted string,
like <literal>system_u:object_r:sepgsql_table_t:s0</>.
It is not a property depending on characteristics of a certain kind of
object, so we can apply common credentials on either database objects
or others.
</para>
<para>
<productname>PostgreSQL</> 9.1 or later supports
<xref linkend="sql-security-label"> statement that allows to assign
a security label on specified database objects, if user wants to change
label from the creation default.
Also <productname>SELinux</> provides an interface to obtain security
label of the peer process that connected to.
</para>
<para>
These facilities enable to integrate <productname>SELinux</> model within
access controls to database objects. Because it makes access control
decision according to a common centralized security policy (a set of rules),
its decision will be always consistent independent from the way to store
information assets.
</para>
</sect2>
<sect2 id="sepgsql-installation">
<title>Installation</title>
<para>
The <filename>sepgsql</> module requires the following packages to install.
Please check it at first.
</para>
<variablelist>
<varlistentry>
<term><productname>Linux kernel</productname></term>
<listitem>
<para>
v2.6.28 or later with built with SELinux enabled
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><productname>libselinux</productname></term>
<listitem>
<para>
v2.0.80 or later
</para>
<para>
This library provides a set of APIs to communicate with
<productname>SELinux</> in kernel.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><productname>selinux-policy</productname></term>
<listitem>
<para>
v3.9.13 or later
</para>
<para>
The default security policy provides a set of access control rules.
Some of distribution may backports necessary rules, even if base
policy was older than above version.
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
<productname>SE-PostgreSQL</> needs <productname>SELinux</> being
available on the platform. You can check the current setting using
<command>sestatus</>.
<screen>
$ sestatus
SELinux status: enabled
SELinuxfs mount: /selinux
Current mode: enforcing
Mode from config file: enforcing
Policy version: 24
Policy from config file: targeted
</screen>
If disabled or not-installed, you need to set up <productname>SELinux</>
prior to all the installation step of <productname>SE-PostgreSQL</>.
</para>
<para>
On the compile time, add <literal>--with-selinux</> option to
the <command>configure</> script to check existence of
the <productname>libselinux</>, and to set a flag whether
we build this contrib module, or not.
<screen>
$ ./configure --enable-debug --enable-cassert --with-selinux
$ make
$ make install
</screen>
</para>
<para>
Next to the <command>initdb</>, add <literal>'$libdir/sepgsql'</>
to <xref linkend="guc-shared-preload-libraries"> in
the <filename>postgresql.conf</>.
It enables to load <filename>sepgsql</> on the starting up of
postmaster process.
</para>
<para>
Then, load the <filename>sepgsql.sql</filename> script for each databases.
It installs functions corresponding to security label management, and
tries to assign initial labels on the target objects.
</para>
<para>
The following instruction assumes your installation is under the
<filename>/usr/local/pgsql</> directory, and the database cluster is in
<filename>/usr/local/pgsql/data</>. Substitute your paths appropriately.
</para>
<screen>
$ initdb -D $PGDATA
$ vi $PGDATA/postgresql.conf
$ for DBNAME in template0 template1 postgres; do
postgres --single -F -O -c exit_on_error=true -D $PGDATA $DBNAME \
< /usr/local/pgsql/share/contrib/sepgsql.sql > /dev/null
done
</screen>
<para>
If all the installation process was done with no errors, start postmaster
process. <productname>SE-PostgreSQL</> shall prevent violated accesses
according to the security policy of <productname>SELinux</>.
</para>
</sect2>
<sect2 id="sepgsql-regression">
<title>Regression Tests</title>
<para>
The regression test of this module requires a few more configurations
on the platform system, in addition to the above installation process.
See the following steps.
</para>
<para>
First, install the policy package for regression test.
The <filename>sepgsql-regtest.pp</> is a special purpose policy package
that provides a set of rules to be allowed during the regression test
cases. It shall be installed at <filename>/usr/local/pgsql/share/contrib</>
directory in the default setup.
</para>
<para>
You need to install this policy package using <command>semodule</>
command which enables to link supplied policy packages and load them
into the kernel space. If you could install the pakage correctly,
<literal><command>semodule</> -l</> prints sepgsql-regtest as a part
of policy packages currently available.
</para>
<screen>
$ su
# semodule -u /usr/local/pgsql/share/contrib/sepgsql-regtest.pp
# semodule -l
:
sepgsql-regtest 1.03
:
</screen>
<para>
Second, turn on the <literal>sepgsql_regression_test_mode</>.
We don't enable all the rules in the <filename>sepgsql-regtest.pp</>
in the default, for your system's safety.
The <literal>sepgsql_regression_test_mode</literal> parameter is associated
with rules to launch regression test.
It can be turned on using <command>setsebool</> command.
</para>
<screen>
$ su
# setsebool sepgsql_regression_test_mode on
# getsebool sepgsql_regression_test_mode
sepgsql_regression_test_mode --> on
</screen>
<para>
Last, kick the regression test from the <literal>unconfined_t</> domain.
</para>
<para>
This test policy is designed to kick each test cases from the
<literal>unconfined_t</> domain that is a default choice in most of
the known <literal>SELinux</> installation base.
So, you don't need to set up anything special, as long as you didn't
change default configuration of SELinux before.
</para>
<para>
The <command>id</> command tells us the current working domain.
Confirm your shell is now performing with <literal>unconfined_t</>
domain as follows.
</para>
<screen>
$ id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
</screen>
<para>
If not an expected one, you should revert this configuration.
The <xref linkend="sepgsql-resources"> section will give you
some useful hints.
</para>
<para>
Then, you will see the all-green result of regression test,
if we have no problem here.
</para>
<screen>
$ make -C contrib/sepgsql/ installcheck
:
../../src/test/regress/pg_regress --inputdir=. --psqldir=/usr/local/pgsql/bin \
--dbname=contrib_regression --launcher ../../contrib/sepgsql/launcher \
label dml
(using postmaster on Unix socket, default port)
============== dropping database "contrib_regression" ==============
DROP DATABASE
============== creating database "contrib_regression" ==============
CREATE DATABASE
ALTER DATABASE
============== running regression test queries ==============
test label ... ok
test dml ... ok
test misc ... ok
=====================
All 3 tests passed.
=====================
</screen>
<para>
If <command>pg_regress</> failed to launch <command>psql</> command,
here is a hint to fix up the matter.
When we try to launch <command>psql</> command with restrictive
privileges, the <command>psql</> must eb labeled as <literal>bin_t</>.
If not, try to run <command>restorecon</> to fix up security label of
the commands as expected.
</para>
<screen>
$ restorecon -R /usr/local/pgsql/
</screen>
</sect2>
<sect2 id="sepgsql-parameters">
<title>GUC Parameters</title>
<variablelist>
<varlistentry id="guc-sepgsql-permissive" xreflabel="sepgsql.permissive">
<term><varname>sepgsql.permissive</> (<type>boolean</type>)</term>
<indexterm>
<primary><varname>sepgsql.permissive</> configuration parameter</primary>
</indexterm>
<listitem>
<para>
This parameter enables to perform <productname>SE-PostgreSQL</>
in permissive mode independent from the system setting.
The default is off (according to the system setting).
This parameter can only be set in the <filename>postgresql.conf</>
file or on the server command line.
</para>
<para>
We have two performing mode except for disabled; The one is enforcing
mode that checks the security policy on references and actually prevents
violated accesses. The other is permissive mode that only checks
the security policy, but does not prevents anything except for log
generation.
This log shall be utilized for debugging of the security policy itself.
</para>
<para>
When this parameter is on, <productname>SE-PostgreSQL</> performs
in permissive mode, even if the platform system is working on enforcing
mode.
We recommend users to keep the default setting, except for the case
when we develop security policy by ourself.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-sepgsql-debug-audit" xreflabel="sepgsql.debug_audit">
<term><varname>sepgsql.debug_audit</> (<type>boolean</>)</>
<indexterm>
<primary><varname>sepgsql.debug_audit</> configuration parameter</>
</indexterm>
<listitem>
<para>
This parameter enables to print audit messages independent from
the policy setting.
The default is off (according to the security policy setting).
</para>
<para>
The security policy of <productname>SELinux</> also has rules to
control what accesses shall be logged, or not.
In the default, any access violations are logged, but any allowed
accesses are not logged.
</para>
<para>
When this parameter is on, all the possible logs shall be printed
independently from the policy settings.
We recommend to keep the variable turned off in normal cases to
avoid noisy messages.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
<sect2 id="sepgsql-features">
<title>Features</title>
<sect3>
<title>controlled object classes</title>
<para>
The security model of <productname>SELinux</> describes all the access
control rules as a relationship between a subject entity (typically,
it is a client of database) and an object entity.
And, these entities are identified by a security label.
</para>
<para>
We call a set of these rules as security policy.
All the access control decision shall be made according to the security
policy, when we ask SELinux whether the required action shall be allowed
or not.
Thus, we have no way to control accesses on any sort of objects without
security labels.
(<productname>SELinux</> assumes <literal>unlabeled_t</> is assigned,
if no valid security label is assigned on the target object.)
</para>
<para>
This version of <productname>SE-PostgreSQL</> supports to assign
a security label on these database object classes: schema, table, column,
sequence, view and procedure.
Other database object classes are not supported to assign security label
on, right now.
</para>
<para>
A security label shall be automatically assigned to the supported
database objects on their creation time.
This label is called as a default security label; being decided according
to the security policy, or a pair of security label of the client and
upper object for more correctly.
</para>
<para>
A new database object basically inherits security label of the upper
object. A new column inherits security label of its parent table for
instance.
If and when the security policy has special rules called as
type-transition on a pair of the client and upper object, we can assign
an individual label as a default. The upper object depends on sort of
object classes as follows.
</para>
<variablelist>
<varlistentry>
<term>schema</term>
<listitem>
<para>
Its upper object is the current database.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>table</term>
<listitem>
<para>
Its upper object is the schema object which owns the new table.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>column</term>
<listitem>
<para>
Its upper object is the table object which owns the new column.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>sequence</term>
<listitem>
<para>
Its upper object is the schema object which owns the new sequence.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>view</term>
<listitem>
<para>
Its upper object is the schema object which owns the new view.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>procedure</term>
<listitem>
<para>
Its upper object is the schema object which owns the new procedure.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect3>
<sect3>
<title>DML Permissions</title>
<para>
This section introduces what permissions shall be checked on DML;
<literal>SELECT</>, <literal>INSERT</>, <literal>UPDATE</> and
<literal>DELETE</>.
</para>
<para>
DML statements are used to reference or modify contents within
the specified database objects; such as tables or columns.
We basically checks access rights of the client on all the appeared
objects in the given statement, and kind of privileges depend on
class of object and sort of accesses.
</para>
<para>
For tables, <literal>db_table:select</>, <literal>db_table:insert</>,
<literal>db_table:update</> or <literal>db_table:delete</> shall be
checked for all the appeared target tables depending on the sort of
statement;
In addition, <literal>db_table:select</> shall be also checked for
all the tables that containin the columns to be referenced in
<literal>WHERE</> or <literal>RETURNING</> clause, as a data source
of <literal>UPDATE</>, and so on.
</para>
<para>
<synopsis>
UPDATE t1 SET x = 2, y = md5sum(y) WHERE z = 100;
</synopsis>
In this case, we must have <literal>db_table:select</>, not only
<literal>db_table:update</>, because <literal>t1.a</> is referenced
within <literal>WHERE</> clause.
Also note that column-level permission shall be checked individually.
</para>
<para>
The client must be allowed to reference all the appeared tables and
columns, even if they are originated from views then expanded, unlike
the default database privileges, because we intend to apply consistent
access control rules independent from the route to reference contents
of the tables.
</para>
<para>
For columns, <literal>db_column:select</> shall be also checked on
not only the columns being read using <literal>SELECT</>, but being
referenced in other DML statement.
</para>
<para>
Of course, it also checks <literal>db_column:update</> or
<literal>db_column:insert</> on the column being modified by
<literal>UPDATE</> or <literal>INSERT</>.
Note that we have no definition of column-level delete permission,
like as the default database privilege doing.
</para>
<para>
<synopsis>
UPDATE t1 SET x = 2, y = md5sum(y) WHERE z = 100;
</synopsis>
In this case, it checks <literal>db_column:update</> on
the <literal>t1.x</> being updated, <literal>db_column:{select update}</>
on the <literal>t1.y</> being updated and referenced,
and <literal>db_column:select</> on the <literal>t1.z</> being only
referenced in the <literal>WHERE</> clause.
Also note that <literal>db_table:{select update}</> shall be checked
in the table-level granularity.
</para>
<para>
For sequences, <literal>db_sequence:get_value</> when we reference
a sequence object using <literal>SELECT</>, however, note that we
cannot check permissions on execution of corresponding functions
such as <literal>lastval()</> right now, although they performs same
job, because here is no object access hook to acquire controls.
</para>
<para>
For views, <literal>db_view:expand</> shall be checked, then any other
corresponding permissions shall be also checked on the objects being
expanded from the view, individually.
Note that both of permissions have to be allowed.
</para>
<para>
For procedures, <literal>db_procedure:{execute}</> is defined, but not
checked in this version.
</para>
<para>
Here is a few more corner cases.
The default database privilege system allows database superusers to
modify system catalogs using DML commands, and reference or modify
toast tables, however, both of the cases shall be denied when
<productname>SE-PostgreSQL</> is enabled.
</para>
</sect3>
<sect3>
<title>DDL Permissions</title>
<para>
On <xref linkend="sql-security-label"> command, <literal>setattr</> and
<literal>relabelfrom</> shall be checked on the object being relabeled
with an old security label, then <literal>relabelto</> on the supplied
new security label.
</para>
<para>
In a case when multiple label providers are installed and user tries
to set a security label, but is not managed by <productname>SELinux</>,
only <literal>setattr</> should be checked here.
However, it is not unavailable because of limitation of the hook.
</para>
<para>
As we will describe in <xref linkend="sepgsql-limitations"> section,
<productname>SE-PostgreSQL</> does not control any other DDL operations.
</para>
</sect3>
<sect3>
<title>Trusted Procedure</title>
<para>
It is a similar idea to security definer functions or set-uid commands
on operating systems. <productname>SELinux</> provides a feature to
switch privilege of the client (that is a security label of the client
for more correctness) during execution of certain functions; being
called as trusted procedures.
</para>
<para>
A trusted function is a function with a special security label being
set up as a trusted procedure.
So, we need to assign the special security label on the function that
we hope it to perform as a trusted procedure, by administrative users.
The default security policy also provides this special security label.
See the following example.
</para>
<screen>
postgres=# CREATE TABLE customer (
cid int primary key,
cname text,
credit text
);
CREATE TABLE
postgres=# SECURITY LABEL ON COLUMN customer.credit
IS 'system_u:object_r:sepgsql_secret_table_t:s0';
SECURITY LABEL
postgres=# CREATE FUNCTION show_credit(int) RETURNS text
AS 'SELECT regexp_replace(credit, ''-[0-9]+$'', ''-xxxx'', ''g'')
FROM customer WHERE cid = $1'
LANGUAGE sql;
CREATE FUNCTION
postgres=# SECURITY LABEL ON FUNCTION show_credit(int)
IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0';
SECURITY LABEL
</screen>
<para>
Above operations shall be done by administrative users.
</para>
<screen>
postgres=# SELECT * FROM customer;
ERROR: SELinux: security policy violation
postgres=# SELECT cid, cname, show_credit(cid) FROM customer;
cid | cname | show_credit
-----+--------+---------------------
1 | taro | 1111-2222-3333-xxxx
2 | hanako | 5555-6666-7777-xxxx
(2 rows)
</screen>
<para>
In this case, a regular user cannot reference <literal>customer.credit</>
directly, but a trusted procedure <literal>show_credit</> enables us
to print credit number of customers with a bit modification.
</para>
</sect3>
<sect3>
<title>Miscellaneous</title>
<para>
In this version, we reject <xref linkend="sql-load"> command across
the board, because the binary module can override security hooks to
make access control decision. It means a risk to invalidate all the
control by security providers.
</para>
</sect3>
</sect2>
<sect2 id="sepgsql-limitations">
<title>Limitations</title>
<para>
This section introduces limitations of <productname>SE-PostgreSQL</>
in this version.
</para>
<variablelist>
<varlistentry>
<term>Userspace access vector cache</term>
<listitem>
<para>
<productname>SE-PostgreSQL</> tells <productname>SELinux</> its access
control decision. It takes system call invocation being heavy, however,
we can reduce number of the invocations using caching mechanism; called
as access vector cache in <productname>SELinux</>.
Because of code size, <productname>SE-PostgreSQL</> does not support
this mechanism yet.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>DDL Permissions</term>
<listitem>
<para>
Now <productname>PostgreSQL</> does not provide a set of hooks on
the DDL routines.
It means plugin modules cannot acquire control here,
so <productname>SE-PostgreSQL</> does not check DDL Permissions
right now.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Row-level access control</term>
<listitem>
<para>
Now <productname>SE-PostgreSQL</> does not support row-level access
control, because a few needed facilities are not supported yet.
The one is security labels on users' tables. The other is behavior of
optimizer. Also see <xref linkend="rules-privileges"> for more details.
We know similar issue on VIEW.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Covert channels</term>
<listitem>
<para>
<productname>SE-PostgreSQL</> never tries to hide existence of
a certain object, even if user is not allowed to reference.
For example, we can infer an existence of invisible object using
primary-key confliction, foreign-key violation, and so on, even if
we cannot reference contents of these objects.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
<sect2 id="sepgsql-resources">
<title>External Resources</title>
<variablelist>
<varlistentry>
<term><ulink url="http://wiki.postgresql.org/wiki/SEPostgreSQL">SE-PostgreSQL Introduction</ulink></term>
<listitem>
<para>
This wikipage provides a brief-overview, security design, architecture,
administration and upcoming feature for more details.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><ulink url="http://docs.fedoraproject.org/selinux-user-guide/">Fedora SELinux User Guide</ulink></term>
<listitem>
<para>
This document provides wide spectrum of knowledge to administrate
SELinux on your systems.
It primary focuses on Fedora, but not limited to Fedora.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><ulink url="http://docs.fedoraproject.org/selinux-faq">Fedora SELinux FAQ</ulink></term>
<listitem>
<para>
This document provides FAQs about SELinux.
It primary focuses on Fedora, but not limited to Fedora.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
<sect2 id="sepgsql-author">
<title>Author</title>
<para>
KaiGai Kohei (<email>kaigai@ak.jp.nec.com</email>)
</para>
</sect2>
</sect1>

View File

@ -158,6 +158,7 @@ with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
with_ossp_uuid = @with_ossp_uuid@
with_selinux = @with_selinux@
with_libxml = @with_libxml@
with_libxslt = @with_libxslt@
with_system_tzdata = @with_system_tzdata@

View File

@ -84,6 +84,7 @@ bool debug = false;
char *inputdir = ".";
char *outputdir = ".";
char *psqldir = PGBINDIR;
char *launcher = NULL;
static _stringlist *loadlanguage = NULL;
static int max_connections = 0;
static char *encoding = NULL;
@ -1871,6 +1872,7 @@ help(void)
printf(_(" --dlpath=DIR look for dynamic libraries in DIR\n"));
printf(_(" --temp-install=DIR create a temporary installation in DIR\n"));
printf(_(" --use-existing use an existing installation\n"));
printf(_(" --launcher=CMD use CMD as launcher of psql\n"));
printf(_("\n"));
printf(_("Options for \"temp-install\" mode:\n"));
printf(_(" --no-locale use C locale\n"));
@ -1922,6 +1924,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
{"create-role", required_argument, NULL, 18},
{"temp-config", required_argument, NULL, 19},
{"use-existing", no_argument, NULL, 20},
{"launcher", required_argument, NULL, 21},
{NULL, 0, NULL, 0}
};
@ -2015,6 +2018,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
case 20:
use_existing = true;
break;
case 21:
launcher = strdup(optarg);
break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),

View File

@ -41,6 +41,7 @@ extern _stringlist *dblist;
extern bool debug;
extern char *inputdir;
extern char *outputdir;
extern char *launcher;
/*
* This should not be global but every module should be able to read command

View File

@ -33,6 +33,7 @@ psql_start_test(const char *testname,
char outfile[MAXPGPATH];
char expectfile[MAXPGPATH];
char psql_cmd[MAXPGPATH * 3];
size_t offset = 0;
/*
* Look for files in the output dir first, consistent with a vpath search.
@ -58,7 +59,11 @@ psql_start_test(const char *testname,
add_stringlist_item(resultfiles, outfile);
add_stringlist_item(expectfiles, expectfile);
snprintf(psql_cmd, sizeof(psql_cmd),
if (launcher)
offset += snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset,
"%s ", launcher);
snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset,
SYSTEMQUOTE "\"%s%spsql\" -X -a -q -d \"%s\" < \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE,
psqldir ? psqldir : "",
psqldir ? "/" : "",